├── .circleci └── config.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin ├── deploy_doc ├── generate_shell_completion_fixture └── test ├── shard.yml ├── spec ├── features │ ├── aliasing_spec.cr │ ├── detail │ │ ├── generating_help_spec.cr │ │ ├── handling_exit_spec.cr │ │ ├── help_on_parsing_error_spec.cr │ │ ├── help_unparsed_args_spec.cr │ │ ├── optarg_spec.cr │ │ └── versioning_spec.cr │ ├── exit_spec.cr │ ├── help_spec.cr │ ├── inheritance_spec.cr │ ├── option_parser_spec.cr │ ├── previous │ │ ├── access_from_options_spec.cr │ │ ├── access_to_options_spec.cr │ │ ├── aliasing_spec.cr │ │ ├── help_for_subcommands_spec.cr │ │ ├── help_spec.cr │ │ ├── inheritance_spec.cr │ │ └── subcommand_spec.cr │ ├── replacing_spec.cr │ └── subcommand_spec.cr ├── fix │ └── gh6_spec.cr ├── internal │ ├── arg_array_title_spec.cr │ ├── custom_command_name_spec.cr │ ├── dashed_subcommand_spec.cr │ ├── default_help_title_spec.cr │ ├── dynamic_validation_spec.cr │ ├── explicit_exit_calls_standard_exit │ │ └── run.cr │ ├── explicit_exit_calls_standard_exit_spec.cr │ ├── help_handler_dsl_spec.cr │ ├── inclusion_spec.cr │ ├── recursive_run_spec.cr │ ├── reflect_command_name_in_option_model_spec.cr │ ├── replacing_spec.cr │ ├── rescue_parsing_error_spec.cr │ ├── shell_completion │ │ ├── class.cr │ │ └── fixtures │ │ │ ├── command.bash │ │ │ ├── command.zsh │ │ │ └── zsh │ │ │ └── _command │ ├── shell_completion_spec.cr │ ├── three_level_command_name_spec.cr │ ├── unknown_option_model_item_spec.cr │ └── version_handler_dsl_spec.cr ├── spec_helper.cr └── wiki │ ├── handling_events │ └── run.cr │ ├── handling_events_spec.cr │ ├── shell_completion │ ├── class.cr │ └── fixtures │ │ ├── ticket-to-ride-completion.bash │ │ ├── ticket-to-ride-completion.zsh │ │ └── zsh │ │ └── _ticket_to_ride │ ├── shell_completion_spec.cr │ ├── the_exit_ways_spec.cr │ └── using_named_ios_spec.cr └── src ├── cli.cr ├── lib.cr ├── lib ├── command.cr ├── command_base.cr ├── command_base │ └── macros.cr ├── command_class.cr ├── command_class │ └── alias.cr ├── exit.cr ├── helps.cr ├── helps │ ├── base.cr │ ├── command.cr │ └── supercommand.cr ├── io_hash.cr ├── ios.cr ├── ios │ └── pipe.cr ├── macros.cr ├── macros │ ├── __any_item_of.cr │ └── __any_of.cr ├── option_metadata.cr ├── option_model.cr ├── option_model │ └── dsl.cr ├── option_model_definitions.cr ├── option_model_definitions │ └── subcommand.cr ├── option_model_mixin.cr ├── option_value_metadata.cr ├── supercommand.cr ├── util.cr └── util │ └── var.cr ├── spec.cr ├── spec └── helper.cr └── version.cr /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | workflows: 3 | version: 2 4 | test: 5 | jobs: 6 | - test_crystal_0_24_2 7 | jobs: 8 | test_crystal_0_24_2: 9 | docker: 10 | - image: 'crystallang/crystal:0.24.2' 11 | working_directory: ~/repo 12 | steps: 13 | - checkout 14 | - restore_cache: 15 | keys: 16 | - 'v1-dependencies-{{ checksum "shard.yml" }}' 17 | - v1-dependencies- 18 | - run: shards 19 | - save_cache: 20 | paths: 21 | - .shards 22 | - lib 23 | key: 'v1-dependencies-{{ checksum "shard.yml" }}' 24 | - run: bin/test 25 | test_crystal_nightly: 26 | docker: 27 | - image: 'crystallang/crystal:nightly' 28 | working_directory: ~/repo 29 | steps: 30 | - checkout 31 | - restore_cache: 32 | keys: 33 | - 'v1-dependencies-{{ checksum "shard.yml" }}' 34 | - v1-dependencies- 35 | - run: shards 36 | - save_cache: 37 | paths: 38 | - .shards 39 | - lib 40 | key: 'v1-dependencies-{{ checksum "shard.yml" }}' 41 | - run: bin/test 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /.crystal/ 3 | /.shards/ 4 | /lib 5 | 6 | # Libraries don't need dependency lock 7 | # Dependencies will be locked in application that uses them 8 | /shard.lock 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | 3 | script: 4 | - bin/test 5 | 6 | after_success: 7 | - if [ "$TRAVIS_BRANCH" == "master" ]; then bin/deploy_doc; fi 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog 3 | 4 | ## :) 5 | 6 | ### Format 7 | 8 | Based on [Keep a Changelog]. 9 | 10 | ### Versioning Policy 11 | 12 | [Semantic Versioning Caret] 13 | 14 | ## Versions 15 | 16 | ### [Edge (HEAD)][edge] 17 | 18 | ### [0.7.0] 19 | 20 | #### Changed 21 | 22 | * Updates Crystal to 0.24.2. 23 | 24 | #### Fixed 25 | 26 | * Uses IO as class, not module. 27 | 28 | [Keep a Changelog]: http://keepachangelog.com/en/1.0.0/ 29 | [Semantic Versioning Caret]: https://github.com/malform/semver-caret 30 | [edge]: https://github.com/mosop/cli/compare/v0.7.0...HEAD 31 | [0.7.0]: https://github.com/mosop/cli/compare/v0.6.10...v0.7.0 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 mosop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crystal CLI 2 | 3 | Yet another Crystal library for building command-line interface applications. 4 | 5 | [![CircleCI](https://circleci.com/gh/mosop/cli.svg?style=shield)](https://circleci.com/gh/mosop/cli) 6 | 7 | ## Installation 8 | 9 | Add this to your application's `shard.yml`: 10 | 11 | ```yaml 12 | dependencies: 13 | cli: 14 | github: mosop/cli 15 | ``` 16 | 17 | 18 | 19 | ## Code Samples 20 | 21 | ### Option Parser 22 | 23 | ```crystal 24 | class Hello < Cli::Command 25 | class Options 26 | bool "--bye" 27 | arg "to" 28 | end 29 | 30 | def run 31 | if args.bye? 32 | print "Goodbye" 33 | else 34 | print "Hello" 35 | end 36 | puts " #{args.to}!" 37 | end 38 | end 39 | 40 | Hello.run %w(world) # prints "Hello, world!" 41 | Hello.run %w(--bye world) # prints "Goodbye, world!" 42 | ``` 43 | 44 | ### Subcommand 45 | 46 | ```crystal 47 | class Polygon < Cli::Supercommand 48 | command "triangle", default: true 49 | 50 | class Triangle < Cli::Command 51 | def run 52 | puts 3 53 | end 54 | end 55 | 56 | class Square < Cli::Command 57 | def run 58 | puts 4 59 | end 60 | end 61 | 62 | class Hexagon < Cli::Command 63 | def run 64 | puts 6 65 | end 66 | end 67 | end 68 | 69 | Polygon.run %w(triangle) # prints "3" 70 | Polygon.run %w(square) # prints "4" 71 | Polygon.run %w(hexagon) # prints "6" 72 | Polygon.run %w() # prints "3" 73 | ``` 74 | 75 | ### Replacing 76 | 77 | ```crystal 78 | class New < Cli::Command 79 | def run 80 | puts "new!" 81 | end 82 | end 83 | 84 | class Obsolete < Cli::Command 85 | replacer_command New 86 | end 87 | 88 | Obsolete.run # prints "new!" 89 | ``` 90 | 91 | ### Inheritance 92 | 93 | ```crystal 94 | abstract class Role < Cli::Command 95 | class Options 96 | string "--name" 97 | end 98 | end 99 | 100 | class Chase < Cli::Supercommand 101 | class Mouse < Role 102 | def run 103 | puts "#{options.name} runs away." 104 | end 105 | end 106 | 107 | class Cat < Role 108 | def run 109 | puts "#{options.name} runs into a wall." 110 | end 111 | end 112 | end 113 | 114 | Chase.run %w(mouse --name Jerry) # prints "Jerry runs away." 115 | Chase.run %w(cat --name Tom) # prints "Tom runs into a wall." 116 | ``` 117 | 118 | ### Help 119 | 120 | ```crystal 121 | class Call < Cli::Command 122 | class Help 123 | header "Receives an ancient message." 124 | footer "(C) 20XX mosop" 125 | end 126 | 127 | class Options 128 | arg "message", desc: "your message to call them", required: true 129 | bool "-w", not: "-W", desc: "wait for response", default: true 130 | help 131 | end 132 | end 133 | 134 | Call.run %w(--help) 135 | ``` 136 | 137 | Output: 138 | 139 | ``` 140 | call [OPTIONS] MESSAGE 141 | 142 | Receives an ancient message. 143 | 144 | Arguments: 145 | MESSAGE (required) your message to call them 146 | 147 | Options: 148 | -w wait for response 149 | (default: true) 150 | -W disable -w 151 | -h, --help show this help 152 | 153 | (C) 20XX mosop 154 | ``` 155 | 156 | ### Versioning 157 | 158 | ```crystal 159 | class Command < Cli::Supercommand 160 | version "1.0.0" 161 | 162 | class Options 163 | version 164 | end 165 | end 166 | 167 | Command.run %w(-v) # prints 1.0.0 168 | ``` 169 | 170 | ### Shell Completion 171 | 172 | ```crystal 173 | class TicketToRide < Cli::Command 174 | class Options 175 | string "--by", any_of: %w(train plane taxi) 176 | arg "for", any_of: %w(kyoto kanazawa kamakura) 177 | end 178 | end 179 | 180 | puts TicketToRide.generate_bash_completion 181 | # or 182 | puts TicketToRide.generate_zsh_completion 183 | ``` 184 | 185 | ## Usage 186 | 187 | ```crystal 188 | require "cli" 189 | ``` 190 | 191 | and see: 192 | 193 | * [Code Samples](#code_samples) 194 | * [Wiki](https://github.com/mosop/cli/wiki) 195 | * [API Document](http://mosop.me/cli/Cli.html) 196 | 197 | ## Want to Do 198 | 199 | - Application-Level Logger 200 | - I18n 201 | 202 | ## Release Notes 203 | 204 | See [Releases](https://github.com/mosop/cli/releases). 205 | -------------------------------------------------------------------------------- /bin/deploy_doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "yaml" 3 | require "tmpdir" 4 | 5 | VERSION = /^refs\/tags\/v(\d+(\.\d+)*)$/ 6 | 7 | Dir.mktmpdir do |dir| 8 | cred_file = File.join(dir, ".git-credentials") 9 | `touch #{cred_file}` 10 | `chmod 600 #{cred_file}` 11 | File.write cred_file, "https://mosop:#{ENV["MOSOP_GITHUB_ACCESS_TOKEN"]}@github.com\n" 12 | Dir.mktmpdir do |dir| 13 | Dir.chdir(dir) do 14 | `git init` 15 | `git config --local credential.helper 'store --file #{cred_file}'` 16 | `git remote add origin https://github.com/mosop/cli.git` 17 | gh_pages_found = false 18 | versions = [] 19 | `git ls-remote`.chomp.split("\n").each do |line| 20 | sha, ref = line.split(/\s+/) 21 | if VERSION =~ ref 22 | versions << Gem::Version.new($1) 23 | elsif ref == "refs/heads/gh-pages" 24 | gh_pages_found = true 25 | end 26 | end 27 | version = versions.sort.last 28 | exit unless version 29 | `git fetch origin v#{version}:tags/v#{version}` 30 | `git checkout -b latest v#{version}` 31 | `crystal deps` 32 | `crystal doc` 33 | doc_dir = File.join(dir, "doc") 34 | Dir.mktmpdir do |dir| 35 | Dir.chdir(dir) do 36 | ver_dir = File.join(dir, "v#{version}") 37 | if gh_pages_found 38 | `git init` 39 | `git config --local credential.helper 'store --file #{cred_file}'` 40 | `git remote add origin https://github.com/mosop/cli.git` 41 | `git pull origin gh-pages` 42 | `git checkout -b gh-pages origin/gh-pages` 43 | Dir.glob("*") do |f| 44 | `rm -rf #{f}` unless f.start_with?("v") 45 | end 46 | `rm -rf .git` 47 | end 48 | `cp -a #{doc_dir}/* #{dir}/` 49 | `rm -rf #{ver_dir}` 50 | `mkdir -p #{ver_dir}` 51 | `cp -a #{doc_dir}/* #{ver_dir}/` 52 | `git init` 53 | `git config --local credential.helper 'store --file #{cred_file}'` 54 | `git remote add origin https://github.com/mosop/cli.git` 55 | `git config --add --local user.name mosop` 56 | `git config --add --local user.email mosop@users.noreply.github.com` 57 | `git checkout -b gh-pages` 58 | `git add .` 59 | `git commit -m '#{Time.now.strftime("%Y%m%d")}'` 60 | `git push -f origin gh-pages` 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /bin/generate_shell_completion_fixture: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env crystal 2 | 3 | require "../src/cli" 4 | require "../spec/internal/shell_completion/class" 5 | require "../spec/wiki/shell_completion/class" 6 | 7 | macro __generate(dir, klass, filename, filename2) 8 | %dir = {{dir}} 9 | %zsh_dir = "#{%dir}/zsh" 10 | Dir.mkdir_p %zsh_dir 11 | %data = {{klass}}.generate_bash_completion 12 | File.write File.join(%dir, {{filename}} + ".bash"), %data + "\n" 13 | %data = {{klass}}.generate_zsh_completion(functional: false) 14 | File.write File.join(%dir, {{filename}} + ".zsh"), %data + "\n" 15 | %data = {{klass}}.generate_zsh_completion 16 | File.write File.join(%zsh_dir, {{filename2}}), %data + "\n" 17 | end 18 | 19 | __generate "#{__DIR__}/../spec/internal/shell_completion/fixtures", CliInternalSpecs::ShellCompletion::Command, "command", "_command" 20 | __generate "#{__DIR__}/../spec/wiki/shell_completion/fixtures", CliWikiShellCompletionFeature::TicketToRide, "ticket-to-ride-completion", "_ticket_to_ride" 21 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | find spec -type f -name "*_spec.cr" -print0 | while IFS= read -r -d $'\0' line; do 6 | crystal spec "$line" 7 | done 8 | 9 | crystal spec 10 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: cli 2 | version: 0.7.0 3 | 4 | authors: 5 | - mosop 6 | 7 | crystal: 0.24.2 8 | 9 | dependencies: 10 | optarg: 11 | github: mosop/optarg 12 | version: ~> 0.5.8 13 | string_inflection: 14 | github: mosop/string_inflection 15 | version: ~> 0.2.1 16 | 17 | development_dependencies: 18 | have_files: 19 | github: mosop/have_files 20 | version: ~> 0.4.0 21 | crystal_plus: 22 | github: mosop/crystal_plus 23 | version: ~> 0.1.4 24 | 25 | license: MIT 26 | -------------------------------------------------------------------------------- /spec/features/aliasing_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliAliasingFeature 4 | class Command < ::Cli::Supercommand 5 | command "l", aliased: "loooooooooong" 6 | 7 | class Loooooooooong < ::Cli::Command 8 | def run 9 | puts "sleep!" 10 | end 11 | end 12 | end 13 | 14 | it name do 15 | Command.run %w(l) do |cmd| 16 | cmd.out.gets_to_end.should eq "sleep!\n" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/features/detail/generating_help_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliGeneratingHelpFeatureDetail 4 | include Cli::Spec::Helper 5 | 6 | class Smile < Cli::Command 7 | class Help 8 | header "Smiles n times." 9 | footer "(C) 20XX mosop" 10 | end 11 | 12 | class Options 13 | arg "face", required: true, desc: "your face like :), :(, :P" 14 | string "--times", var: "NUMBER", default: "1", desc: "number of times to display" 15 | help 16 | end 17 | end 18 | 19 | it name do 20 | Smile.run(%w(--help)).should exit_command(output: <<-EOS 21 | smile [OPTIONS] FACE 22 | 23 | Smiles n times. 24 | 25 | Arguments: 26 | FACE your face like :), :(, :P 27 | 28 | Options: 29 | --times NUMBER number of times to display 30 | (default: 1) 31 | -h, --help show this help 32 | 33 | (C) 20XX mosop 34 | EOS 35 | ) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/features/detail/handling_exit_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliHandlingExitFeatureDetail 4 | include Cli::Spec::Helper 5 | 6 | macro test(run, code = 0, output = nil, err = nil) 7 | class Exit%x < Cli::Command 8 | class Help 9 | title "title" 10 | end 11 | def run 12 | {{run.id}} 13 | end 14 | end 15 | describe name do 16 | it {{run}} do 17 | Exit%x.run.should exit_command(code: {{code}}, output: {{output}}, error: {{err}}) 18 | end 19 | end 20 | end 21 | 22 | test %(exit!) 23 | test %(exit! "message"), output: "message" 24 | test %(exit! "message", error: true), err: "message", code: 1 25 | test %(exit! "message", code: 22), err: "message", code:22 26 | test %(error! "message"), err: "message", code: 1 27 | test %(error! "message", code: 22), err: "message", code: 22 28 | test %(help!), output: "title" 29 | test %(help! "message"), err: "message\n\ntitle", code: 1 30 | test %(help! "message", code: 22), err: "message\n\ntitle", code: 22 31 | test %(help! "message", error: false), output: "message\n\ntitle" 32 | end 33 | -------------------------------------------------------------------------------- /spec/features/detail/help_on_parsing_error_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliHelpOnParsingErrorFeatureDetail 4 | include Cli::Spec::Helper 5 | 6 | class Bookmark < ::Cli::Command 7 | class Options 8 | arg "url", required: true, desc: "a URL to be bookmarked" 9 | end 10 | end 11 | 12 | class Disabled < ::Cli::Command 13 | disable_help_on_parsing_error! 14 | class Options 15 | arg "url", required: true, desc: "a URL to be bookmarked" 16 | end 17 | end 18 | 19 | it name do 20 | Bookmark.run.should exit_command(error: <<-EOS 21 | Parsing Error: The URL argument is required. 22 | 23 | bookmark URL 24 | 25 | Arguments: 26 | URL a URL to be bookmarked 27 | EOS 28 | ) 29 | 30 | Disabled.run.should exit_command(error: <<-EOS 31 | Parsing Error: The URL argument is required. 32 | EOS 33 | ) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/features/detail/help_unparsed_args_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliHelpUnparsedArgsFeatureDetail 4 | include Cli::Spec::Helper 5 | 6 | class Exec < Cli::Command 7 | class Options 8 | arg "command", required: true, stop: true, desc: "command name" 9 | help 10 | end 11 | 12 | class Help 13 | unparsed_args "[ARG1 ARG2 ...]" 14 | end 15 | end 16 | 17 | it name do 18 | Exec.run(%w(-h)).should exit_command(output: <<-EOS 19 | exec [OPTIONS] COMMAND [ARG1 ARG2 ...] 20 | 21 | Arguments: 22 | COMMAND command name 23 | 24 | Options: 25 | -h, --help show this help 26 | EOS 27 | ) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/features/detail/optarg_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliOptargFeatureDetail 4 | class Command < Cli::Command 5 | class Options 6 | arg "arg" 7 | string "-s" 8 | terminator "--" 9 | end 10 | 11 | def run 12 | puts args.arg 13 | puts options.s 14 | puts unparsed_args[0] 15 | end 16 | end 17 | 18 | it name do 19 | Command.run %w(foo -s bar -- baz) do |cmd| 20 | cmd.out.gets_to_end.should eq <<-EOS 21 | foo 22 | bar 23 | baz\n 24 | EOS 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/features/detail/versioning_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliVersioningFeatureDetail 4 | include Cli::Spec::Helper 5 | 6 | class Command < ::Cli::Supercommand 7 | version "1.1.0" 8 | command "inherit" 9 | command "specific" 10 | 11 | class Options 12 | version 13 | end 14 | 15 | module Commands 16 | class Inherit < ::Cli::Command 17 | class Options 18 | version 19 | end 20 | end 21 | 22 | class Specific < ::Cli::Command 23 | version "1.0.0" 24 | 25 | class Options 26 | version 27 | end 28 | end 29 | end 30 | end 31 | 32 | it name do 33 | Command.run(%w(-v)).should exit_command("1.1.0") 34 | Command.run(%w(inherit -v)).should exit_command("1.1.0") 35 | Command.run(%w(specific -v)).should exit_command("1.0.0") 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/features/exit_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliExitFeature 4 | include Cli::Spec::Helper 5 | 6 | class Open < Cli::Command 7 | class Options 8 | arg "word" 9 | end 10 | 11 | def valid? 12 | args.word == "sesame" 13 | end 14 | 15 | def run 16 | if valid? 17 | exit! "Opened!" 18 | else 19 | error! "Not opened!" 20 | end 21 | end 22 | end 23 | 24 | it name do 25 | Open.run(%w(sesame)).should exit_command(output: "Opened!", code: 0) 26 | Open.run(%w(paprika)).should exit_command(error: "Not opened!", code: 1) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/features/help_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliHelpFeature 4 | include Cli::Spec::Helper 5 | 6 | class Lang < Cli::Command 7 | class Help 8 | header "Converts a language to other languages." 9 | footer "(C) 20XX mosop" 10 | end 11 | 12 | class Options 13 | arg "from", desc: "source language", required: true 14 | array "--to", var: "LANG", desc: "target language", default: %w(crystal) 15 | string "--indent", var: "NUM", desc: "set number of tab size", default: "2" 16 | bool "--std", not: "--Std", desc: "use standard library", default: true 17 | help 18 | end 19 | end 20 | 21 | it name do 22 | Lang.run(%w(--help)).should exit_command(output: <<-EOS 23 | lang [OPTIONS] FROM 24 | 25 | Converts a language to other languages. 26 | 27 | Arguments: 28 | FROM source language 29 | 30 | Options: 31 | --indent NUM set number of tab size 32 | (default: 2) 33 | --std use standard library 34 | (enabled as default) 35 | --Std disable --std 36 | --to LANG (multiple) target language 37 | (default: crystal) 38 | -h, --help show this help 39 | 40 | (C) 20XX mosop 41 | EOS 42 | ) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/features/inheritance_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInheritanceFeature 4 | abstract class Role < Cli::Command 5 | class Options 6 | string "--name" 7 | end 8 | end 9 | 10 | class Chase < ::Cli::Supercommand 11 | class Mouse < Role 12 | def run 13 | puts "#{options.name} runs away." 14 | end 15 | end 16 | 17 | class Cat < Role 18 | def run 19 | puts "#{options.name} runs into a wall." 20 | end 21 | end 22 | end 23 | 24 | it name do 25 | Chase.run %w(mouse --name Jerry) do |cmd| 26 | cmd.out.gets_to_end.should eq "Jerry runs away.\n" 27 | end 28 | Chase.run(%w(cat --name Tom)) do |cmd| 29 | cmd.out.gets_to_end.should eq "Tom runs into a wall.\n" 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/features/option_parser_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliOptionParserFeature 4 | class Command < Cli::Command 5 | class Options 6 | string "--hello" 7 | end 8 | 9 | def run 10 | puts "Hello, #{options.hello}!" 11 | end 12 | end 13 | 14 | it name do 15 | Command.run %w(--hello world) do |cmd| 16 | cmd.out.gets_to_end.should eq "Hello, world!\n" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/features/previous/access_from_options_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliAccessFromOptionsPreviousFeature 4 | class Command < Cli::Command 5 | class Options 6 | on("--go") { command.go(with: "the Wind") } 7 | end 8 | 9 | def go(with some) 10 | puts "Gone with #{some}" 11 | raise ::Cli::Exit.new 12 | end 13 | end 14 | 15 | it name do 16 | Command.run(%w(--go)) do |cmd| 17 | cmd.out.gets_to_end.should eq "Gone with the Wind\n" 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/features/previous/access_to_options_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliAccessToOptionsPreviousFeature 4 | class Command < Cli::Command 5 | class Options 6 | string "--option" 7 | terminator "--" 8 | end 9 | 10 | def run 11 | puts "#{options.option} #{args[0]} #{unparsed_args[0]}" 12 | end 13 | end 14 | 15 | it name do 16 | Command.run %w(--option foo bar -- baz) do |cmd| 17 | cmd.out.gets_to_end.should eq "foo bar baz\n" 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/features/previous/aliasing_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliAliasingPreviousFeature 4 | class Command < ::Cli::Supercommand 5 | command "loooooooooong" 6 | command "l", aliased: "loooooooooong" 7 | 8 | module Commands 9 | class Loooooooooong < ::Cli::Command 10 | def run 11 | puts "sleep!" 12 | end 13 | end 14 | end 15 | end 16 | 17 | it name do 18 | Command.run %w(l) do |cmd| 19 | cmd.out.gets_to_end.should eq "sleep!\n" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/features/previous/help_for_subcommands_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliHelpForSubcommandsPreviousFeature 4 | include Cli::Spec::Helper 5 | 6 | class Package < ::Cli::Supercommand 7 | command "install", default: true 8 | command "update" 9 | command "remove" 10 | command "uninstall", aliased: "remove" 11 | 12 | class Options 13 | help 14 | end 15 | 16 | abstract class Base < Cli::Command 17 | class Options 18 | arg "package_name", desc: "specify package's name", required: true 19 | help 20 | end 21 | end 22 | 23 | module Commands 24 | class Install < Base 25 | class Options 26 | string "-v", var: "VERSION", desc: "specify package's version" 27 | end 28 | 29 | class Help 30 | caption "install package" 31 | end 32 | end 33 | 34 | class Update < Base 35 | class Help 36 | caption "update package" 37 | end 38 | 39 | class Options 40 | bool "--major", desc: "update major version if any" 41 | end 42 | end 43 | 44 | class Remove < Base 45 | class Help 46 | caption "remove package" 47 | end 48 | 49 | class Options 50 | bool "-f", desc: "force to remove" 51 | end 52 | end 53 | end 54 | end 55 | 56 | describe name do 57 | it "prints supercommand's help" do 58 | Package.run(%w(--help)).should exit_command(output: <<-EOS 59 | package [OPTIONS] [SUBCOMMAND] 60 | 61 | Subcommands: 62 | install (default) install package 63 | remove remove package 64 | uninstall alias for remove 65 | update update package 66 | 67 | Options: 68 | -h, --help show this help 69 | EOS 70 | ) 71 | end 72 | 73 | it "prints install's help" do 74 | Package.run(%w(install --help)).should exit_command(output: <<-EOS 75 | package install [OPTIONS] PACKAGE_NAME 76 | 77 | Arguments: 78 | PACKAGE_NAME specify package's name 79 | 80 | Options: 81 | -v VERSION specify package's version 82 | -h, --help show this help 83 | EOS 84 | ) 85 | end 86 | 87 | it "prints update's help" do 88 | Package.run(%w(update --help)).should exit_command(output: <<-EOS 89 | package update [OPTIONS] PACKAGE_NAME 90 | 91 | Arguments: 92 | PACKAGE_NAME specify package's name 93 | 94 | Options: 95 | --major update major version if any 96 | -h, --help show this help 97 | EOS 98 | ) 99 | end 100 | 101 | it "prints remove's help" do 102 | Package.run(%w(remove --help)).should exit_command(output: <<-EOS 103 | package remove [OPTIONS] PACKAGE_NAME 104 | 105 | Arguments: 106 | PACKAGE_NAME specify package's name 107 | 108 | Options: 109 | -f force to remove 110 | -h, --help show this help 111 | EOS 112 | ) 113 | end 114 | 115 | it "prints uninstall's help" do 116 | Package.run(%w(uninstall --help)).should exit_command(output: <<-EOS 117 | package remove [OPTIONS] PACKAGE_NAME 118 | 119 | Arguments: 120 | PACKAGE_NAME specify package's name 121 | 122 | Options: 123 | -f force to remove 124 | -h, --help show this help 125 | EOS 126 | ) 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /spec/features/previous/help_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliHelpPreviousFeature 4 | include Cli::Spec::Helper 5 | 6 | class Lang < Cli::Command 7 | class Help 8 | header "Converts a language to other languages." 9 | footer "(C) 20XX mosop" 10 | end 11 | 12 | class Options 13 | arg "from", desc: "source language", required: true 14 | array "--to", var: "LANG", desc: "target language", default: %w(crystal) 15 | string "--indent", var: "NUM", desc: "set number of tab size", default: "2" 16 | bool "--std", not: "--Std", desc: "use standard library", default: true 17 | help 18 | end 19 | end 20 | 21 | it name do 22 | Lang.run(%w(--help)).should exit_command(output: <<-EOS 23 | lang [OPTIONS] FROM 24 | 25 | Converts a language to other languages. 26 | 27 | Arguments: 28 | FROM source language 29 | 30 | Options: 31 | --indent NUM set number of tab size 32 | (default: 2) 33 | --std use standard library 34 | (enabled as default) 35 | --Std disable --std 36 | --to LANG (multiple) target language 37 | (default: crystal) 38 | -h, --help show this help 39 | 40 | (C) 20XX mosop 41 | EOS 42 | ) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/features/previous/inheritance_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliInheritancePreviousFeature 4 | class Role < Cli::Command 5 | class Options 6 | string "--name" 7 | end 8 | end 9 | 10 | class Chase < ::Cli::Supercommand 11 | command "mouse" 12 | command "cat" 13 | 14 | module Commands 15 | class Mouse < Role 16 | def run 17 | puts "#{options.name} runs away." 18 | end 19 | end 20 | 21 | class Cat < Role 22 | def run 23 | puts "#{options.name} runs into a wall." 24 | end 25 | end 26 | end 27 | end 28 | 29 | it name do 30 | Chase.run %w(mouse --name Jerry) do |cmd| 31 | cmd.out.gets_to_end.should eq "Jerry runs away.\n" 32 | end 33 | Chase.run %w(cat --name Tom) do |cmd| 34 | cmd.out.gets_to_end.should eq "Tom runs into a wall.\n" 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/features/previous/subcommand_spec.cr: -------------------------------------------------------------------------------- 1 | require "../../spec_helper" 2 | 3 | module CliSubcommandPreviousFeature 4 | class Polygon < Cli::Supercommand 5 | command "triangle", default: true 6 | command "square" 7 | command "hexagon" 8 | 9 | module Commands 10 | class Triangle < Cli::Command 11 | def run 12 | puts 3 13 | end 14 | end 15 | 16 | class Square < Cli::Command 17 | def run 18 | puts 4 19 | end 20 | end 21 | 22 | class Hexagon < Cli::Command 23 | def run 24 | puts 6 25 | end 26 | end 27 | end 28 | end 29 | 30 | it name do 31 | Polygon.run %w(triangle) do |cmd| 32 | cmd.out.gets_to_end.should eq "3\n" 33 | end 34 | Polygon.run %w(square) do |cmd| 35 | cmd.out.gets_to_end.should eq "4\n" 36 | end 37 | Polygon.run %w(hexagon) do |cmd| 38 | cmd.out.gets_to_end.should eq "6\n" 39 | end 40 | Polygon.run %w() do |cmd| 41 | cmd.out.gets_to_end.should eq "3\n" 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/features/replacing_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliReplacingFeature 4 | class New < Cli::Command 5 | def run 6 | puts "new!" 7 | end 8 | end 9 | 10 | class Obsolete < Cli::Command 11 | replacer_command New 12 | end 13 | 14 | it name do 15 | Obsolete.run do |cmd| 16 | cmd.out.gets_to_end.chomp.should eq "new!" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/features/subcommand_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliSubcommandFeature 4 | class Polygon < Cli::Supercommand 5 | command "triangle", default: true 6 | 7 | class Triangle < Cli::Command 8 | def run 9 | puts 3 10 | end 11 | end 12 | 13 | class Square < Cli::Command 14 | def run 15 | puts 4 16 | end 17 | end 18 | 19 | class Hexagon < Cli::Command 20 | def run 21 | puts 6 22 | end 23 | end 24 | end 25 | 26 | it name do 27 | Polygon.run %w(triangle) do |cmd| 28 | cmd.out.gets_to_end.should eq "3\n" 29 | end 30 | Polygon.run %w(square) do |cmd| 31 | cmd.out.gets_to_end.should eq "4\n" 32 | end 33 | Polygon.run %w(hexagon) do |cmd| 34 | cmd.out.gets_to_end.should eq "6\n" 35 | end 36 | Polygon.run %w() do |cmd| 37 | cmd.out.gets_to_end.should eq "3\n" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/fix/gh6_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliFixes::Gh6 4 | module CliFixes 5 | module Gh6 6 | class Main < Cli::Supercommand 7 | class Help 8 | header "Main header" 9 | footer "Main footer" 10 | end 11 | end 12 | 13 | it name do 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/internal/arg_array_title_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalArgArrayTitleFeature 4 | class Command < Cli::Command 5 | class Options 6 | arg_array "name" 7 | end 8 | end 9 | 10 | class WithMin < Cli::Command 11 | class Options 12 | arg_array "name", min: 3 13 | end 14 | end 15 | 16 | it name do 17 | Command.__klass.default_title.should eq "command [NAME1 NAME2...]" 18 | WithMin.__klass.default_title.should eq "with-min NAME1 NAME2 NAME3 [NAME4 NAME5...]" 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/internal/custom_command_name_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalCustomCommandNameFeature 4 | class Command < Cli::Supercommand 5 | command_name "custom" 6 | end 7 | 8 | it name do 9 | Command.__klass.global_name.should eq "custom" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/internal/dashed_subcommand_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalDashedSubcommandFeature 4 | class Command < Cli::Supercommand 5 | command "dashed-command" 6 | 7 | module Commands 8 | class DashedCommand < Cli::Command 9 | def run 10 | puts "ok" 11 | end 12 | end 13 | end 14 | end 15 | 16 | it name do 17 | Command.run %w(dashed-command) do |cmd| 18 | cmd.out.gets_to_end.should eq "ok\n" 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/internal/default_help_title_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalDefaultHelpTitleFeature 4 | class Optional < Cli::Command 5 | class Options 6 | string "-s" 7 | end 8 | end 9 | 10 | class Required < Cli::Command 11 | class Options 12 | string "-s", required: true 13 | end 14 | end 15 | 16 | class OptionalArg < Cli::Command 17 | class Options 18 | arg "arg" 19 | end 20 | end 21 | 22 | class RequiredArg < Cli::Command 23 | class Options 24 | arg "arg", required: true 25 | end 26 | end 27 | 28 | class Supercommand < Cli::Supercommand 29 | end 30 | 31 | class SupercommandWithDefault < Cli::Supercommand 32 | command "sub", default: true 33 | 34 | module Commands 35 | class Sub < Cli::Command 36 | end 37 | end 38 | end 39 | 40 | it name do 41 | Optional.__klass.default_title.should eq "optional [OPTIONS]" 42 | Required.__klass.default_title.should eq "required OPTIONS" 43 | OptionalArg.__klass.default_title.should eq "optional-arg [ARG]" 44 | RequiredArg.__klass.default_title.should eq "required-arg ARG" 45 | Supercommand.__klass.default_title.should eq "supercommand SUBCOMMAND" 46 | SupercommandWithDefault.__klass.default_title.should eq "supercommand-with-default [SUBCOMMAND]" 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/internal/dynamic_validation_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalDynamicValidationFeature 4 | include Cli::Spec::Helper 5 | 6 | class Command < Cli::Command 7 | class Options 8 | arg_array "name" do |definition| 9 | definition.on_validate do |context, options| 10 | context.validate_element_inclusion options.command.expected 11 | end 12 | end 13 | end 14 | 15 | def expected 16 | %w(foo bar baz) 17 | end 18 | end 19 | 20 | class Command2 < Cli::Command 21 | class Options 22 | arg_array "name" do |definition| 23 | definition.on_validate do |context, options| 24 | context.validate_element_inclusion options.command.expected2 25 | end 26 | end 27 | end 28 | 29 | def expected2 30 | %w(foo bar baz) 31 | end 32 | end 33 | 34 | it name do 35 | Command.run(%w(other)).should exit_command(error: /^Parsing Error: /) 36 | Command2.run(%w(other)).should exit_command(error: /^Parsing Error: /) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/internal/explicit_exit_calls_standard_exit/run.cr: -------------------------------------------------------------------------------- 1 | require "../../../src/cli" 2 | 3 | class Command < Cli::Command 4 | def run 5 | exit! code: 99 6 | end 7 | end 8 | 9 | at_exit {|code| puts ":) #{code}"; LibC.exit 0 } 10 | 11 | Command.run 12 | -------------------------------------------------------------------------------- /spec/internal/explicit_exit_calls_standard_exit_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalExplicitExitCallsStandardExitFeature 4 | it name do 5 | {{ run("#{__DIR__}/explicit_exit_calls_standard_exit/run").stringify }}.should eq ":) 99\n" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/internal/help_handler_dsl_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalHelpHandlerDslFeature 4 | include Cli::Spec::Helper 5 | 6 | class Default < Cli::Command 7 | class Options 8 | help 9 | end 10 | end 11 | 12 | class Specific < Cli::Command 13 | class Options 14 | help "--show-help", desc: "help!" 15 | end 16 | end 17 | 18 | macro test(example, klass, names, desc) 19 | it {{example}} do 20 | handler = {{klass.id}}::Options.__klass.definitions.handlers[{{names[0]}}] 21 | handler.names.should eq {{names}} 22 | {% for e, i in names %} 23 | {{klass.id}}.run([{{e}}]).should exit_command(output: <<-EOS 24 | {{klass.downcase.id}} [OPTIONS] 25 | 26 | Options: 27 | #{ ({{names}}).join(", ") } {{desc.id}} 28 | EOS 29 | ) 30 | {% end %} 31 | end 32 | end 33 | 34 | describe name do 35 | test "default", "Default", %w(-h --help), "show this help" 36 | test "specific", "Specific", %w(--show-help), "help!" 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/internal/inclusion_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalInclusionFeature 4 | include Cli::Spec::Helper 5 | 6 | class ByArray < Cli::Command 7 | class Options 8 | arg "arg", desc: "description", any_of: %w(a b c) 9 | help 10 | end 11 | end 12 | 13 | class ByTuple < Cli::Command 14 | class Options 15 | arg "arg", desc: "description", any_of: { 16 | {"a", {desc: "foo"}}, 17 | {"b", "bar"}, 18 | {"c"} 19 | } 20 | help 21 | end 22 | end 23 | 24 | describe name do 25 | it "error" do 26 | ByArray.run(%w(d)).should exit_command(error: /^Parsing Error: /) 27 | ByTuple.run(%w(d)).should exit_command(error: /^Parsing Error: /) 28 | end 29 | 30 | it "help" do 31 | ByArray.run(%w(-h)).should exit_command(output: <<-EOS 32 | by-array [OPTIONS] ARG 33 | 34 | Arguments: 35 | ARG description 36 | one of: 37 | a 38 | b 39 | c 40 | 41 | Options: 42 | -h, --help show this help 43 | EOS 44 | ) 45 | 46 | ByTuple.run(%w(-h)).should exit_command(output: <<-EOS 47 | by-tuple [OPTIONS] ARG 48 | 49 | Arguments: 50 | ARG description 51 | one of: 52 | a foo 53 | b bar 54 | c 55 | 56 | Options: 57 | -h, --help show this help 58 | EOS 59 | ) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/internal/recursive_run_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalRecursiveRunFeature 4 | include Cli::Spec::Helper 5 | 6 | class Command < Cli::Command 7 | class Options 8 | arg "arg", required: true 9 | end 10 | 11 | def run 12 | Command.run self 13 | end 14 | end 15 | 16 | describe name do 17 | it "returns internal error" do 18 | Command.run(%w(arg)).should exit_command(error: /^Parsing Error: /) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/internal/reflect_command_name_in_option_model_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalReflectCommandNameInOptionModelFeature 4 | class Default < Cli::Command 5 | end 6 | 7 | class Specific < Cli::Command 8 | command_name "specific_name" 9 | end 10 | 11 | it name do 12 | Default.__klass.options.name.should eq "default" 13 | Specific.__klass.options.name.should eq "specific_name" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/internal/replacing_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalSpecs::Replacing 4 | include Cli::Spec::Helper 5 | 6 | class New < Cli::Command 7 | class Options 8 | string "-s" 9 | help 10 | end 11 | 12 | def run 13 | puts options.s 14 | end 15 | end 16 | 17 | class Obsolete < Cli::Command 18 | replacer_command New 19 | end 20 | 21 | describe name do 22 | it "works" do 23 | Obsolete.run %w(-s new!) do |cmd| 24 | cmd.out.gets_to_end.chomp.should eq "new!" 25 | end 26 | end 27 | 28 | it "help" do 29 | Obsolete.run(%w(-h)).should exit_command(output: <<-EOS 30 | new [OPTIONS] 31 | 32 | Options: 33 | -s 34 | -h, --help show this help 35 | EOS 36 | ) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/internal/rescue_parsing_error_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalRescueParsingErrorFeature 4 | include Cli::Spec::Helper 5 | 6 | class RequiredArgument < Cli::Command 7 | class Options 8 | arg "arg", required: true 9 | end 10 | end 11 | 12 | class RequiredOption < Cli::Command 13 | class Options 14 | string "-s", required: true 15 | end 16 | end 17 | 18 | class MinimumLength < Cli::Command 19 | class Options 20 | array "-a", min: 1 21 | end 22 | end 23 | 24 | class UnknownOption < Cli::Command 25 | end 26 | 27 | class MissingValue < Cli::Command 28 | class Options 29 | string "-s" 30 | end 31 | end 32 | 33 | class UnsupportedConcatenation < Cli::Command 34 | class Options 35 | string "-s" 36 | bool "-b" 37 | end 38 | end 39 | 40 | macro test(command, args = %w()) 41 | it {{command}}.to_s do 42 | {{command}}.run({{args}}).should exit_command(error: /^Parsing Error: /, code: 1) 43 | end 44 | end 45 | 46 | test RequiredArgument 47 | test RequiredOption 48 | test MinimumLength 49 | test UnknownOption, %w(-s) 50 | test MissingValue, %w(-s) 51 | test UnsupportedConcatenation, %w(-sb) 52 | end 53 | -------------------------------------------------------------------------------- /spec/internal/shell_completion/class.cr: -------------------------------------------------------------------------------- 1 | module CliInternalSpecs::ShellCompletion 2 | class Command < Cli::Supercommand 3 | class Subcommand1 < Cli::Command 4 | class Options 5 | string "-s" 6 | end 7 | end 8 | 9 | class Subcommand2 < Cli::Command 10 | class Options 11 | string "-s" 12 | end 13 | end 14 | 15 | class Subcommand3 < Cli::Command 16 | replacer_command Subcommand2 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/internal/shell_completion/fixtures/command.bash: -------------------------------------------------------------------------------- 1 | _command___keys=(' subcommand ') 2 | _command___args=(0) 3 | _command___acts=('') 4 | _command___cmds=('') 5 | _command___lens=(1) 6 | _command___nexts=(' subcommand1 subcommand2 ') 7 | _command___occurs=(1) 8 | _command___tags=(' arg stop ') 9 | _command___words=(' subcommand1 subcommand2 ') 10 | 11 | function _command___act() { 12 | _command___cur 13 | COMPREPLY=( $(compgen -A $1 -- "${_command___c}") ) 14 | return 0 15 | } 16 | 17 | function _command___add() { 18 | _command___cur 19 | COMPREPLY=( $(compgen -W "$(echo ${@:1})" -- "${_command___c}") ) 20 | return 0 21 | } 22 | 23 | function _command___any() { 24 | _command___act file 25 | return $? 26 | } 27 | 28 | function _command___cur() { 29 | _command___c="${COMP_WORDS[COMP_CWORD]}" 30 | return 0 31 | } 32 | 33 | function _command___end() { 34 | if [ $_command___i -lt $COMP_CWORD ]; then 35 | return 1 36 | fi 37 | return 0 38 | } 39 | 40 | function _command___found() { 41 | if _command___keyerr; then return 1; fi 42 | local n 43 | n=${_command___f[$_command___k]} 44 | if [[ "$n" == "" ]]; then 45 | n=1 46 | else 47 | let n+=1 48 | fi 49 | _command___f[$_command___k]=$n 50 | return 0 51 | } 52 | 53 | function _command___inc() { 54 | let _command___i+=1 55 | return 0 56 | } 57 | 58 | function _command___keyerr() { 59 | if [[ "$_command___k" == "" ]] || [ $_command___k -lt 0 ]; then 60 | return 0 61 | fi 62 | return 1 63 | } 64 | 65 | function _command___word() { 66 | _command___w="${COMP_WORDS[$_command___i]}" 67 | return 0 68 | } 69 | 70 | function _command() { 71 | _command___i=1 72 | _command___k=-1 73 | _command___ai=0 74 | _command___f=() 75 | while ! _command___tag stop; do 76 | _command___word 77 | _command___key 78 | if _command___tag term; then 79 | _command___inc 80 | break 81 | fi 82 | if _command___end; then 83 | _command___ls 84 | return $? 85 | fi 86 | if [[ $_command___w =~ ^- ]]; then 87 | if [ $_command___k -eq -1 ]; then 88 | _command___any 89 | return $? 90 | fi 91 | _command___found 92 | _command___inc 93 | _command___len 94 | if [ $_command___l -eq 1 ]; then 95 | continue 96 | fi 97 | if _command___end; then 98 | _command___lskey 99 | return $? 100 | fi 101 | _command___inc 102 | else 103 | if _command___arg; then 104 | if _command___end; then 105 | _command___lskey 106 | return $? 107 | fi 108 | fi 109 | _command___inc 110 | fi 111 | done 112 | if [[ "${_command___nexts[$_command___k]}" != "" ]]; then 113 | _command___next 114 | else 115 | _command___any 116 | fi 117 | return $? 118 | } 119 | 120 | function _command___arg() { 121 | if [ $_command___ai -lt ${#_command___args[@]} ]; then 122 | _command___k=${_command___args[$_command___ai]} 123 | if ! _command___tag varg; then 124 | let _command___ai+=1 125 | fi 126 | return 0 127 | fi 128 | return 1 129 | } 130 | 131 | function _command___key() { 132 | local i 133 | i=0 134 | while [ $i -lt ${#_command___keys[@]} ]; do 135 | if [[ ${_command___keys[$i]} == *' '$_command___w' '* ]]; then 136 | _command___k=$i 137 | return 0 138 | fi 139 | let i+=1 140 | done 141 | _command___k=-1 142 | return 1 143 | } 144 | 145 | function _command___len() { 146 | if _command___keyerr; then return 1; fi 147 | _command___l=${_command___lens[$_command___k]} 148 | return 0 149 | } 150 | 151 | function _command___ls() { 152 | local a i max found arg act cmd 153 | a=() 154 | if [[ "$_command___w" =~ ^- ]]; then 155 | i=0 156 | while [ $i -lt ${#_command___keys[@]} ]; do 157 | if _command___tag arg $i; then 158 | let i+=1 159 | continue 160 | fi 161 | found=${_command___f[$i]} 162 | if [[ "$found" == "" ]]; then 163 | found=0 164 | fi 165 | max=${_command___occurs[$i]} 166 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 167 | a+=($(echo "${_command___keys[$i]}")) 168 | fi 169 | let i+=1 170 | done 171 | else 172 | if [ $_command___ai -lt ${#_command___args[@]} ]; then 173 | arg=${_command___args[$_command___ai]} 174 | act=${_command___acts[$arg]} 175 | cmd=${_command___cmds[$arg]} 176 | if [[ "$act" != "" ]]; then 177 | _command___act $act 178 | return 0 179 | elif [[ "$cmd" != "" ]]; then 180 | a=($(eval $cmd)) 181 | else 182 | a=($(echo "${_command___words[$arg]}")) 183 | fi 184 | fi 185 | fi 186 | if [ ${#a[@]} -gt 0 ]; then 187 | _command___add "${a[@]}" 188 | return 0 189 | fi 190 | _command___any 191 | return $? 192 | } 193 | 194 | function _command___lskey() { 195 | if ! _command___keyerr; then 196 | local act cmd a 197 | act=${_command___acts[$_command___k]} 198 | cmd=${_command___cmds[$_command___k]} 199 | if [[ "$act" != "" ]]; then 200 | : 201 | elif [[ "$cmd" != "" ]]; then 202 | a=($(eval $cmd)) 203 | else 204 | a=($(echo "${_command___words[$_command___k]}")) 205 | fi 206 | if [[ "$act" != "" ]]; then 207 | _command___act $act 208 | return 0 209 | elif [ ${#a[@]} -gt 0 ]; then 210 | _command___add "${a[@]}" 211 | return 0 212 | fi 213 | fi 214 | _command___any 215 | return $? 216 | } 217 | 218 | function _command___tag() { 219 | local k 220 | if [[ "$2" == "" ]]; then 221 | if _command___keyerr; then return 1; fi 222 | k=$_command___k 223 | else 224 | k=$2 225 | fi 226 | if [[ ${_command___tags[$k]} == *' '$1' '* ]]; then 227 | return 0 228 | fi 229 | return 1 230 | } 231 | 232 | function _command___next() { 233 | case $_command___w in 234 | 'subcommand1') 235 | _command__subcommand1 236 | ;; 237 | 'subcommand2') 238 | _command__subcommand2 239 | ;; 240 | *) 241 | _command___any 242 | ;; 243 | esac 244 | return $? 245 | } 246 | 247 | _command__subcommand1___keys=(' -s ') 248 | _command__subcommand1___args=() 249 | _command__subcommand1___acts=('') 250 | _command__subcommand1___cmds=('') 251 | _command__subcommand1___lens=(2) 252 | _command__subcommand1___nexts=('') 253 | _command__subcommand1___occurs=(1) 254 | _command__subcommand1___tags=(' opt ') 255 | _command__subcommand1___words=('') 256 | 257 | function _command__subcommand1() { 258 | _command___k=-1 259 | _command___ai=0 260 | _command___f=() 261 | while ! _command__subcommand1___tag stop; do 262 | _command___word 263 | _command__subcommand1___key 264 | if _command__subcommand1___tag term; then 265 | _command___inc 266 | break 267 | fi 268 | if _command___end; then 269 | _command__subcommand1___ls 270 | return $? 271 | fi 272 | if [[ $_command___w =~ ^- ]]; then 273 | if [ $_command___k -eq -1 ]; then 274 | _command___any 275 | return $? 276 | fi 277 | _command___found 278 | _command___inc 279 | _command__subcommand1___len 280 | if [ $_command___l -eq 1 ]; then 281 | continue 282 | fi 283 | if _command___end; then 284 | _command__subcommand1___lskey 285 | return $? 286 | fi 287 | _command___inc 288 | else 289 | if _command__subcommand1___arg; then 290 | if _command___end; then 291 | _command__subcommand1___lskey 292 | return $? 293 | fi 294 | fi 295 | _command___inc 296 | fi 297 | done 298 | if [[ "${_command__subcommand1___nexts[$_command___k]}" != "" ]]; then 299 | _command__subcommand1___next 300 | else 301 | _command___any 302 | fi 303 | return $? 304 | } 305 | 306 | function _command__subcommand1___arg() { 307 | if [ $_command___ai -lt ${#_command__subcommand1___args[@]} ]; then 308 | _command___k=${_command__subcommand1___args[$_command___ai]} 309 | if ! _command__subcommand1___tag varg; then 310 | let _command___ai+=1 311 | fi 312 | return 0 313 | fi 314 | return 1 315 | } 316 | 317 | function _command__subcommand1___key() { 318 | local i 319 | i=0 320 | while [ $i -lt ${#_command__subcommand1___keys[@]} ]; do 321 | if [[ ${_command__subcommand1___keys[$i]} == *' '$_command___w' '* ]]; then 322 | _command___k=$i 323 | return 0 324 | fi 325 | let i+=1 326 | done 327 | _command___k=-1 328 | return 1 329 | } 330 | 331 | function _command__subcommand1___len() { 332 | if _command___keyerr; then return 1; fi 333 | _command___l=${_command__subcommand1___lens[$_command___k]} 334 | return 0 335 | } 336 | 337 | function _command__subcommand1___ls() { 338 | local a i max found arg act cmd 339 | a=() 340 | if [[ "$_command___w" =~ ^- ]]; then 341 | i=0 342 | while [ $i -lt ${#_command__subcommand1___keys[@]} ]; do 343 | if _command__subcommand1___tag arg $i; then 344 | let i+=1 345 | continue 346 | fi 347 | found=${_command___f[$i]} 348 | if [[ "$found" == "" ]]; then 349 | found=0 350 | fi 351 | max=${_command__subcommand1___occurs[$i]} 352 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 353 | a+=($(echo "${_command__subcommand1___keys[$i]}")) 354 | fi 355 | let i+=1 356 | done 357 | else 358 | if [ $_command___ai -lt ${#_command__subcommand1___args[@]} ]; then 359 | arg=${_command__subcommand1___args[$_command___ai]} 360 | act=${_command__subcommand1___acts[$arg]} 361 | cmd=${_command__subcommand1___cmds[$arg]} 362 | if [[ "$act" != "" ]]; then 363 | _command___act $act 364 | return 0 365 | elif [[ "$cmd" != "" ]]; then 366 | a=($(eval $cmd)) 367 | else 368 | a=($(echo "${_command__subcommand1___words[$arg]}")) 369 | fi 370 | fi 371 | fi 372 | if [ ${#a[@]} -gt 0 ]; then 373 | _command___add "${a[@]}" 374 | return 0 375 | fi 376 | _command___any 377 | return $? 378 | } 379 | 380 | function _command__subcommand1___lskey() { 381 | if ! _command___keyerr; then 382 | local act cmd a 383 | act=${_command__subcommand1___acts[$_command___k]} 384 | cmd=${_command__subcommand1___cmds[$_command___k]} 385 | if [[ "$act" != "" ]]; then 386 | : 387 | elif [[ "$cmd" != "" ]]; then 388 | a=($(eval $cmd)) 389 | else 390 | a=($(echo "${_command__subcommand1___words[$_command___k]}")) 391 | fi 392 | if [[ "$act" != "" ]]; then 393 | _command___act $act 394 | return 0 395 | elif [ ${#a[@]} -gt 0 ]; then 396 | _command___add "${a[@]}" 397 | return 0 398 | fi 399 | fi 400 | _command___any 401 | return $? 402 | } 403 | 404 | function _command__subcommand1___tag() { 405 | local k 406 | if [[ "$2" == "" ]]; then 407 | if _command___keyerr; then return 1; fi 408 | k=$_command___k 409 | else 410 | k=$2 411 | fi 412 | if [[ ${_command__subcommand1___tags[$k]} == *' '$1' '* ]]; then 413 | return 0 414 | fi 415 | return 1 416 | } 417 | 418 | _command__subcommand2___keys=(' -s ') 419 | _command__subcommand2___args=() 420 | _command__subcommand2___acts=('') 421 | _command__subcommand2___cmds=('') 422 | _command__subcommand2___lens=(2) 423 | _command__subcommand2___nexts=('') 424 | _command__subcommand2___occurs=(1) 425 | _command__subcommand2___tags=(' opt ') 426 | _command__subcommand2___words=('') 427 | 428 | function _command__subcommand2() { 429 | _command___k=-1 430 | _command___ai=0 431 | _command___f=() 432 | while ! _command__subcommand2___tag stop; do 433 | _command___word 434 | _command__subcommand2___key 435 | if _command__subcommand2___tag term; then 436 | _command___inc 437 | break 438 | fi 439 | if _command___end; then 440 | _command__subcommand2___ls 441 | return $? 442 | fi 443 | if [[ $_command___w =~ ^- ]]; then 444 | if [ $_command___k -eq -1 ]; then 445 | _command___any 446 | return $? 447 | fi 448 | _command___found 449 | _command___inc 450 | _command__subcommand2___len 451 | if [ $_command___l -eq 1 ]; then 452 | continue 453 | fi 454 | if _command___end; then 455 | _command__subcommand2___lskey 456 | return $? 457 | fi 458 | _command___inc 459 | else 460 | if _command__subcommand2___arg; then 461 | if _command___end; then 462 | _command__subcommand2___lskey 463 | return $? 464 | fi 465 | fi 466 | _command___inc 467 | fi 468 | done 469 | if [[ "${_command__subcommand2___nexts[$_command___k]}" != "" ]]; then 470 | _command__subcommand2___next 471 | else 472 | _command___any 473 | fi 474 | return $? 475 | } 476 | 477 | function _command__subcommand2___arg() { 478 | if [ $_command___ai -lt ${#_command__subcommand2___args[@]} ]; then 479 | _command___k=${_command__subcommand2___args[$_command___ai]} 480 | if ! _command__subcommand2___tag varg; then 481 | let _command___ai+=1 482 | fi 483 | return 0 484 | fi 485 | return 1 486 | } 487 | 488 | function _command__subcommand2___key() { 489 | local i 490 | i=0 491 | while [ $i -lt ${#_command__subcommand2___keys[@]} ]; do 492 | if [[ ${_command__subcommand2___keys[$i]} == *' '$_command___w' '* ]]; then 493 | _command___k=$i 494 | return 0 495 | fi 496 | let i+=1 497 | done 498 | _command___k=-1 499 | return 1 500 | } 501 | 502 | function _command__subcommand2___len() { 503 | if _command___keyerr; then return 1; fi 504 | _command___l=${_command__subcommand2___lens[$_command___k]} 505 | return 0 506 | } 507 | 508 | function _command__subcommand2___ls() { 509 | local a i max found arg act cmd 510 | a=() 511 | if [[ "$_command___w" =~ ^- ]]; then 512 | i=0 513 | while [ $i -lt ${#_command__subcommand2___keys[@]} ]; do 514 | if _command__subcommand2___tag arg $i; then 515 | let i+=1 516 | continue 517 | fi 518 | found=${_command___f[$i]} 519 | if [[ "$found" == "" ]]; then 520 | found=0 521 | fi 522 | max=${_command__subcommand2___occurs[$i]} 523 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 524 | a+=($(echo "${_command__subcommand2___keys[$i]}")) 525 | fi 526 | let i+=1 527 | done 528 | else 529 | if [ $_command___ai -lt ${#_command__subcommand2___args[@]} ]; then 530 | arg=${_command__subcommand2___args[$_command___ai]} 531 | act=${_command__subcommand2___acts[$arg]} 532 | cmd=${_command__subcommand2___cmds[$arg]} 533 | if [[ "$act" != "" ]]; then 534 | _command___act $act 535 | return 0 536 | elif [[ "$cmd" != "" ]]; then 537 | a=($(eval $cmd)) 538 | else 539 | a=($(echo "${_command__subcommand2___words[$arg]}")) 540 | fi 541 | fi 542 | fi 543 | if [ ${#a[@]} -gt 0 ]; then 544 | _command___add "${a[@]}" 545 | return 0 546 | fi 547 | _command___any 548 | return $? 549 | } 550 | 551 | function _command__subcommand2___lskey() { 552 | if ! _command___keyerr; then 553 | local act cmd a 554 | act=${_command__subcommand2___acts[$_command___k]} 555 | cmd=${_command__subcommand2___cmds[$_command___k]} 556 | if [[ "$act" != "" ]]; then 557 | : 558 | elif [[ "$cmd" != "" ]]; then 559 | a=($(eval $cmd)) 560 | else 561 | a=($(echo "${_command__subcommand2___words[$_command___k]}")) 562 | fi 563 | if [[ "$act" != "" ]]; then 564 | _command___act $act 565 | return 0 566 | elif [ ${#a[@]} -gt 0 ]; then 567 | _command___add "${a[@]}" 568 | return 0 569 | fi 570 | fi 571 | _command___any 572 | return $? 573 | } 574 | 575 | function _command__subcommand2___tag() { 576 | local k 577 | if [[ "$2" == "" ]]; then 578 | if _command___keyerr; then return 1; fi 579 | k=$_command___k 580 | else 581 | k=$2 582 | fi 583 | if [[ ${_command__subcommand2___tags[$k]} == *' '$1' '* ]]; then 584 | return 0 585 | fi 586 | return 1 587 | } 588 | 589 | complete -F _command command 590 | -------------------------------------------------------------------------------- /spec/internal/shell_completion/fixtures/command.zsh: -------------------------------------------------------------------------------- 1 | _command___keys=(' subcommand ') 2 | _command___args=(0) 3 | _command___acts=('') 4 | _command___cmds=('') 5 | _command___lens=(1) 6 | _command___nexts=(' subcommand1 subcommand2 ') 7 | _command___occurs=(1) 8 | _command___tags=(' arg stop ') 9 | _command___words=(' subcommand1 subcommand2 ') 10 | 11 | function _command___act() { 12 | setopt localoptions ksharrays 13 | 14 | local -a a jids 15 | case $1 in 16 | alias) 17 | a=( "${(k)aliases[@]}" ) ;; 18 | arrayvar) 19 | a=( "${(k@)parameters[(R)array*]}" ) 20 | ;; 21 | binding) 22 | a=( "${(k)widgets[@]}" ) 23 | ;; 24 | builtin) 25 | a=( "${(k)builtins[@]}" "${(k)dis_builtins[@]}" ) 26 | ;; 27 | command) 28 | a=( "${(k)commands[@]}" "${(k)aliases[@]}" "${(k)builtins[@]}" "${(k)functions[@]}" "${(k)reswords[@]}") 29 | ;; 30 | directory) 31 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N-/) ) 32 | ;; 33 | disabled) 34 | a=( "${(k)dis_builtins[@]}" ) 35 | ;; 36 | enabled) 37 | a=( "${(k)builtins[@]}" ) 38 | ;; 39 | export) 40 | a=( "${(k)parameters[(R)*export*]}" ) 41 | ;; 42 | file) 43 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 44 | ;; 45 | function) 46 | a=( "${(k)functions[@]}" ) 47 | ;; 48 | group) 49 | _groups -U -O a 50 | ;; 51 | hostname) 52 | _hosts -U -O a 53 | ;; 54 | job) 55 | a=( "${savejobtexts[@]%% *}" ) 56 | ;; 57 | keyword) 58 | a=( "${(k)reswords[@]}" ) 59 | ;; 60 | running) 61 | a=() 62 | jids=( "${(@k)savejobstates[(R)running*]}" ) 63 | for job in "${jids[@]}"; do 64 | a+=( ${savejobtexts[$job]%% *} ) 65 | done 66 | ;; 67 | stopped) 68 | a=() 69 | jids=( "${(@k)savejobstates[(R)suspended*]}" ) 70 | for job in "${jids[@]}"; do 71 | a+=( ${savejobtexts[$job]%% *} ) 72 | done 73 | ;; 74 | setopt|shopt) 75 | a=( "${(k)options[@]}" ) 76 | ;; 77 | signal) 78 | a=( "SIG${^signals[@]}" ) 79 | ;; 80 | user) 81 | a=( "${(k)userdirs[@]}" ) 82 | ;; 83 | variable) 84 | a=( "${(k)parameters[@]}" ) 85 | ;; 86 | *) 87 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 88 | ;; 89 | esac 90 | compadd -- "${a[@]}" 91 | return 0 92 | } 93 | 94 | function _command___add() { 95 | setopt localoptions ksharrays 96 | 97 | compadd -- "${@:1}" 98 | return 0 99 | } 100 | 101 | function _command___any() { 102 | setopt localoptions ksharrays 103 | 104 | _command___act file 105 | return $? 106 | } 107 | 108 | function _command___cur() { 109 | setopt localoptions ksharrays 110 | 111 | _command___c="${COMP_WORDS[COMP_CWORD]}" 112 | return 0 113 | } 114 | 115 | function _command___end() { 116 | setopt localoptions ksharrays 117 | 118 | if [ $_command___i -lt $COMP_CWORD ]; then 119 | return 1 120 | fi 121 | return 0 122 | } 123 | 124 | function _command___found() { 125 | setopt localoptions ksharrays 126 | 127 | if _command___keyerr; then return 1; fi 128 | local n 129 | n=${_command___f[$_command___k]} 130 | if [[ "$n" == "" ]]; then 131 | n=1 132 | else 133 | let n+=1 134 | fi 135 | _command___f[$_command___k]=$n 136 | return 0 137 | } 138 | 139 | function _command___inc() { 140 | setopt localoptions ksharrays 141 | 142 | let _command___i+=1 143 | return 0 144 | } 145 | 146 | function _command___keyerr() { 147 | setopt localoptions ksharrays 148 | 149 | if [[ "$_command___k" == "" ]] || [ $_command___k -lt 0 ]; then 150 | return 0 151 | fi 152 | return 1 153 | } 154 | 155 | function _command___word() { 156 | setopt localoptions ksharrays 157 | 158 | _command___w="${COMP_WORDS[$_command___i]}" 159 | return 0 160 | } 161 | 162 | function _command() { 163 | setopt localoptions ksharrays 164 | 165 | (( COMP_CWORD = CURRENT - 1 )) 166 | COMP_WORDS=($(echo ${words[@]})) 167 | _command___i=1 168 | _command___k=-1 169 | _command___ai=0 170 | _command___f=() 171 | while ! _command___tag stop; do 172 | _command___word 173 | _command___key 174 | if _command___tag term; then 175 | _command___inc 176 | break 177 | fi 178 | if _command___end; then 179 | _command___ls 180 | return $? 181 | fi 182 | if [[ $_command___w =~ ^- ]]; then 183 | if [ $_command___k -eq -1 ]; then 184 | _command___any 185 | return $? 186 | fi 187 | _command___found 188 | _command___inc 189 | _command___len 190 | if [ $_command___l -eq 1 ]; then 191 | continue 192 | fi 193 | if _command___end; then 194 | _command___lskey 195 | return $? 196 | fi 197 | _command___inc 198 | else 199 | if _command___arg; then 200 | if _command___end; then 201 | _command___lskey 202 | return $? 203 | fi 204 | fi 205 | _command___inc 206 | fi 207 | done 208 | if [[ "${_command___nexts[$_command___k]}" != "" ]]; then 209 | _command___next 210 | else 211 | _command___any 212 | fi 213 | return $? 214 | } 215 | 216 | function _command___arg() { 217 | setopt localoptions ksharrays 218 | 219 | if [ $_command___ai -lt ${#_command___args[@]} ]; then 220 | _command___k=${_command___args[$_command___ai]} 221 | if ! _command___tag varg; then 222 | let _command___ai+=1 223 | fi 224 | return 0 225 | fi 226 | return 1 227 | } 228 | 229 | function _command___key() { 230 | setopt localoptions ksharrays 231 | 232 | local i 233 | i=0 234 | while [ $i -lt ${#_command___keys[@]} ]; do 235 | if [[ ${_command___keys[$i]} == *' '$_command___w' '* ]]; then 236 | _command___k=$i 237 | return 0 238 | fi 239 | let i+=1 240 | done 241 | _command___k=-1 242 | return 1 243 | } 244 | 245 | function _command___len() { 246 | setopt localoptions ksharrays 247 | 248 | if _command___keyerr; then return 1; fi 249 | _command___l=${_command___lens[$_command___k]} 250 | return 0 251 | } 252 | 253 | function _command___ls() { 254 | setopt localoptions ksharrays 255 | 256 | local a i max found arg act cmd 257 | a=() 258 | if [[ "$_command___w" =~ ^- ]]; then 259 | i=0 260 | while [ $i -lt ${#_command___keys[@]} ]; do 261 | if _command___tag arg $i; then 262 | let i+=1 263 | continue 264 | fi 265 | found=${_command___f[$i]} 266 | if [[ "$found" == "" ]]; then 267 | found=0 268 | fi 269 | max=${_command___occurs[$i]} 270 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 271 | a+=($(echo "${_command___keys[$i]}")) 272 | fi 273 | let i+=1 274 | done 275 | else 276 | if [ $_command___ai -lt ${#_command___args[@]} ]; then 277 | arg=${_command___args[$_command___ai]} 278 | act=${_command___acts[$arg]} 279 | cmd=${_command___cmds[$arg]} 280 | if [[ "$act" != "" ]]; then 281 | _command___act $act 282 | return 0 283 | elif [[ "$cmd" != "" ]]; then 284 | a=($(eval $cmd)) 285 | else 286 | a=($(echo "${_command___words[$arg]}")) 287 | fi 288 | fi 289 | fi 290 | if [ ${#a[@]} -gt 0 ]; then 291 | _command___add "${a[@]}" 292 | return 0 293 | fi 294 | _command___any 295 | return $? 296 | } 297 | 298 | function _command___lskey() { 299 | setopt localoptions ksharrays 300 | 301 | if ! _command___keyerr; then 302 | local act cmd a 303 | act=${_command___acts[$_command___k]} 304 | cmd=${_command___cmds[$_command___k]} 305 | if [[ "$act" != "" ]]; then 306 | : 307 | elif [[ "$cmd" != "" ]]; then 308 | a=($(eval $cmd)) 309 | else 310 | a=($(echo "${_command___words[$_command___k]}")) 311 | fi 312 | if [[ "$act" != "" ]]; then 313 | _command___act $act 314 | return 0 315 | elif [ ${#a[@]} -gt 0 ]; then 316 | _command___add "${a[@]}" 317 | return 0 318 | fi 319 | fi 320 | _command___any 321 | return $? 322 | } 323 | 324 | function _command___tag() { 325 | setopt localoptions ksharrays 326 | 327 | local k 328 | if [[ "$2" == "" ]]; then 329 | if _command___keyerr; then return 1; fi 330 | k=$_command___k 331 | else 332 | k=$2 333 | fi 334 | if [[ ${_command___tags[$k]} == *' '$1' '* ]]; then 335 | return 0 336 | fi 337 | return 1 338 | } 339 | 340 | function _command___next() { 341 | setopt localoptions ksharrays 342 | 343 | case $_command___w in 344 | 'subcommand1') 345 | _command__subcommand1 346 | ;; 347 | 'subcommand2') 348 | _command__subcommand2 349 | ;; 350 | *) 351 | _command___any 352 | ;; 353 | esac 354 | return $? 355 | } 356 | 357 | _command__subcommand1___keys=(' -s ') 358 | _command__subcommand1___args=() 359 | _command__subcommand1___acts=('') 360 | _command__subcommand1___cmds=('') 361 | _command__subcommand1___lens=(2) 362 | _command__subcommand1___nexts=('') 363 | _command__subcommand1___occurs=(1) 364 | _command__subcommand1___tags=(' opt ') 365 | _command__subcommand1___words=('') 366 | 367 | function _command__subcommand1() { 368 | setopt localoptions ksharrays 369 | 370 | _command___k=-1 371 | _command___ai=0 372 | _command___f=() 373 | while ! _command__subcommand1___tag stop; do 374 | _command___word 375 | _command__subcommand1___key 376 | if _command__subcommand1___tag term; then 377 | _command___inc 378 | break 379 | fi 380 | if _command___end; then 381 | _command__subcommand1___ls 382 | return $? 383 | fi 384 | if [[ $_command___w =~ ^- ]]; then 385 | if [ $_command___k -eq -1 ]; then 386 | _command___any 387 | return $? 388 | fi 389 | _command___found 390 | _command___inc 391 | _command__subcommand1___len 392 | if [ $_command___l -eq 1 ]; then 393 | continue 394 | fi 395 | if _command___end; then 396 | _command__subcommand1___lskey 397 | return $? 398 | fi 399 | _command___inc 400 | else 401 | if _command__subcommand1___arg; then 402 | if _command___end; then 403 | _command__subcommand1___lskey 404 | return $? 405 | fi 406 | fi 407 | _command___inc 408 | fi 409 | done 410 | if [[ "${_command__subcommand1___nexts[$_command___k]}" != "" ]]; then 411 | _command__subcommand1___next 412 | else 413 | _command___any 414 | fi 415 | return $? 416 | } 417 | 418 | function _command__subcommand1___arg() { 419 | setopt localoptions ksharrays 420 | 421 | if [ $_command___ai -lt ${#_command__subcommand1___args[@]} ]; then 422 | _command___k=${_command__subcommand1___args[$_command___ai]} 423 | if ! _command__subcommand1___tag varg; then 424 | let _command___ai+=1 425 | fi 426 | return 0 427 | fi 428 | return 1 429 | } 430 | 431 | function _command__subcommand1___key() { 432 | setopt localoptions ksharrays 433 | 434 | local i 435 | i=0 436 | while [ $i -lt ${#_command__subcommand1___keys[@]} ]; do 437 | if [[ ${_command__subcommand1___keys[$i]} == *' '$_command___w' '* ]]; then 438 | _command___k=$i 439 | return 0 440 | fi 441 | let i+=1 442 | done 443 | _command___k=-1 444 | return 1 445 | } 446 | 447 | function _command__subcommand1___len() { 448 | setopt localoptions ksharrays 449 | 450 | if _command___keyerr; then return 1; fi 451 | _command___l=${_command__subcommand1___lens[$_command___k]} 452 | return 0 453 | } 454 | 455 | function _command__subcommand1___ls() { 456 | setopt localoptions ksharrays 457 | 458 | local a i max found arg act cmd 459 | a=() 460 | if [[ "$_command___w" =~ ^- ]]; then 461 | i=0 462 | while [ $i -lt ${#_command__subcommand1___keys[@]} ]; do 463 | if _command__subcommand1___tag arg $i; then 464 | let i+=1 465 | continue 466 | fi 467 | found=${_command___f[$i]} 468 | if [[ "$found" == "" ]]; then 469 | found=0 470 | fi 471 | max=${_command__subcommand1___occurs[$i]} 472 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 473 | a+=($(echo "${_command__subcommand1___keys[$i]}")) 474 | fi 475 | let i+=1 476 | done 477 | else 478 | if [ $_command___ai -lt ${#_command__subcommand1___args[@]} ]; then 479 | arg=${_command__subcommand1___args[$_command___ai]} 480 | act=${_command__subcommand1___acts[$arg]} 481 | cmd=${_command__subcommand1___cmds[$arg]} 482 | if [[ "$act" != "" ]]; then 483 | _command___act $act 484 | return 0 485 | elif [[ "$cmd" != "" ]]; then 486 | a=($(eval $cmd)) 487 | else 488 | a=($(echo "${_command__subcommand1___words[$arg]}")) 489 | fi 490 | fi 491 | fi 492 | if [ ${#a[@]} -gt 0 ]; then 493 | _command___add "${a[@]}" 494 | return 0 495 | fi 496 | _command___any 497 | return $? 498 | } 499 | 500 | function _command__subcommand1___lskey() { 501 | setopt localoptions ksharrays 502 | 503 | if ! _command___keyerr; then 504 | local act cmd a 505 | act=${_command__subcommand1___acts[$_command___k]} 506 | cmd=${_command__subcommand1___cmds[$_command___k]} 507 | if [[ "$act" != "" ]]; then 508 | : 509 | elif [[ "$cmd" != "" ]]; then 510 | a=($(eval $cmd)) 511 | else 512 | a=($(echo "${_command__subcommand1___words[$_command___k]}")) 513 | fi 514 | if [[ "$act" != "" ]]; then 515 | _command___act $act 516 | return 0 517 | elif [ ${#a[@]} -gt 0 ]; then 518 | _command___add "${a[@]}" 519 | return 0 520 | fi 521 | fi 522 | _command___any 523 | return $? 524 | } 525 | 526 | function _command__subcommand1___tag() { 527 | setopt localoptions ksharrays 528 | 529 | local k 530 | if [[ "$2" == "" ]]; then 531 | if _command___keyerr; then return 1; fi 532 | k=$_command___k 533 | else 534 | k=$2 535 | fi 536 | if [[ ${_command__subcommand1___tags[$k]} == *' '$1' '* ]]; then 537 | return 0 538 | fi 539 | return 1 540 | } 541 | 542 | _command__subcommand2___keys=(' -s ') 543 | _command__subcommand2___args=() 544 | _command__subcommand2___acts=('') 545 | _command__subcommand2___cmds=('') 546 | _command__subcommand2___lens=(2) 547 | _command__subcommand2___nexts=('') 548 | _command__subcommand2___occurs=(1) 549 | _command__subcommand2___tags=(' opt ') 550 | _command__subcommand2___words=('') 551 | 552 | function _command__subcommand2() { 553 | setopt localoptions ksharrays 554 | 555 | _command___k=-1 556 | _command___ai=0 557 | _command___f=() 558 | while ! _command__subcommand2___tag stop; do 559 | _command___word 560 | _command__subcommand2___key 561 | if _command__subcommand2___tag term; then 562 | _command___inc 563 | break 564 | fi 565 | if _command___end; then 566 | _command__subcommand2___ls 567 | return $? 568 | fi 569 | if [[ $_command___w =~ ^- ]]; then 570 | if [ $_command___k -eq -1 ]; then 571 | _command___any 572 | return $? 573 | fi 574 | _command___found 575 | _command___inc 576 | _command__subcommand2___len 577 | if [ $_command___l -eq 1 ]; then 578 | continue 579 | fi 580 | if _command___end; then 581 | _command__subcommand2___lskey 582 | return $? 583 | fi 584 | _command___inc 585 | else 586 | if _command__subcommand2___arg; then 587 | if _command___end; then 588 | _command__subcommand2___lskey 589 | return $? 590 | fi 591 | fi 592 | _command___inc 593 | fi 594 | done 595 | if [[ "${_command__subcommand2___nexts[$_command___k]}" != "" ]]; then 596 | _command__subcommand2___next 597 | else 598 | _command___any 599 | fi 600 | return $? 601 | } 602 | 603 | function _command__subcommand2___arg() { 604 | setopt localoptions ksharrays 605 | 606 | if [ $_command___ai -lt ${#_command__subcommand2___args[@]} ]; then 607 | _command___k=${_command__subcommand2___args[$_command___ai]} 608 | if ! _command__subcommand2___tag varg; then 609 | let _command___ai+=1 610 | fi 611 | return 0 612 | fi 613 | return 1 614 | } 615 | 616 | function _command__subcommand2___key() { 617 | setopt localoptions ksharrays 618 | 619 | local i 620 | i=0 621 | while [ $i -lt ${#_command__subcommand2___keys[@]} ]; do 622 | if [[ ${_command__subcommand2___keys[$i]} == *' '$_command___w' '* ]]; then 623 | _command___k=$i 624 | return 0 625 | fi 626 | let i+=1 627 | done 628 | _command___k=-1 629 | return 1 630 | } 631 | 632 | function _command__subcommand2___len() { 633 | setopt localoptions ksharrays 634 | 635 | if _command___keyerr; then return 1; fi 636 | _command___l=${_command__subcommand2___lens[$_command___k]} 637 | return 0 638 | } 639 | 640 | function _command__subcommand2___ls() { 641 | setopt localoptions ksharrays 642 | 643 | local a i max found arg act cmd 644 | a=() 645 | if [[ "$_command___w" =~ ^- ]]; then 646 | i=0 647 | while [ $i -lt ${#_command__subcommand2___keys[@]} ]; do 648 | if _command__subcommand2___tag arg $i; then 649 | let i+=1 650 | continue 651 | fi 652 | found=${_command___f[$i]} 653 | if [[ "$found" == "" ]]; then 654 | found=0 655 | fi 656 | max=${_command__subcommand2___occurs[$i]} 657 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 658 | a+=($(echo "${_command__subcommand2___keys[$i]}")) 659 | fi 660 | let i+=1 661 | done 662 | else 663 | if [ $_command___ai -lt ${#_command__subcommand2___args[@]} ]; then 664 | arg=${_command__subcommand2___args[$_command___ai]} 665 | act=${_command__subcommand2___acts[$arg]} 666 | cmd=${_command__subcommand2___cmds[$arg]} 667 | if [[ "$act" != "" ]]; then 668 | _command___act $act 669 | return 0 670 | elif [[ "$cmd" != "" ]]; then 671 | a=($(eval $cmd)) 672 | else 673 | a=($(echo "${_command__subcommand2___words[$arg]}")) 674 | fi 675 | fi 676 | fi 677 | if [ ${#a[@]} -gt 0 ]; then 678 | _command___add "${a[@]}" 679 | return 0 680 | fi 681 | _command___any 682 | return $? 683 | } 684 | 685 | function _command__subcommand2___lskey() { 686 | setopt localoptions ksharrays 687 | 688 | if ! _command___keyerr; then 689 | local act cmd a 690 | act=${_command__subcommand2___acts[$_command___k]} 691 | cmd=${_command__subcommand2___cmds[$_command___k]} 692 | if [[ "$act" != "" ]]; then 693 | : 694 | elif [[ "$cmd" != "" ]]; then 695 | a=($(eval $cmd)) 696 | else 697 | a=($(echo "${_command__subcommand2___words[$_command___k]}")) 698 | fi 699 | if [[ "$act" != "" ]]; then 700 | _command___act $act 701 | return 0 702 | elif [ ${#a[@]} -gt 0 ]; then 703 | _command___add "${a[@]}" 704 | return 0 705 | fi 706 | fi 707 | _command___any 708 | return $? 709 | } 710 | 711 | function _command__subcommand2___tag() { 712 | setopt localoptions ksharrays 713 | 714 | local k 715 | if [[ "$2" == "" ]]; then 716 | if _command___keyerr; then return 1; fi 717 | k=$_command___k 718 | else 719 | k=$2 720 | fi 721 | if [[ ${_command__subcommand2___tags[$k]} == *' '$1' '* ]]; then 722 | return 0 723 | fi 724 | return 1 725 | } 726 | 727 | compdef _command command 728 | -------------------------------------------------------------------------------- /spec/internal/shell_completion/fixtures/zsh/_command: -------------------------------------------------------------------------------- 1 | #compdef command 2 | 3 | _command___keys=(' subcommand ') 4 | _command___args=(0) 5 | _command___acts=('') 6 | _command___cmds=('') 7 | _command___lens=(1) 8 | _command___nexts=(' subcommand1 subcommand2 ') 9 | _command___occurs=(1) 10 | _command___tags=(' arg stop ') 11 | _command___words=(' subcommand1 subcommand2 ') 12 | 13 | function _command___act() { 14 | setopt localoptions ksharrays 15 | 16 | local -a a jids 17 | case $1 in 18 | alias) 19 | a=( "${(k)aliases[@]}" ) ;; 20 | arrayvar) 21 | a=( "${(k@)parameters[(R)array*]}" ) 22 | ;; 23 | binding) 24 | a=( "${(k)widgets[@]}" ) 25 | ;; 26 | builtin) 27 | a=( "${(k)builtins[@]}" "${(k)dis_builtins[@]}" ) 28 | ;; 29 | command) 30 | a=( "${(k)commands[@]}" "${(k)aliases[@]}" "${(k)builtins[@]}" "${(k)functions[@]}" "${(k)reswords[@]}") 31 | ;; 32 | directory) 33 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N-/) ) 34 | ;; 35 | disabled) 36 | a=( "${(k)dis_builtins[@]}" ) 37 | ;; 38 | enabled) 39 | a=( "${(k)builtins[@]}" ) 40 | ;; 41 | export) 42 | a=( "${(k)parameters[(R)*export*]}" ) 43 | ;; 44 | file) 45 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 46 | ;; 47 | function) 48 | a=( "${(k)functions[@]}" ) 49 | ;; 50 | group) 51 | _groups -U -O a 52 | ;; 53 | hostname) 54 | _hosts -U -O a 55 | ;; 56 | job) 57 | a=( "${savejobtexts[@]%% *}" ) 58 | ;; 59 | keyword) 60 | a=( "${(k)reswords[@]}" ) 61 | ;; 62 | running) 63 | a=() 64 | jids=( "${(@k)savejobstates[(R)running*]}" ) 65 | for job in "${jids[@]}"; do 66 | a+=( ${savejobtexts[$job]%% *} ) 67 | done 68 | ;; 69 | stopped) 70 | a=() 71 | jids=( "${(@k)savejobstates[(R)suspended*]}" ) 72 | for job in "${jids[@]}"; do 73 | a+=( ${savejobtexts[$job]%% *} ) 74 | done 75 | ;; 76 | setopt|shopt) 77 | a=( "${(k)options[@]}" ) 78 | ;; 79 | signal) 80 | a=( "SIG${^signals[@]}" ) 81 | ;; 82 | user) 83 | a=( "${(k)userdirs[@]}" ) 84 | ;; 85 | variable) 86 | a=( "${(k)parameters[@]}" ) 87 | ;; 88 | *) 89 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 90 | ;; 91 | esac 92 | compadd -- "${a[@]}" 93 | return 0 94 | } 95 | 96 | function _command___add() { 97 | setopt localoptions ksharrays 98 | 99 | compadd -- "${@:1}" 100 | return 0 101 | } 102 | 103 | function _command___any() { 104 | setopt localoptions ksharrays 105 | 106 | _command___act file 107 | return $? 108 | } 109 | 110 | function _command___cur() { 111 | setopt localoptions ksharrays 112 | 113 | _command___c="${COMP_WORDS[COMP_CWORD]}" 114 | return 0 115 | } 116 | 117 | function _command___end() { 118 | setopt localoptions ksharrays 119 | 120 | if [ $_command___i -lt $COMP_CWORD ]; then 121 | return 1 122 | fi 123 | return 0 124 | } 125 | 126 | function _command___found() { 127 | setopt localoptions ksharrays 128 | 129 | if _command___keyerr; then return 1; fi 130 | local n 131 | n=${_command___f[$_command___k]} 132 | if [[ "$n" == "" ]]; then 133 | n=1 134 | else 135 | let n+=1 136 | fi 137 | _command___f[$_command___k]=$n 138 | return 0 139 | } 140 | 141 | function _command___inc() { 142 | setopt localoptions ksharrays 143 | 144 | let _command___i+=1 145 | return 0 146 | } 147 | 148 | function _command___keyerr() { 149 | setopt localoptions ksharrays 150 | 151 | if [[ "$_command___k" == "" ]] || [ $_command___k -lt 0 ]; then 152 | return 0 153 | fi 154 | return 1 155 | } 156 | 157 | function _command___word() { 158 | setopt localoptions ksharrays 159 | 160 | _command___w="${COMP_WORDS[$_command___i]}" 161 | return 0 162 | } 163 | 164 | function _command() { 165 | setopt localoptions ksharrays 166 | 167 | (( COMP_CWORD = CURRENT - 1 )) 168 | COMP_WORDS=($(echo ${words[@]})) 169 | _command___i=1 170 | _command___k=-1 171 | _command___ai=0 172 | _command___f=() 173 | while ! _command___tag stop; do 174 | _command___word 175 | _command___key 176 | if _command___tag term; then 177 | _command___inc 178 | break 179 | fi 180 | if _command___end; then 181 | _command___ls 182 | return $? 183 | fi 184 | if [[ $_command___w =~ ^- ]]; then 185 | if [ $_command___k -eq -1 ]; then 186 | _command___any 187 | return $? 188 | fi 189 | _command___found 190 | _command___inc 191 | _command___len 192 | if [ $_command___l -eq 1 ]; then 193 | continue 194 | fi 195 | if _command___end; then 196 | _command___lskey 197 | return $? 198 | fi 199 | _command___inc 200 | else 201 | if _command___arg; then 202 | if _command___end; then 203 | _command___lskey 204 | return $? 205 | fi 206 | fi 207 | _command___inc 208 | fi 209 | done 210 | if [[ "${_command___nexts[$_command___k]}" != "" ]]; then 211 | _command___next 212 | else 213 | _command___any 214 | fi 215 | return $? 216 | } 217 | 218 | function _command___arg() { 219 | setopt localoptions ksharrays 220 | 221 | if [ $_command___ai -lt ${#_command___args[@]} ]; then 222 | _command___k=${_command___args[$_command___ai]} 223 | if ! _command___tag varg; then 224 | let _command___ai+=1 225 | fi 226 | return 0 227 | fi 228 | return 1 229 | } 230 | 231 | function _command___key() { 232 | setopt localoptions ksharrays 233 | 234 | local i 235 | i=0 236 | while [ $i -lt ${#_command___keys[@]} ]; do 237 | if [[ ${_command___keys[$i]} == *' '$_command___w' '* ]]; then 238 | _command___k=$i 239 | return 0 240 | fi 241 | let i+=1 242 | done 243 | _command___k=-1 244 | return 1 245 | } 246 | 247 | function _command___len() { 248 | setopt localoptions ksharrays 249 | 250 | if _command___keyerr; then return 1; fi 251 | _command___l=${_command___lens[$_command___k]} 252 | return 0 253 | } 254 | 255 | function _command___ls() { 256 | setopt localoptions ksharrays 257 | 258 | local a i max found arg act cmd 259 | a=() 260 | if [[ "$_command___w" =~ ^- ]]; then 261 | i=0 262 | while [ $i -lt ${#_command___keys[@]} ]; do 263 | if _command___tag arg $i; then 264 | let i+=1 265 | continue 266 | fi 267 | found=${_command___f[$i]} 268 | if [[ "$found" == "" ]]; then 269 | found=0 270 | fi 271 | max=${_command___occurs[$i]} 272 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 273 | a+=($(echo "${_command___keys[$i]}")) 274 | fi 275 | let i+=1 276 | done 277 | else 278 | if [ $_command___ai -lt ${#_command___args[@]} ]; then 279 | arg=${_command___args[$_command___ai]} 280 | act=${_command___acts[$arg]} 281 | cmd=${_command___cmds[$arg]} 282 | if [[ "$act" != "" ]]; then 283 | _command___act $act 284 | return 0 285 | elif [[ "$cmd" != "" ]]; then 286 | a=($(eval $cmd)) 287 | else 288 | a=($(echo "${_command___words[$arg]}")) 289 | fi 290 | fi 291 | fi 292 | if [ ${#a[@]} -gt 0 ]; then 293 | _command___add "${a[@]}" 294 | return 0 295 | fi 296 | _command___any 297 | return $? 298 | } 299 | 300 | function _command___lskey() { 301 | setopt localoptions ksharrays 302 | 303 | if ! _command___keyerr; then 304 | local act cmd a 305 | act=${_command___acts[$_command___k]} 306 | cmd=${_command___cmds[$_command___k]} 307 | if [[ "$act" != "" ]]; then 308 | : 309 | elif [[ "$cmd" != "" ]]; then 310 | a=($(eval $cmd)) 311 | else 312 | a=($(echo "${_command___words[$_command___k]}")) 313 | fi 314 | if [[ "$act" != "" ]]; then 315 | _command___act $act 316 | return 0 317 | elif [ ${#a[@]} -gt 0 ]; then 318 | _command___add "${a[@]}" 319 | return 0 320 | fi 321 | fi 322 | _command___any 323 | return $? 324 | } 325 | 326 | function _command___tag() { 327 | setopt localoptions ksharrays 328 | 329 | local k 330 | if [[ "$2" == "" ]]; then 331 | if _command___keyerr; then return 1; fi 332 | k=$_command___k 333 | else 334 | k=$2 335 | fi 336 | if [[ ${_command___tags[$k]} == *' '$1' '* ]]; then 337 | return 0 338 | fi 339 | return 1 340 | } 341 | 342 | function _command___next() { 343 | setopt localoptions ksharrays 344 | 345 | case $_command___w in 346 | 'subcommand1') 347 | _command__subcommand1 348 | ;; 349 | 'subcommand2') 350 | _command__subcommand2 351 | ;; 352 | *) 353 | _command___any 354 | ;; 355 | esac 356 | return $? 357 | } 358 | 359 | _command__subcommand1___keys=(' -s ') 360 | _command__subcommand1___args=() 361 | _command__subcommand1___acts=('') 362 | _command__subcommand1___cmds=('') 363 | _command__subcommand1___lens=(2) 364 | _command__subcommand1___nexts=('') 365 | _command__subcommand1___occurs=(1) 366 | _command__subcommand1___tags=(' opt ') 367 | _command__subcommand1___words=('') 368 | 369 | function _command__subcommand1() { 370 | setopt localoptions ksharrays 371 | 372 | _command___k=-1 373 | _command___ai=0 374 | _command___f=() 375 | while ! _command__subcommand1___tag stop; do 376 | _command___word 377 | _command__subcommand1___key 378 | if _command__subcommand1___tag term; then 379 | _command___inc 380 | break 381 | fi 382 | if _command___end; then 383 | _command__subcommand1___ls 384 | return $? 385 | fi 386 | if [[ $_command___w =~ ^- ]]; then 387 | if [ $_command___k -eq -1 ]; then 388 | _command___any 389 | return $? 390 | fi 391 | _command___found 392 | _command___inc 393 | _command__subcommand1___len 394 | if [ $_command___l -eq 1 ]; then 395 | continue 396 | fi 397 | if _command___end; then 398 | _command__subcommand1___lskey 399 | return $? 400 | fi 401 | _command___inc 402 | else 403 | if _command__subcommand1___arg; then 404 | if _command___end; then 405 | _command__subcommand1___lskey 406 | return $? 407 | fi 408 | fi 409 | _command___inc 410 | fi 411 | done 412 | if [[ "${_command__subcommand1___nexts[$_command___k]}" != "" ]]; then 413 | _command__subcommand1___next 414 | else 415 | _command___any 416 | fi 417 | return $? 418 | } 419 | 420 | function _command__subcommand1___arg() { 421 | setopt localoptions ksharrays 422 | 423 | if [ $_command___ai -lt ${#_command__subcommand1___args[@]} ]; then 424 | _command___k=${_command__subcommand1___args[$_command___ai]} 425 | if ! _command__subcommand1___tag varg; then 426 | let _command___ai+=1 427 | fi 428 | return 0 429 | fi 430 | return 1 431 | } 432 | 433 | function _command__subcommand1___key() { 434 | setopt localoptions ksharrays 435 | 436 | local i 437 | i=0 438 | while [ $i -lt ${#_command__subcommand1___keys[@]} ]; do 439 | if [[ ${_command__subcommand1___keys[$i]} == *' '$_command___w' '* ]]; then 440 | _command___k=$i 441 | return 0 442 | fi 443 | let i+=1 444 | done 445 | _command___k=-1 446 | return 1 447 | } 448 | 449 | function _command__subcommand1___len() { 450 | setopt localoptions ksharrays 451 | 452 | if _command___keyerr; then return 1; fi 453 | _command___l=${_command__subcommand1___lens[$_command___k]} 454 | return 0 455 | } 456 | 457 | function _command__subcommand1___ls() { 458 | setopt localoptions ksharrays 459 | 460 | local a i max found arg act cmd 461 | a=() 462 | if [[ "$_command___w" =~ ^- ]]; then 463 | i=0 464 | while [ $i -lt ${#_command__subcommand1___keys[@]} ]; do 465 | if _command__subcommand1___tag arg $i; then 466 | let i+=1 467 | continue 468 | fi 469 | found=${_command___f[$i]} 470 | if [[ "$found" == "" ]]; then 471 | found=0 472 | fi 473 | max=${_command__subcommand1___occurs[$i]} 474 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 475 | a+=($(echo "${_command__subcommand1___keys[$i]}")) 476 | fi 477 | let i+=1 478 | done 479 | else 480 | if [ $_command___ai -lt ${#_command__subcommand1___args[@]} ]; then 481 | arg=${_command__subcommand1___args[$_command___ai]} 482 | act=${_command__subcommand1___acts[$arg]} 483 | cmd=${_command__subcommand1___cmds[$arg]} 484 | if [[ "$act" != "" ]]; then 485 | _command___act $act 486 | return 0 487 | elif [[ "$cmd" != "" ]]; then 488 | a=($(eval $cmd)) 489 | else 490 | a=($(echo "${_command__subcommand1___words[$arg]}")) 491 | fi 492 | fi 493 | fi 494 | if [ ${#a[@]} -gt 0 ]; then 495 | _command___add "${a[@]}" 496 | return 0 497 | fi 498 | _command___any 499 | return $? 500 | } 501 | 502 | function _command__subcommand1___lskey() { 503 | setopt localoptions ksharrays 504 | 505 | if ! _command___keyerr; then 506 | local act cmd a 507 | act=${_command__subcommand1___acts[$_command___k]} 508 | cmd=${_command__subcommand1___cmds[$_command___k]} 509 | if [[ "$act" != "" ]]; then 510 | : 511 | elif [[ "$cmd" != "" ]]; then 512 | a=($(eval $cmd)) 513 | else 514 | a=($(echo "${_command__subcommand1___words[$_command___k]}")) 515 | fi 516 | if [[ "$act" != "" ]]; then 517 | _command___act $act 518 | return 0 519 | elif [ ${#a[@]} -gt 0 ]; then 520 | _command___add "${a[@]}" 521 | return 0 522 | fi 523 | fi 524 | _command___any 525 | return $? 526 | } 527 | 528 | function _command__subcommand1___tag() { 529 | setopt localoptions ksharrays 530 | 531 | local k 532 | if [[ "$2" == "" ]]; then 533 | if _command___keyerr; then return 1; fi 534 | k=$_command___k 535 | else 536 | k=$2 537 | fi 538 | if [[ ${_command__subcommand1___tags[$k]} == *' '$1' '* ]]; then 539 | return 0 540 | fi 541 | return 1 542 | } 543 | 544 | _command__subcommand2___keys=(' -s ') 545 | _command__subcommand2___args=() 546 | _command__subcommand2___acts=('') 547 | _command__subcommand2___cmds=('') 548 | _command__subcommand2___lens=(2) 549 | _command__subcommand2___nexts=('') 550 | _command__subcommand2___occurs=(1) 551 | _command__subcommand2___tags=(' opt ') 552 | _command__subcommand2___words=('') 553 | 554 | function _command__subcommand2() { 555 | setopt localoptions ksharrays 556 | 557 | _command___k=-1 558 | _command___ai=0 559 | _command___f=() 560 | while ! _command__subcommand2___tag stop; do 561 | _command___word 562 | _command__subcommand2___key 563 | if _command__subcommand2___tag term; then 564 | _command___inc 565 | break 566 | fi 567 | if _command___end; then 568 | _command__subcommand2___ls 569 | return $? 570 | fi 571 | if [[ $_command___w =~ ^- ]]; then 572 | if [ $_command___k -eq -1 ]; then 573 | _command___any 574 | return $? 575 | fi 576 | _command___found 577 | _command___inc 578 | _command__subcommand2___len 579 | if [ $_command___l -eq 1 ]; then 580 | continue 581 | fi 582 | if _command___end; then 583 | _command__subcommand2___lskey 584 | return $? 585 | fi 586 | _command___inc 587 | else 588 | if _command__subcommand2___arg; then 589 | if _command___end; then 590 | _command__subcommand2___lskey 591 | return $? 592 | fi 593 | fi 594 | _command___inc 595 | fi 596 | done 597 | if [[ "${_command__subcommand2___nexts[$_command___k]}" != "" ]]; then 598 | _command__subcommand2___next 599 | else 600 | _command___any 601 | fi 602 | return $? 603 | } 604 | 605 | function _command__subcommand2___arg() { 606 | setopt localoptions ksharrays 607 | 608 | if [ $_command___ai -lt ${#_command__subcommand2___args[@]} ]; then 609 | _command___k=${_command__subcommand2___args[$_command___ai]} 610 | if ! _command__subcommand2___tag varg; then 611 | let _command___ai+=1 612 | fi 613 | return 0 614 | fi 615 | return 1 616 | } 617 | 618 | function _command__subcommand2___key() { 619 | setopt localoptions ksharrays 620 | 621 | local i 622 | i=0 623 | while [ $i -lt ${#_command__subcommand2___keys[@]} ]; do 624 | if [[ ${_command__subcommand2___keys[$i]} == *' '$_command___w' '* ]]; then 625 | _command___k=$i 626 | return 0 627 | fi 628 | let i+=1 629 | done 630 | _command___k=-1 631 | return 1 632 | } 633 | 634 | function _command__subcommand2___len() { 635 | setopt localoptions ksharrays 636 | 637 | if _command___keyerr; then return 1; fi 638 | _command___l=${_command__subcommand2___lens[$_command___k]} 639 | return 0 640 | } 641 | 642 | function _command__subcommand2___ls() { 643 | setopt localoptions ksharrays 644 | 645 | local a i max found arg act cmd 646 | a=() 647 | if [[ "$_command___w" =~ ^- ]]; then 648 | i=0 649 | while [ $i -lt ${#_command__subcommand2___keys[@]} ]; do 650 | if _command__subcommand2___tag arg $i; then 651 | let i+=1 652 | continue 653 | fi 654 | found=${_command___f[$i]} 655 | if [[ "$found" == "" ]]; then 656 | found=0 657 | fi 658 | max=${_command__subcommand2___occurs[$i]} 659 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 660 | a+=($(echo "${_command__subcommand2___keys[$i]}")) 661 | fi 662 | let i+=1 663 | done 664 | else 665 | if [ $_command___ai -lt ${#_command__subcommand2___args[@]} ]; then 666 | arg=${_command__subcommand2___args[$_command___ai]} 667 | act=${_command__subcommand2___acts[$arg]} 668 | cmd=${_command__subcommand2___cmds[$arg]} 669 | if [[ "$act" != "" ]]; then 670 | _command___act $act 671 | return 0 672 | elif [[ "$cmd" != "" ]]; then 673 | a=($(eval $cmd)) 674 | else 675 | a=($(echo "${_command__subcommand2___words[$arg]}")) 676 | fi 677 | fi 678 | fi 679 | if [ ${#a[@]} -gt 0 ]; then 680 | _command___add "${a[@]}" 681 | return 0 682 | fi 683 | _command___any 684 | return $? 685 | } 686 | 687 | function _command__subcommand2___lskey() { 688 | setopt localoptions ksharrays 689 | 690 | if ! _command___keyerr; then 691 | local act cmd a 692 | act=${_command__subcommand2___acts[$_command___k]} 693 | cmd=${_command__subcommand2___cmds[$_command___k]} 694 | if [[ "$act" != "" ]]; then 695 | : 696 | elif [[ "$cmd" != "" ]]; then 697 | a=($(eval $cmd)) 698 | else 699 | a=($(echo "${_command__subcommand2___words[$_command___k]}")) 700 | fi 701 | if [[ "$act" != "" ]]; then 702 | _command___act $act 703 | return 0 704 | elif [ ${#a[@]} -gt 0 ]; then 705 | _command___add "${a[@]}" 706 | return 0 707 | fi 708 | fi 709 | _command___any 710 | return $? 711 | } 712 | 713 | function _command__subcommand2___tag() { 714 | setopt localoptions ksharrays 715 | 716 | local k 717 | if [[ "$2" == "" ]]; then 718 | if _command___keyerr; then return 1; fi 719 | k=$_command___k 720 | else 721 | k=$2 722 | fi 723 | if [[ ${_command__subcommand2___tags[$k]} == *' '$1' '* ]]; then 724 | return 0 725 | fi 726 | return 1 727 | } 728 | -------------------------------------------------------------------------------- /spec/internal/shell_completion_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | require "./shell_completion/class" 3 | 4 | module CliInternalSpecs::ShellCompletion 5 | extend HaveFiles::Spec::Dsl 6 | 7 | it name do 8 | Dir.tmp do |tmp| 9 | Dir.mkdir_p File.join(tmp, "zsh") 10 | File.write(File.join(tmp, "command.bash"), Command.generate_bash_completion + "\n") 11 | File.write(File.join(tmp, "command.zsh"), Command.generate_zsh_completion(functional: false) + "\n") 12 | File.write(File.join(tmp, "zsh", "_command"), Command.generate_zsh_completion + "\n") 13 | tmp.should have_files File.join(__DIR__, "shell_completion", "fixtures") 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/internal/three_level_command_name_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalThreeLevelCommandNameFeature 4 | class One < Cli::Supercommand 5 | command "two" 6 | 7 | module Commands 8 | class Two < Cli::Supercommand 9 | command "three" 10 | 11 | module Commands 12 | class Three < Cli::Supercommand 13 | end 14 | end 15 | end 16 | end 17 | end 18 | 19 | it name do 20 | One::Commands::Two::Commands::Three.__klass.global_name.should eq "one two three" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/internal/unknown_option_model_item_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalSpecs::UnknownOptionModelItem 4 | class Command < Cli::Command 5 | class Options 6 | string "-s" 7 | unknown 8 | end 9 | 10 | def run 11 | puts options.s? 12 | puts unparsed_args.join(" ") 13 | end 14 | end 15 | 16 | it name do 17 | Command.run(%w(foo bar baz)) do |cmd| 18 | cmd.out.gets_to_end.chomp.should eq "\nfoo bar baz" 19 | end 20 | Command.run(%w(-s foo bar baz)) do |cmd| 21 | cmd.out.gets_to_end.chomp.should eq "foo\nbar baz" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/internal/version_handler_dsl_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliInternalVersionHandlerDslFeature 4 | include Cli::Spec::Helper 5 | 6 | class Default < Cli::Command 7 | version "1.0.0" 8 | class Options 9 | version 10 | end 11 | end 12 | 13 | class Specific < Cli::Command 14 | version "1.0.0" 15 | class Options 16 | version "--show-version", desc: "version!" 17 | end 18 | end 19 | 20 | macro test(example, klass, names, desc) 21 | it {{example}} do 22 | handler = {{klass.id}}::Options.__klass.definitions.handlers[{{names[0]}}] 23 | handler.names.should eq {{names}} 24 | {% for e, i in names %} 25 | {{klass.id}}.run([{{e}}]).should exit_command(output: "1.0.0") 26 | {% end %} 27 | end 28 | end 29 | 30 | describe name do 31 | test "default", "Default", %w(-v --version), "show version" 32 | test "specific", "Specific", %w(--show-version), "version!" 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "crystal_plus/dir/.tmp" 3 | require "have_files/spec/dsl" 4 | require "../src/cli" 5 | require "../src/spec" 6 | 7 | ENV["CRYSTAL_CLI_ENV"] = ENV["CRYSTAL_CLI_ENV"]? || "test" 8 | -------------------------------------------------------------------------------- /spec/wiki/handling_events/run.cr: -------------------------------------------------------------------------------- 1 | require "../../../src/cli" 2 | 3 | class FinallySmile < Cli::Command 4 | after_exit do |cmd, exit| 5 | cmd.puts exit.message 6 | cmd.puts ":)" 7 | end 8 | 9 | def run 10 | exit! ":(" 11 | end 12 | end 13 | 14 | FinallySmile.run 15 | -------------------------------------------------------------------------------- /spec/wiki/handling_events_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliWikiHandlingEventsFeature 4 | it name do 5 | {{ run("#{__DIR__}/handling_events/run").stringify }}.should eq ":(\n:(\n:)\n" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/wiki/shell_completion/class.cr: -------------------------------------------------------------------------------- 1 | module CliWikiShellCompletionFeature 2 | class TicketToRide < Cli::Command 3 | class Options 4 | string "--by", any_of: %w(train plane taxi) 5 | arg "for", any_of: %w(kyoto kanazawa kamakura) 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/wiki/shell_completion/fixtures/ticket-to-ride-completion.bash: -------------------------------------------------------------------------------- 1 | _ticket_to_ride___keys=(' --by ' ' for ') 2 | _ticket_to_ride___args=(1 ) 3 | _ticket_to_ride___acts=('' '') 4 | _ticket_to_ride___cmds=('' '') 5 | _ticket_to_ride___lens=(2 1) 6 | _ticket_to_ride___nexts=('' '') 7 | _ticket_to_ride___occurs=(1 1) 8 | _ticket_to_ride___tags=(' opt ' ' arg ') 9 | _ticket_to_ride___words=(' train plane taxi ' ' kyoto kanazawa kamakura ') 10 | 11 | function _ticket_to_ride___act() { 12 | _ticket_to_ride___cur 13 | COMPREPLY=( $(compgen -A $1 -- "${_ticket_to_ride___c}") ) 14 | return 0 15 | } 16 | 17 | function _ticket_to_ride___add() { 18 | _ticket_to_ride___cur 19 | COMPREPLY=( $(compgen -W "$(echo ${@:1})" -- "${_ticket_to_ride___c}") ) 20 | return 0 21 | } 22 | 23 | function _ticket_to_ride___any() { 24 | _ticket_to_ride___act file 25 | return $? 26 | } 27 | 28 | function _ticket_to_ride___cur() { 29 | _ticket_to_ride___c="${COMP_WORDS[COMP_CWORD]}" 30 | return 0 31 | } 32 | 33 | function _ticket_to_ride___end() { 34 | if [ $_ticket_to_ride___i -lt $COMP_CWORD ]; then 35 | return 1 36 | fi 37 | return 0 38 | } 39 | 40 | function _ticket_to_ride___found() { 41 | if _ticket_to_ride___keyerr; then return 1; fi 42 | local n 43 | n=${_ticket_to_ride___f[$_ticket_to_ride___k]} 44 | if [[ "$n" == "" ]]; then 45 | n=1 46 | else 47 | let n+=1 48 | fi 49 | _ticket_to_ride___f[$_ticket_to_ride___k]=$n 50 | return 0 51 | } 52 | 53 | function _ticket_to_ride___inc() { 54 | let _ticket_to_ride___i+=1 55 | return 0 56 | } 57 | 58 | function _ticket_to_ride___keyerr() { 59 | if [[ "$_ticket_to_ride___k" == "" ]] || [ $_ticket_to_ride___k -lt 0 ]; then 60 | return 0 61 | fi 62 | return 1 63 | } 64 | 65 | function _ticket_to_ride___word() { 66 | _ticket_to_ride___w="${COMP_WORDS[$_ticket_to_ride___i]}" 67 | return 0 68 | } 69 | 70 | function _ticket_to_ride() { 71 | _ticket_to_ride___i=1 72 | _ticket_to_ride___k=-1 73 | _ticket_to_ride___ai=0 74 | _ticket_to_ride___f=() 75 | while ! _ticket_to_ride___tag stop; do 76 | _ticket_to_ride___word 77 | _ticket_to_ride___key 78 | if _ticket_to_ride___tag term; then 79 | _ticket_to_ride___inc 80 | break 81 | fi 82 | if _ticket_to_ride___end; then 83 | _ticket_to_ride___ls 84 | return $? 85 | fi 86 | if [[ $_ticket_to_ride___w =~ ^- ]]; then 87 | if [ $_ticket_to_ride___k -eq -1 ]; then 88 | _ticket_to_ride___any 89 | return $? 90 | fi 91 | _ticket_to_ride___found 92 | _ticket_to_ride___inc 93 | _ticket_to_ride___len 94 | if [ $_ticket_to_ride___l -eq 1 ]; then 95 | continue 96 | fi 97 | if _ticket_to_ride___end; then 98 | _ticket_to_ride___lskey 99 | return $? 100 | fi 101 | _ticket_to_ride___inc 102 | else 103 | if _ticket_to_ride___arg; then 104 | if _ticket_to_ride___end; then 105 | _ticket_to_ride___lskey 106 | return $? 107 | fi 108 | fi 109 | _ticket_to_ride___inc 110 | fi 111 | done 112 | if [[ "${_ticket_to_ride___nexts[$_ticket_to_ride___k]}" != "" ]]; then 113 | _ticket_to_ride___next 114 | else 115 | _ticket_to_ride___any 116 | fi 117 | return $? 118 | } 119 | 120 | function _ticket_to_ride___arg() { 121 | if [ $_ticket_to_ride___ai -lt ${#_ticket_to_ride___args[@]} ]; then 122 | _ticket_to_ride___k=${_ticket_to_ride___args[$_ticket_to_ride___ai]} 123 | if ! _ticket_to_ride___tag varg; then 124 | let _ticket_to_ride___ai+=1 125 | fi 126 | return 0 127 | fi 128 | return 1 129 | } 130 | 131 | function _ticket_to_ride___key() { 132 | local i 133 | i=0 134 | while [ $i -lt ${#_ticket_to_ride___keys[@]} ]; do 135 | if [[ ${_ticket_to_ride___keys[$i]} == *' '$_ticket_to_ride___w' '* ]]; then 136 | _ticket_to_ride___k=$i 137 | return 0 138 | fi 139 | let i+=1 140 | done 141 | _ticket_to_ride___k=-1 142 | return 1 143 | } 144 | 145 | function _ticket_to_ride___len() { 146 | if _ticket_to_ride___keyerr; then return 1; fi 147 | _ticket_to_ride___l=${_ticket_to_ride___lens[$_ticket_to_ride___k]} 148 | return 0 149 | } 150 | 151 | function _ticket_to_ride___ls() { 152 | local a i max found arg act cmd 153 | a=() 154 | if [[ "$_ticket_to_ride___w" =~ ^- ]]; then 155 | i=0 156 | while [ $i -lt ${#_ticket_to_ride___keys[@]} ]; do 157 | if _ticket_to_ride___tag arg $i; then 158 | let i+=1 159 | continue 160 | fi 161 | found=${_ticket_to_ride___f[$i]} 162 | if [[ "$found" == "" ]]; then 163 | found=0 164 | fi 165 | max=${_ticket_to_ride___occurs[$i]} 166 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 167 | a+=($(echo "${_ticket_to_ride___keys[$i]}")) 168 | fi 169 | let i+=1 170 | done 171 | else 172 | if [ $_ticket_to_ride___ai -lt ${#_ticket_to_ride___args[@]} ]; then 173 | arg=${_ticket_to_ride___args[$_ticket_to_ride___ai]} 174 | act=${_ticket_to_ride___acts[$arg]} 175 | cmd=${_ticket_to_ride___cmds[$arg]} 176 | if [[ "$act" != "" ]]; then 177 | _ticket_to_ride___act $act 178 | return 0 179 | elif [[ "$cmd" != "" ]]; then 180 | a=($(eval $cmd)) 181 | else 182 | a=($(echo "${_ticket_to_ride___words[$arg]}")) 183 | fi 184 | fi 185 | fi 186 | if [ ${#a[@]} -gt 0 ]; then 187 | _ticket_to_ride___add "${a[@]}" 188 | return 0 189 | fi 190 | _ticket_to_ride___any 191 | return $? 192 | } 193 | 194 | function _ticket_to_ride___lskey() { 195 | if ! _ticket_to_ride___keyerr; then 196 | local act cmd a 197 | act=${_ticket_to_ride___acts[$_ticket_to_ride___k]} 198 | cmd=${_ticket_to_ride___cmds[$_ticket_to_ride___k]} 199 | if [[ "$act" != "" ]]; then 200 | : 201 | elif [[ "$cmd" != "" ]]; then 202 | a=($(eval $cmd)) 203 | else 204 | a=($(echo "${_ticket_to_ride___words[$_ticket_to_ride___k]}")) 205 | fi 206 | if [[ "$act" != "" ]]; then 207 | _ticket_to_ride___act $act 208 | return 0 209 | elif [ ${#a[@]} -gt 0 ]; then 210 | _ticket_to_ride___add "${a[@]}" 211 | return 0 212 | fi 213 | fi 214 | _ticket_to_ride___any 215 | return $? 216 | } 217 | 218 | function _ticket_to_ride___tag() { 219 | local k 220 | if [[ "$2" == "" ]]; then 221 | if _ticket_to_ride___keyerr; then return 1; fi 222 | k=$_ticket_to_ride___k 223 | else 224 | k=$2 225 | fi 226 | if [[ ${_ticket_to_ride___tags[$k]} == *' '$1' '* ]]; then 227 | return 0 228 | fi 229 | return 1 230 | } 231 | 232 | complete -F _ticket_to_ride ticket-to-ride 233 | -------------------------------------------------------------------------------- /spec/wiki/shell_completion/fixtures/ticket-to-ride-completion.zsh: -------------------------------------------------------------------------------- 1 | _ticket_to_ride___keys=(' --by ' ' for ') 2 | _ticket_to_ride___args=(1 ) 3 | _ticket_to_ride___acts=('' '') 4 | _ticket_to_ride___cmds=('' '') 5 | _ticket_to_ride___lens=(2 1) 6 | _ticket_to_ride___nexts=('' '') 7 | _ticket_to_ride___occurs=(1 1) 8 | _ticket_to_ride___tags=(' opt ' ' arg ') 9 | _ticket_to_ride___words=(' train plane taxi ' ' kyoto kanazawa kamakura ') 10 | 11 | function _ticket_to_ride___act() { 12 | setopt localoptions ksharrays 13 | 14 | local -a a jids 15 | case $1 in 16 | alias) 17 | a=( "${(k)aliases[@]}" ) ;; 18 | arrayvar) 19 | a=( "${(k@)parameters[(R)array*]}" ) 20 | ;; 21 | binding) 22 | a=( "${(k)widgets[@]}" ) 23 | ;; 24 | builtin) 25 | a=( "${(k)builtins[@]}" "${(k)dis_builtins[@]}" ) 26 | ;; 27 | command) 28 | a=( "${(k)commands[@]}" "${(k)aliases[@]}" "${(k)builtins[@]}" "${(k)functions[@]}" "${(k)reswords[@]}") 29 | ;; 30 | directory) 31 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N-/) ) 32 | ;; 33 | disabled) 34 | a=( "${(k)dis_builtins[@]}" ) 35 | ;; 36 | enabled) 37 | a=( "${(k)builtins[@]}" ) 38 | ;; 39 | export) 40 | a=( "${(k)parameters[(R)*export*]}" ) 41 | ;; 42 | file) 43 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 44 | ;; 45 | function) 46 | a=( "${(k)functions[@]}" ) 47 | ;; 48 | group) 49 | _groups -U -O a 50 | ;; 51 | hostname) 52 | _hosts -U -O a 53 | ;; 54 | job) 55 | a=( "${savejobtexts[@]%% *}" ) 56 | ;; 57 | keyword) 58 | a=( "${(k)reswords[@]}" ) 59 | ;; 60 | running) 61 | a=() 62 | jids=( "${(@k)savejobstates[(R)running*]}" ) 63 | for job in "${jids[@]}"; do 64 | a+=( ${savejobtexts[$job]%% *} ) 65 | done 66 | ;; 67 | stopped) 68 | a=() 69 | jids=( "${(@k)savejobstates[(R)suspended*]}" ) 70 | for job in "${jids[@]}"; do 71 | a+=( ${savejobtexts[$job]%% *} ) 72 | done 73 | ;; 74 | setopt|shopt) 75 | a=( "${(k)options[@]}" ) 76 | ;; 77 | signal) 78 | a=( "SIG${^signals[@]}" ) 79 | ;; 80 | user) 81 | a=( "${(k)userdirs[@]}" ) 82 | ;; 83 | variable) 84 | a=( "${(k)parameters[@]}" ) 85 | ;; 86 | *) 87 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 88 | ;; 89 | esac 90 | compadd -- "${a[@]}" 91 | return 0 92 | } 93 | 94 | function _ticket_to_ride___add() { 95 | setopt localoptions ksharrays 96 | 97 | compadd -- "${@:1}" 98 | return 0 99 | } 100 | 101 | function _ticket_to_ride___any() { 102 | setopt localoptions ksharrays 103 | 104 | _ticket_to_ride___act file 105 | return $? 106 | } 107 | 108 | function _ticket_to_ride___cur() { 109 | setopt localoptions ksharrays 110 | 111 | _ticket_to_ride___c="${COMP_WORDS[COMP_CWORD]}" 112 | return 0 113 | } 114 | 115 | function _ticket_to_ride___end() { 116 | setopt localoptions ksharrays 117 | 118 | if [ $_ticket_to_ride___i -lt $COMP_CWORD ]; then 119 | return 1 120 | fi 121 | return 0 122 | } 123 | 124 | function _ticket_to_ride___found() { 125 | setopt localoptions ksharrays 126 | 127 | if _ticket_to_ride___keyerr; then return 1; fi 128 | local n 129 | n=${_ticket_to_ride___f[$_ticket_to_ride___k]} 130 | if [[ "$n" == "" ]]; then 131 | n=1 132 | else 133 | let n+=1 134 | fi 135 | _ticket_to_ride___f[$_ticket_to_ride___k]=$n 136 | return 0 137 | } 138 | 139 | function _ticket_to_ride___inc() { 140 | setopt localoptions ksharrays 141 | 142 | let _ticket_to_ride___i+=1 143 | return 0 144 | } 145 | 146 | function _ticket_to_ride___keyerr() { 147 | setopt localoptions ksharrays 148 | 149 | if [[ "$_ticket_to_ride___k" == "" ]] || [ $_ticket_to_ride___k -lt 0 ]; then 150 | return 0 151 | fi 152 | return 1 153 | } 154 | 155 | function _ticket_to_ride___word() { 156 | setopt localoptions ksharrays 157 | 158 | _ticket_to_ride___w="${COMP_WORDS[$_ticket_to_ride___i]}" 159 | return 0 160 | } 161 | 162 | function _ticket_to_ride() { 163 | setopt localoptions ksharrays 164 | 165 | (( COMP_CWORD = CURRENT - 1 )) 166 | COMP_WORDS=($(echo ${words[@]})) 167 | _ticket_to_ride___i=1 168 | _ticket_to_ride___k=-1 169 | _ticket_to_ride___ai=0 170 | _ticket_to_ride___f=() 171 | while ! _ticket_to_ride___tag stop; do 172 | _ticket_to_ride___word 173 | _ticket_to_ride___key 174 | if _ticket_to_ride___tag term; then 175 | _ticket_to_ride___inc 176 | break 177 | fi 178 | if _ticket_to_ride___end; then 179 | _ticket_to_ride___ls 180 | return $? 181 | fi 182 | if [[ $_ticket_to_ride___w =~ ^- ]]; then 183 | if [ $_ticket_to_ride___k -eq -1 ]; then 184 | _ticket_to_ride___any 185 | return $? 186 | fi 187 | _ticket_to_ride___found 188 | _ticket_to_ride___inc 189 | _ticket_to_ride___len 190 | if [ $_ticket_to_ride___l -eq 1 ]; then 191 | continue 192 | fi 193 | if _ticket_to_ride___end; then 194 | _ticket_to_ride___lskey 195 | return $? 196 | fi 197 | _ticket_to_ride___inc 198 | else 199 | if _ticket_to_ride___arg; then 200 | if _ticket_to_ride___end; then 201 | _ticket_to_ride___lskey 202 | return $? 203 | fi 204 | fi 205 | _ticket_to_ride___inc 206 | fi 207 | done 208 | if [[ "${_ticket_to_ride___nexts[$_ticket_to_ride___k]}" != "" ]]; then 209 | _ticket_to_ride___next 210 | else 211 | _ticket_to_ride___any 212 | fi 213 | return $? 214 | } 215 | 216 | function _ticket_to_ride___arg() { 217 | setopt localoptions ksharrays 218 | 219 | if [ $_ticket_to_ride___ai -lt ${#_ticket_to_ride___args[@]} ]; then 220 | _ticket_to_ride___k=${_ticket_to_ride___args[$_ticket_to_ride___ai]} 221 | if ! _ticket_to_ride___tag varg; then 222 | let _ticket_to_ride___ai+=1 223 | fi 224 | return 0 225 | fi 226 | return 1 227 | } 228 | 229 | function _ticket_to_ride___key() { 230 | setopt localoptions ksharrays 231 | 232 | local i 233 | i=0 234 | while [ $i -lt ${#_ticket_to_ride___keys[@]} ]; do 235 | if [[ ${_ticket_to_ride___keys[$i]} == *' '$_ticket_to_ride___w' '* ]]; then 236 | _ticket_to_ride___k=$i 237 | return 0 238 | fi 239 | let i+=1 240 | done 241 | _ticket_to_ride___k=-1 242 | return 1 243 | } 244 | 245 | function _ticket_to_ride___len() { 246 | setopt localoptions ksharrays 247 | 248 | if _ticket_to_ride___keyerr; then return 1; fi 249 | _ticket_to_ride___l=${_ticket_to_ride___lens[$_ticket_to_ride___k]} 250 | return 0 251 | } 252 | 253 | function _ticket_to_ride___ls() { 254 | setopt localoptions ksharrays 255 | 256 | local a i max found arg act cmd 257 | a=() 258 | if [[ "$_ticket_to_ride___w" =~ ^- ]]; then 259 | i=0 260 | while [ $i -lt ${#_ticket_to_ride___keys[@]} ]; do 261 | if _ticket_to_ride___tag arg $i; then 262 | let i+=1 263 | continue 264 | fi 265 | found=${_ticket_to_ride___f[$i]} 266 | if [[ "$found" == "" ]]; then 267 | found=0 268 | fi 269 | max=${_ticket_to_ride___occurs[$i]} 270 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 271 | a+=($(echo "${_ticket_to_ride___keys[$i]}")) 272 | fi 273 | let i+=1 274 | done 275 | else 276 | if [ $_ticket_to_ride___ai -lt ${#_ticket_to_ride___args[@]} ]; then 277 | arg=${_ticket_to_ride___args[$_ticket_to_ride___ai]} 278 | act=${_ticket_to_ride___acts[$arg]} 279 | cmd=${_ticket_to_ride___cmds[$arg]} 280 | if [[ "$act" != "" ]]; then 281 | _ticket_to_ride___act $act 282 | return 0 283 | elif [[ "$cmd" != "" ]]; then 284 | a=($(eval $cmd)) 285 | else 286 | a=($(echo "${_ticket_to_ride___words[$arg]}")) 287 | fi 288 | fi 289 | fi 290 | if [ ${#a[@]} -gt 0 ]; then 291 | _ticket_to_ride___add "${a[@]}" 292 | return 0 293 | fi 294 | _ticket_to_ride___any 295 | return $? 296 | } 297 | 298 | function _ticket_to_ride___lskey() { 299 | setopt localoptions ksharrays 300 | 301 | if ! _ticket_to_ride___keyerr; then 302 | local act cmd a 303 | act=${_ticket_to_ride___acts[$_ticket_to_ride___k]} 304 | cmd=${_ticket_to_ride___cmds[$_ticket_to_ride___k]} 305 | if [[ "$act" != "" ]]; then 306 | : 307 | elif [[ "$cmd" != "" ]]; then 308 | a=($(eval $cmd)) 309 | else 310 | a=($(echo "${_ticket_to_ride___words[$_ticket_to_ride___k]}")) 311 | fi 312 | if [[ "$act" != "" ]]; then 313 | _ticket_to_ride___act $act 314 | return 0 315 | elif [ ${#a[@]} -gt 0 ]; then 316 | _ticket_to_ride___add "${a[@]}" 317 | return 0 318 | fi 319 | fi 320 | _ticket_to_ride___any 321 | return $? 322 | } 323 | 324 | function _ticket_to_ride___tag() { 325 | setopt localoptions ksharrays 326 | 327 | local k 328 | if [[ "$2" == "" ]]; then 329 | if _ticket_to_ride___keyerr; then return 1; fi 330 | k=$_ticket_to_ride___k 331 | else 332 | k=$2 333 | fi 334 | if [[ ${_ticket_to_ride___tags[$k]} == *' '$1' '* ]]; then 335 | return 0 336 | fi 337 | return 1 338 | } 339 | 340 | compdef _ticket_to_ride ticket-to-ride 341 | -------------------------------------------------------------------------------- /spec/wiki/shell_completion/fixtures/zsh/_ticket_to_ride: -------------------------------------------------------------------------------- 1 | #compdef ticket-to-ride 2 | 3 | _ticket_to_ride___keys=(' --by ' ' for ') 4 | _ticket_to_ride___args=(1 ) 5 | _ticket_to_ride___acts=('' '') 6 | _ticket_to_ride___cmds=('' '') 7 | _ticket_to_ride___lens=(2 1) 8 | _ticket_to_ride___nexts=('' '') 9 | _ticket_to_ride___occurs=(1 1) 10 | _ticket_to_ride___tags=(' opt ' ' arg ') 11 | _ticket_to_ride___words=(' train plane taxi ' ' kyoto kanazawa kamakura ') 12 | 13 | function _ticket_to_ride___act() { 14 | setopt localoptions ksharrays 15 | 16 | local -a a jids 17 | case $1 in 18 | alias) 19 | a=( "${(k)aliases[@]}" ) ;; 20 | arrayvar) 21 | a=( "${(k@)parameters[(R)array*]}" ) 22 | ;; 23 | binding) 24 | a=( "${(k)widgets[@]}" ) 25 | ;; 26 | builtin) 27 | a=( "${(k)builtins[@]}" "${(k)dis_builtins[@]}" ) 28 | ;; 29 | command) 30 | a=( "${(k)commands[@]}" "${(k)aliases[@]}" "${(k)builtins[@]}" "${(k)functions[@]}" "${(k)reswords[@]}") 31 | ;; 32 | directory) 33 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N-/) ) 34 | ;; 35 | disabled) 36 | a=( "${(k)dis_builtins[@]}" ) 37 | ;; 38 | enabled) 39 | a=( "${(k)builtins[@]}" ) 40 | ;; 41 | export) 42 | a=( "${(k)parameters[(R)*export*]}" ) 43 | ;; 44 | file) 45 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 46 | ;; 47 | function) 48 | a=( "${(k)functions[@]}" ) 49 | ;; 50 | group) 51 | _groups -U -O a 52 | ;; 53 | hostname) 54 | _hosts -U -O a 55 | ;; 56 | job) 57 | a=( "${savejobtexts[@]%% *}" ) 58 | ;; 59 | keyword) 60 | a=( "${(k)reswords[@]}" ) 61 | ;; 62 | running) 63 | a=() 64 | jids=( "${(@k)savejobstates[(R)running*]}" ) 65 | for job in "${jids[@]}"; do 66 | a+=( ${savejobtexts[$job]%% *} ) 67 | done 68 | ;; 69 | stopped) 70 | a=() 71 | jids=( "${(@k)savejobstates[(R)suspended*]}" ) 72 | for job in "${jids[@]}"; do 73 | a+=( ${savejobtexts[$job]%% *} ) 74 | done 75 | ;; 76 | setopt|shopt) 77 | a=( "${(k)options[@]}" ) 78 | ;; 79 | signal) 80 | a=( "SIG${^signals[@]}" ) 81 | ;; 82 | user) 83 | a=( "${(k)userdirs[@]}" ) 84 | ;; 85 | variable) 86 | a=( "${(k)parameters[@]}" ) 87 | ;; 88 | *) 89 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 90 | ;; 91 | esac 92 | compadd -- "${a[@]}" 93 | return 0 94 | } 95 | 96 | function _ticket_to_ride___add() { 97 | setopt localoptions ksharrays 98 | 99 | compadd -- "${@:1}" 100 | return 0 101 | } 102 | 103 | function _ticket_to_ride___any() { 104 | setopt localoptions ksharrays 105 | 106 | _ticket_to_ride___act file 107 | return $? 108 | } 109 | 110 | function _ticket_to_ride___cur() { 111 | setopt localoptions ksharrays 112 | 113 | _ticket_to_ride___c="${COMP_WORDS[COMP_CWORD]}" 114 | return 0 115 | } 116 | 117 | function _ticket_to_ride___end() { 118 | setopt localoptions ksharrays 119 | 120 | if [ $_ticket_to_ride___i -lt $COMP_CWORD ]; then 121 | return 1 122 | fi 123 | return 0 124 | } 125 | 126 | function _ticket_to_ride___found() { 127 | setopt localoptions ksharrays 128 | 129 | if _ticket_to_ride___keyerr; then return 1; fi 130 | local n 131 | n=${_ticket_to_ride___f[$_ticket_to_ride___k]} 132 | if [[ "$n" == "" ]]; then 133 | n=1 134 | else 135 | let n+=1 136 | fi 137 | _ticket_to_ride___f[$_ticket_to_ride___k]=$n 138 | return 0 139 | } 140 | 141 | function _ticket_to_ride___inc() { 142 | setopt localoptions ksharrays 143 | 144 | let _ticket_to_ride___i+=1 145 | return 0 146 | } 147 | 148 | function _ticket_to_ride___keyerr() { 149 | setopt localoptions ksharrays 150 | 151 | if [[ "$_ticket_to_ride___k" == "" ]] || [ $_ticket_to_ride___k -lt 0 ]; then 152 | return 0 153 | fi 154 | return 1 155 | } 156 | 157 | function _ticket_to_ride___word() { 158 | setopt localoptions ksharrays 159 | 160 | _ticket_to_ride___w="${COMP_WORDS[$_ticket_to_ride___i]}" 161 | return 0 162 | } 163 | 164 | function _ticket_to_ride() { 165 | setopt localoptions ksharrays 166 | 167 | (( COMP_CWORD = CURRENT - 1 )) 168 | COMP_WORDS=($(echo ${words[@]})) 169 | _ticket_to_ride___i=1 170 | _ticket_to_ride___k=-1 171 | _ticket_to_ride___ai=0 172 | _ticket_to_ride___f=() 173 | while ! _ticket_to_ride___tag stop; do 174 | _ticket_to_ride___word 175 | _ticket_to_ride___key 176 | if _ticket_to_ride___tag term; then 177 | _ticket_to_ride___inc 178 | break 179 | fi 180 | if _ticket_to_ride___end; then 181 | _ticket_to_ride___ls 182 | return $? 183 | fi 184 | if [[ $_ticket_to_ride___w =~ ^- ]]; then 185 | if [ $_ticket_to_ride___k -eq -1 ]; then 186 | _ticket_to_ride___any 187 | return $? 188 | fi 189 | _ticket_to_ride___found 190 | _ticket_to_ride___inc 191 | _ticket_to_ride___len 192 | if [ $_ticket_to_ride___l -eq 1 ]; then 193 | continue 194 | fi 195 | if _ticket_to_ride___end; then 196 | _ticket_to_ride___lskey 197 | return $? 198 | fi 199 | _ticket_to_ride___inc 200 | else 201 | if _ticket_to_ride___arg; then 202 | if _ticket_to_ride___end; then 203 | _ticket_to_ride___lskey 204 | return $? 205 | fi 206 | fi 207 | _ticket_to_ride___inc 208 | fi 209 | done 210 | if [[ "${_ticket_to_ride___nexts[$_ticket_to_ride___k]}" != "" ]]; then 211 | _ticket_to_ride___next 212 | else 213 | _ticket_to_ride___any 214 | fi 215 | return $? 216 | } 217 | 218 | function _ticket_to_ride___arg() { 219 | setopt localoptions ksharrays 220 | 221 | if [ $_ticket_to_ride___ai -lt ${#_ticket_to_ride___args[@]} ]; then 222 | _ticket_to_ride___k=${_ticket_to_ride___args[$_ticket_to_ride___ai]} 223 | if ! _ticket_to_ride___tag varg; then 224 | let _ticket_to_ride___ai+=1 225 | fi 226 | return 0 227 | fi 228 | return 1 229 | } 230 | 231 | function _ticket_to_ride___key() { 232 | setopt localoptions ksharrays 233 | 234 | local i 235 | i=0 236 | while [ $i -lt ${#_ticket_to_ride___keys[@]} ]; do 237 | if [[ ${_ticket_to_ride___keys[$i]} == *' '$_ticket_to_ride___w' '* ]]; then 238 | _ticket_to_ride___k=$i 239 | return 0 240 | fi 241 | let i+=1 242 | done 243 | _ticket_to_ride___k=-1 244 | return 1 245 | } 246 | 247 | function _ticket_to_ride___len() { 248 | setopt localoptions ksharrays 249 | 250 | if _ticket_to_ride___keyerr; then return 1; fi 251 | _ticket_to_ride___l=${_ticket_to_ride___lens[$_ticket_to_ride___k]} 252 | return 0 253 | } 254 | 255 | function _ticket_to_ride___ls() { 256 | setopt localoptions ksharrays 257 | 258 | local a i max found arg act cmd 259 | a=() 260 | if [[ "$_ticket_to_ride___w" =~ ^- ]]; then 261 | i=0 262 | while [ $i -lt ${#_ticket_to_ride___keys[@]} ]; do 263 | if _ticket_to_ride___tag arg $i; then 264 | let i+=1 265 | continue 266 | fi 267 | found=${_ticket_to_ride___f[$i]} 268 | if [[ "$found" == "" ]]; then 269 | found=0 270 | fi 271 | max=${_ticket_to_ride___occurs[$i]} 272 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 273 | a+=($(echo "${_ticket_to_ride___keys[$i]}")) 274 | fi 275 | let i+=1 276 | done 277 | else 278 | if [ $_ticket_to_ride___ai -lt ${#_ticket_to_ride___args[@]} ]; then 279 | arg=${_ticket_to_ride___args[$_ticket_to_ride___ai]} 280 | act=${_ticket_to_ride___acts[$arg]} 281 | cmd=${_ticket_to_ride___cmds[$arg]} 282 | if [[ "$act" != "" ]]; then 283 | _ticket_to_ride___act $act 284 | return 0 285 | elif [[ "$cmd" != "" ]]; then 286 | a=($(eval $cmd)) 287 | else 288 | a=($(echo "${_ticket_to_ride___words[$arg]}")) 289 | fi 290 | fi 291 | fi 292 | if [ ${#a[@]} -gt 0 ]; then 293 | _ticket_to_ride___add "${a[@]}" 294 | return 0 295 | fi 296 | _ticket_to_ride___any 297 | return $? 298 | } 299 | 300 | function _ticket_to_ride___lskey() { 301 | setopt localoptions ksharrays 302 | 303 | if ! _ticket_to_ride___keyerr; then 304 | local act cmd a 305 | act=${_ticket_to_ride___acts[$_ticket_to_ride___k]} 306 | cmd=${_ticket_to_ride___cmds[$_ticket_to_ride___k]} 307 | if [[ "$act" != "" ]]; then 308 | : 309 | elif [[ "$cmd" != "" ]]; then 310 | a=($(eval $cmd)) 311 | else 312 | a=($(echo "${_ticket_to_ride___words[$_ticket_to_ride___k]}")) 313 | fi 314 | if [[ "$act" != "" ]]; then 315 | _ticket_to_ride___act $act 316 | return 0 317 | elif [ ${#a[@]} -gt 0 ]; then 318 | _ticket_to_ride___add "${a[@]}" 319 | return 0 320 | fi 321 | fi 322 | _ticket_to_ride___any 323 | return $? 324 | } 325 | 326 | function _ticket_to_ride___tag() { 327 | setopt localoptions ksharrays 328 | 329 | local k 330 | if [[ "$2" == "" ]]; then 331 | if _ticket_to_ride___keyerr; then return 1; fi 332 | k=$_ticket_to_ride___k 333 | else 334 | k=$2 335 | fi 336 | if [[ ${_ticket_to_ride___tags[$k]} == *' '$1' '* ]]; then 337 | return 0 338 | fi 339 | return 1 340 | } 341 | -------------------------------------------------------------------------------- /spec/wiki/shell_completion_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | require "./shell_completion/class" 3 | 4 | module CliWikiShellCompletionFeature 5 | it name do 6 | dir = "#{__DIR__}/shell_completion/fixtures" 7 | zsh_dir = "#{dir}/zsh" 8 | TicketToRide.generate_bash_completion.should eq File.read("#{dir}/ticket-to-ride-completion.bash").chomp 9 | TicketToRide.generate_zsh_completion(functional: false).should eq File.read("#{dir}/ticket-to-ride-completion.zsh").chomp 10 | TicketToRide.generate_zsh_completion.should eq File.read("#{zsh_dir}/_ticket_to_ride").chomp 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/wiki/the_exit_ways_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliWikiTheExitWaysFeature 4 | module NormalReturn 5 | class Command < Cli::Command 6 | def run 7 | ":)" 8 | end 9 | end 10 | 11 | it name do 12 | Command.run.should eq ":)" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/wiki/using_named_ios_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module CliWikiUsingNamedIosFeature 4 | module SettingCustomNamedIos 5 | MEMORY = IO::Memory.new 6 | 7 | class SmileLog < Cli::Command 8 | on_initialize do |cmd| 9 | cmd.io[:smiles] = MEMORY 10 | end 11 | 12 | def run 13 | io[:smiles].puts ":)" 14 | end 15 | end 16 | 17 | it name do 18 | SmileLog.run do |cmd| 19 | cmd.io[:smiles].to_s.should eq ":)\n" 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /src/cli.cr: -------------------------------------------------------------------------------- 1 | require "./lib" 2 | 3 | module Cli 4 | # :nodoc: 5 | def self.env 6 | ENV["CRYSTAL_CLI_ENV"]? 7 | end 8 | 9 | # :nodoc: 10 | def self.test? 11 | env == "test" 12 | end 13 | 14 | # :nodoc: 15 | def self.new_default_io 16 | IoHash.new.tap do |io| 17 | io[:out] = if test? 18 | Ios::Pipe.new(read_blocking: false, write_blocking: true) 19 | else 20 | STDOUT 21 | end 22 | io[:err] = if test? 23 | Ios::Pipe.new(read_blocking: false, write_blocking: true) 24 | else 25 | STDERR 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /src/lib.cr: -------------------------------------------------------------------------------- 1 | require "optarg" 2 | require "string_inflection/kebab" 3 | require "string_inflection/snake" 4 | require "./version" 5 | require "./lib/macros" 6 | require "./lib/command_base" 7 | require "./lib/*" 8 | -------------------------------------------------------------------------------- /src/lib/command.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # The base of command classes. 3 | abstract class Command < CommandBase 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/lib/command_base.cr: -------------------------------------------------------------------------------- 1 | require "./command_base/macros" 2 | 3 | module Cli 4 | # The base of command classes. 5 | # 6 | # Your application should not directly inherit this class. Instead, use `Command` or `Supercommand`. 7 | abstract class CommandBase 8 | Callback.enable 9 | 10 | macro inherited 11 | {% if @type.superclass != ::Cli::CommandBase %} 12 | {% 13 | type_id = @type.name.split("(")[0].split("::").join("_").id 14 | last_name = @type.name.split("::").last.id 15 | snake_type_id = type_id.underscore 16 | %} 17 | {% if @type.superclass == ::Cli::Command %} 18 | {% 19 | is_command_root = true 20 | is_supercommand_root = false 21 | is_supercommand = false 22 | super_option_data = "Cli::OptionModel".id 23 | %} 24 | {% elsif @type.superclass == ::Cli::Supercommand %} 25 | {% 26 | is_command_root = false 27 | is_supercommand_root = true 28 | is_supercommand = true 29 | super_option_data = "Cli::OptionModel".id 30 | %} 31 | {% else %} 32 | {% 33 | is_command_root = false 34 | is_supercommand_root = false 35 | is_supercommand = @type < ::Cli::Supercommand 36 | super_option_data = "#{@type.superclass}::Options".id 37 | %} 38 | {% end %} 39 | 40 | {% if is_command_root || is_supercommand_root %} 41 | define_callback_group :initialize 42 | define_callback_group :exit, proc_type: Proc(::Cli::Exit, Nil) 43 | {% else %} 44 | inherit_callback_group :initialize 45 | inherit_callback_group :exit, proc_type: Proc(::Cli::Exit, Nil) 46 | {% end %} 47 | 48 | # The dedicated Cli::OptionModel subclass for the `{{last_name}}` class. 49 | # 50 | # This class is automatically defined by the Crystal CLI library. 51 | class Options < ::{{super_option_data}} 52 | end 53 | 54 | # :nodoc: 55 | macro __define_run(klass) 56 | \{% 57 | klass = klass.resolve if klass.class_name == "Path" 58 | \%} 59 | # :nodoc: 60 | class ::Cli::CommandClass 61 | {% unless @type.abstract? %} 62 | # :nodoc: 63 | def {{snake_type_id}}__run(argv) 64 | {{snake_type_id}}__run(nil, argv) 65 | end 66 | 67 | # :nodoc: 68 | def {{snake_type_id}}__run(argv, &block : ::\{{klass}} ->) 69 | {{snake_type_id}}__run(nil, argv, &block) 70 | end 71 | 72 | # :nodoc: 73 | def {{snake_type_id}}__run(previous, argv) 74 | {{snake_type_id}}__run(previous, argv) {} 75 | end 76 | 77 | # :nodoc: 78 | def {{snake_type_id}}__run(previous, argv, &block : ::\{{klass}} ->) 79 | cmd = ::\{{klass}}.new(previous, argv) 80 | rescue_exit(cmd) do 81 | rescue_error(cmd) do 82 | begin 83 | cmd.__option_data.__parse 84 | result = cmd.run 85 | cmd.io.close_writer unless previous 86 | yield cmd 87 | result 88 | ensure 89 | cmd.io.close_writer unless previous 90 | end 91 | end 92 | end 93 | end 94 | 95 | # :nodoc: 96 | def rescue_exit(cmd) 97 | if cmd.__previous? 98 | yield 99 | else 100 | begin 101 | result = yield 102 | cmd.run_callbacks_for_exit(::Cli::Exit.new) {} 103 | result 104 | rescue ex : ::Cli::Exit 105 | if ::Cli.test? 106 | cmd.run_callbacks_for_exit(ex) {} 107 | ex 108 | else 109 | cmd.run_callbacks_for_exit ex do 110 | ex.stdout.puts ex.message if ex.message 111 | end 112 | exit ex.exit_code 113 | end 114 | end 115 | end 116 | end 117 | 118 | @@runners[\{{klass.name.stringify}}] = Runner.new do |previous, args| 119 | ::\{{klass}}.__klass.{{snake_type_id}}__run(previous, args) 120 | end 121 | {% end %} 122 | end 123 | 124 | # Run the command. 125 | # 126 | # This method is automatically defined by the Crystal CLI library. 127 | def self.run(argv : Array(String) = \\%w(), &block : ::\{{klass}} ->) 128 | __klass.{{snake_type_id}}__run(argv, &block) 129 | end 130 | end 131 | __define_run ::{{@type}} 132 | 133 | @@__klass = ::Cli::CommandClass.new( 134 | supercommand: __get_supercommand_class, 135 | inherited_class: {{ is_command_root || is_supercommand_root ? nil : "::#{@type.superclass}.__klass".id }}, 136 | class_name: {{@type.name.stringify}}, 137 | name: ::StringInflection.kebab(::{{@type}}.name.split("::").last), 138 | is_supercommand: {{is_supercommand}}, 139 | abstract: {{@type.abstract?}}, 140 | options: Options.__klass 141 | ) 142 | 143 | # :nodoc: 144 | def self.__klass; @@__klass; end 145 | 146 | # :nodoc: 147 | def __klass; @@__klass; end 148 | 149 | {% unless @type.abstract? %} 150 | if @@__klass.supercommand? 151 | @@__klass.supercommand << @@__klass 152 | end 153 | 154 | # Run the command. 155 | # 156 | # This method is automatically defined by the Crystal CLI library. 157 | def self.run 158 | run(\%w()) 159 | end 160 | 161 | # Run the command. 162 | # 163 | # This method is automatically defined by the Crystal CLI library. 164 | def self.run(argv : Array(String)) 165 | __klass.{{snake_type_id}}__run(argv) 166 | end 167 | 168 | # Run the command. 169 | # 170 | # This method is automatically defined by the Crystal CLI library. 171 | def self.run(previous : ::Cli::CommandBase, argv : Array(String) = \%w()) 172 | __klass.{{snake_type_id}}__run(previous, argv) 173 | end 174 | {% end %} 175 | 176 | class Options 177 | # :nodoc: 178 | def self.__cli_command 179 | ::{{@type}} 180 | end 181 | 182 | # :nodoc: 183 | def __cli_command 184 | @__cli_command.as(::{{@type}}) 185 | end 186 | 187 | {% if is_supercommand_root %} 188 | __klass.definitions << ::{{@type}}.__klass.subcommand_option_model_definition 189 | {% end %} 190 | end 191 | 192 | # Configures help message attributes for the `{{last_name}}` class. 193 | # 194 | # This class is automatically defined by the Crystal CLI library. 195 | class Help 196 | # Sets the caption. 197 | def self.caption(s : String) 198 | ::{{@type}}.__klass.caption = s 199 | end 200 | 201 | # Sets the title. 202 | def self.title(s : String) 203 | ::{{@type}}.__klass.title = s 204 | end 205 | 206 | # Sets the header. 207 | def self.header(s : String) 208 | ::{{@type}}.__klass.header = s 209 | end 210 | 211 | # Sets the footer. 212 | def self.footer(s : String) 213 | ::{{@type}}.__klass.footer = s 214 | end 215 | 216 | # Sets the string for unparsed arguments. 217 | def self.unparsed_args(s) 218 | ::{{@type}}.__klass.unparsed_args = s 219 | end 220 | end 221 | 222 | # :nodoc: 223 | def __option_data 224 | (@__option_data.var ||= Options.new(@__argv, self)).as(Options) 225 | end 226 | {% end %} 227 | end 228 | 229 | # :nodoc: 230 | getter? __previous : ::Cli::CommandBase? 231 | 232 | @__argv : Array(String) 233 | 234 | # :nodoc: 235 | def initialize(argv) 236 | initialize nil, argv 237 | end 238 | 239 | # :nodoc: 240 | def initialize(@__previous, @__argv) 241 | run_callbacks_for_initialize {} 242 | end 243 | 244 | @__option_data = Util::Var(Optarg::Model).new 245 | 246 | # Returns option and argument values (an `OptionModel` instance). 247 | # 248 | # This method is the same as `#args`. 249 | def options; __option_data; end 250 | 251 | # Returns option and argument values (an `OptionModel` instance). 252 | # 253 | # This method is the same as `#options`. 254 | def args; __option_data; end 255 | 256 | # Returns an array of nameless argument values. 257 | # 258 | # This method is a short form of `#args`.nameless_args. 259 | def nameless_args : Array(String) 260 | __option_data.nameless_args 261 | end 262 | 263 | # Returns an array of unparsed argument values. 264 | # 265 | # This method is a short form of `#args`.unparsed_args. 266 | def unparsed_args : Array(String) 267 | __option_data.unparsed_args 268 | end 269 | 270 | # Returns the command version. 271 | def version : String 272 | __klass.version 273 | end 274 | 275 | # Returns the command version. 276 | # 277 | # Returns nil, if no version is set. 278 | def version? : String? 279 | __klass.version? 280 | end 281 | 282 | # Sets the command name. 283 | def self.command_name(value : String) 284 | __klass.name = value 285 | end 286 | 287 | # Disables printing a help message when a parsing error occurs. 288 | def self.disable_help_on_parsing_error! 289 | __klass.disable_help_on_parsing_error! 290 | end 291 | 292 | # Sets the command version. 293 | def self.version(value : String) 294 | __klass.version = value 295 | end 296 | 297 | # Prints a help message and exits the command. 298 | def help!(message : String? = nil, error : Bool? = nil, code : Int32? = nil, indent = 2) 299 | error = !message.nil? if error.nil? 300 | exit! message, error, code, true, indent 301 | end 302 | 303 | # Exits the command. 304 | def exit!(message : String? = nil, error : Bool = false, code : Int32? = nil, help = false, indent = 2) 305 | a = %w() 306 | a << message if message 307 | if help 308 | a << __klass.new_help(indent: indent).text 309 | end 310 | message = a.join("\n\n") unless a.empty? 311 | code ||= error ? 1 : 0 312 | raise ::Cli::Exit.new(message, code) 313 | end 314 | 315 | # Exits the command with an error status. 316 | def error!(message : String? = nil, code : Int32? = nil, help : Bool = false, indent = 2) 317 | exit! message, true, code, help, indent 318 | end 319 | 320 | # Prints a version string and exits the command. 321 | def version! 322 | exit! version 323 | end 324 | 325 | # Runs the command. 326 | # 327 | # This method is an entrypoint for running a command. 328 | # 329 | # Subclasses must override this method. 330 | def run 331 | raise "Not implemented." 332 | end 333 | 334 | # Generates a bash completion script. 335 | def self.generate_bash_completion 336 | __klass.generate_bash_completion 337 | end 338 | 339 | # Generates a zsh completion script. 340 | def self.generate_zsh_completion(functional : Bool = true) 341 | __klass.generate_zsh_completion(functional) 342 | end 343 | 344 | @io : IoHash? 345 | # Returns a named IO container. 346 | def io 347 | @io ||= if prev = @__previous 348 | prev.io 349 | else 350 | Cli.new_default_io 351 | end 352 | end 353 | 354 | # Invokes the :out IO's puts method. 355 | def puts(*args) 356 | io[:out].puts *args 357 | end 358 | 359 | # Invokes the :out IO'S print method. 360 | def print(*args) 361 | io[:out].print *args 362 | end 363 | 364 | # Returns the :out IO. 365 | def out 366 | io[:out] 367 | end 368 | 369 | # Returns the :err IO. 370 | def err 371 | io[:err] 372 | end 373 | 374 | # :nodoc: 375 | def self.replacer_command 376 | end 377 | 378 | # Replaces this command with the *klass* command. 379 | macro replacer_command(klass) 380 | __define_run {{klass}} 381 | @@__klass.completable = false 382 | end 383 | end 384 | end 385 | -------------------------------------------------------------------------------- /src/lib/command_base/macros.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | abstract class CommandBase 3 | # :nodoc: 4 | macro __get_supercommand_class(type = nil) 5 | {% 6 | names = @type.name.split("::").map{|i| i.id} 7 | %} 8 | {% if names.size >= 3 %} 9 | __get_supercommand_class2 ::{{names[0..-2].join("::").id}}, ::{{names[0..-3].join("::").id}} 10 | {% elsif names.size >= 2 %} 11 | __get_supercommand_class2 ::{{names[0..-2].join("::").id}} 12 | {% else %} 13 | nil 14 | {% end %} 15 | end 16 | 17 | # :nodoc: 18 | macro __get_supercommand_class2(type1, type2 = nil) 19 | {% if type1.resolve < ::Cli::Supercommand %} 20 | {{type1}}.__klass 21 | {% elsif type2 && type2.resolve < ::Cli::Supercommand %} 22 | {{type2}}.__klass 23 | {% else %} 24 | nil 25 | {% end %} 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /src/lib/command_class.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # :nodoc: 3 | class CommandClass 4 | alias Runner = Proc(CommandBase, Array(String), Nil) 5 | @@runners = {} of String => Runner 6 | 7 | getter! class_name : String? 8 | getter name = "" 9 | getter! inherited_class : CommandClass? 10 | getter! supercommand : CommandClass? 11 | getter? helps_on_parsing_error = true 12 | getter subcommands = {} of String => CommandClass 13 | property! default_subcommand_name : String? 14 | setter version : String? 15 | property! caption : String? 16 | property! title : String? 17 | property! header : String? 18 | property! footer : String? 19 | property! unparsed_args : String? 20 | getter! options : Optarg::ModelClass? 21 | property? completable = true 22 | 23 | def initialize(@supercommand, @inherited_class, @class_name : String, name, @abstract : Bool, @is_supercommand : Bool, @options : Optarg::ModelClass) 24 | self.name = name 25 | end 26 | 27 | def name=(v) 28 | options.name = v 29 | @name = v 30 | end 31 | 32 | @abstract : Bool? 33 | def abstract? 34 | @abstract.not_nil! 35 | end 36 | 37 | @is_supercommand : Bool? 38 | def is_supercommand? 39 | @is_supercommand.not_nil! 40 | end 41 | 42 | def help_on_parsing_error! 43 | @helps_on_parsing_error = true 44 | end 45 | 46 | def disable_help_on_parsing_error! 47 | @helps_on_parsing_error = false 48 | end 49 | 50 | @subcommand_option_model_definition : OptionModelDefinitions::Subcommand? 51 | def subcommand_option_model_definition 52 | @subcommand_option_model_definition ||= OptionModelDefinitions::Subcommand.new(self) 53 | end 54 | 55 | @snake_name : String? 56 | def snake_name 57 | @snake_name ||= StringInflection.snake(@name) 58 | end 59 | 60 | # def initialize 61 | # inherit 62 | # end 63 | # 64 | # def inherit 65 | # if inherited_class? 66 | # inherited_class.subcommand_values.each do |cmd| 67 | # self << cmd 68 | # end 69 | # @default_subcommand_name = inherited_class.default_subcommand_name? 70 | # end 71 | # end 72 | 73 | def <<(subcommand : CommandClass) 74 | subcommands[subcommand.name] = subcommand 75 | end 76 | 77 | def version? 78 | @version 79 | end 80 | 81 | def version 82 | @version ||= begin 83 | if v = @version 84 | v 85 | elsif sup = @supercommand 86 | sup.version 87 | else 88 | raise "No version." 89 | end 90 | end 91 | end 92 | 93 | @global_name : String? 94 | def global_name 95 | @global_name ||= if sup = @supercommand 96 | "#{sup.global_name} #{name}" 97 | else 98 | @name 99 | end 100 | end 101 | 102 | def run(previous, args) 103 | @@runners[class_name].call(previous, args) 104 | end 105 | 106 | def resolve_subcommand(name) 107 | name ||= default_subcommand_name? 108 | subcommands[name]? 109 | end 110 | 111 | def generate_bash_completion 112 | g = options.bash_completion.new_generator("_#{snake_name}") 113 | <<-EOS 114 | #{g.result} 115 | 116 | complete -F #{g.entry_point} #{name} 117 | EOS 118 | end 119 | 120 | def generate_zsh_completion(functional) 121 | g = options.zsh_completion.new_generator("_#{snake_name}") 122 | if functional 123 | <<-EOS 124 | #compdef #{name} 125 | 126 | #{g.result} 127 | EOS 128 | else 129 | <<-EOS 130 | #{g.result} 131 | 132 | compdef #{g.entry_point} #{name} 133 | EOS 134 | end 135 | end 136 | 137 | def define_subcommand_alias(name, real) 138 | self << Alias.new(self, name, real) 139 | end 140 | 141 | def rescue_error(command) 142 | yield 143 | rescue ex : ::Optarg::ParsingError 144 | command.exit! "Parsing Error: #{ex.message}", error: true, help: helps_on_parsing_error? 145 | end 146 | 147 | @default_title : String? 148 | def default_title 149 | @default_title ||= begin 150 | a = %w() 151 | a << global_name 152 | unless options.definitions.options.empty? 153 | required = options.definitions.value_options.any? do |kv| 154 | kv[1].value_required? 155 | end 156 | a << (required ? "OPTIONS" : "[OPTIONS]") 157 | end 158 | options.definitions.arguments.each do |kv| 159 | if df = kv[1].as?(::Optarg::Definitions::StringArrayArgument) 160 | min = df.minimum_length_of_array 161 | if min > 0 162 | (1..min).each do |n| 163 | a << "#{df.metadata.display_name}#{n}" 164 | end 165 | end 166 | vargs = ((min + 1)..(min + 2)).map do |n| 167 | "#{df.metadata.display_name}#{n}" 168 | end 169 | a << "[" + vargs.join(" ") + "...]" 170 | elsif df = kv[1].as?(::Optarg::Definitions::StringArgument) 171 | required = df.value_required? 172 | a << (required ? df.metadata.display_name : "[#{df.metadata.display_name}]") 173 | end 174 | end 175 | if unparsed_args? 176 | a << unparsed_args 177 | end 178 | a.join(" ") 179 | end 180 | end 181 | 182 | def new_help(indent) 183 | if @is_supercommand 184 | Helps::Supercommand.new(self, indent: indent) 185 | else 186 | Helps::Command.new(self, indent: indent) 187 | end 188 | end 189 | end 190 | end 191 | 192 | require "./command_class/*" 193 | -------------------------------------------------------------------------------- /src/lib/command_class/alias.cr: -------------------------------------------------------------------------------- 1 | class Cli::CommandClass 2 | class Alias < CommandClass 3 | getter real_name : String 4 | 5 | def initialize(@supercommand, @name, @real_name) 6 | end 7 | 8 | @real_command : CommandClass? 9 | def real_command 10 | @real_command ||= supercommand.subcommands[@real_name] 11 | end 12 | 13 | def inherited_class? 14 | real_command.inherited_class? 15 | end 16 | 17 | def abstract? 18 | real_command.abstract? 19 | end 20 | 21 | def options 22 | real_command.options 23 | end 24 | 25 | def command 26 | real_command.command 27 | end 28 | 29 | def help 30 | real_command.help 31 | end 32 | 33 | def run(previous, args) 34 | case cmd = real_command 35 | when Alias 36 | else 37 | cmd.run previous, args 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /src/lib/exit.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # Contains information about exit. 3 | class Exit < Exception 4 | # Returns an exit code. 5 | getter exit_code : Int32 6 | 7 | # Tests if the exit code is zero. 8 | def success? 9 | @exit_code == 0 10 | end 11 | 12 | # Tests if the exit code is non-zero. 13 | def error? 14 | !success? 15 | end 16 | 17 | # :nodoc: 18 | def stdout 19 | success? ? STDOUT : STDERR 20 | end 21 | 22 | # :nodoc: 23 | def initialize(message = nil, @exit_code = 0) 24 | super message 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /src/lib/helps.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module Cli::Helps 3 | end 4 | 5 | require "./helps/base" 6 | require "./helps/*" 7 | -------------------------------------------------------------------------------- /src/lib/helps/base.cr: -------------------------------------------------------------------------------- 1 | module Cli::Helps 2 | abstract class Base 3 | TYPES = { 4 | :argument => :argument, 5 | :array => :option, 6 | :bool => :option, 7 | :string => :option, 8 | :string_array => :option, 9 | :handler => :handler 10 | } 11 | 12 | alias Description = {head: ::String, body: ::Array(::String)} 13 | 14 | @command : CommandClass 15 | @indent : ::Int32 16 | @definition_descriptions : {option: ::Array(Description), argument: ::Array(Description), handler: ::Array(Description)}? 17 | @arguments : ::String | ::Bool | ::Nil 18 | @options : ::String | ::Bool | ::Nil 19 | @text : ::String? 20 | 21 | def initialize(@command, indent = nil) 22 | @indent = indent || 2 23 | end 24 | 25 | def definition_descriptions 26 | @definition_descriptions ||= begin 27 | { 28 | argument: descriptions_for(@command.options.definitions.arguments), 29 | option: descriptions_for(@command.options.definitions.value_options), 30 | handler: descriptions_for(@command.options.definitions.handlers), 31 | } 32 | end 33 | end 34 | 35 | def descriptions_for(dfs) 36 | a = [] of Description 37 | dfs.each do |kv| 38 | df = kv[1] 39 | head = names_of(df) 40 | varname = variable_name_of(df) 41 | head += " #{varname}" if varname 42 | array_size = array_size_of(df) 43 | head += " (#{array_size})" if array_size 44 | body = %w() 45 | desc = description_of(df) 46 | default = default_of(df) 47 | inclusion = inclusion_of(df) 48 | body += desc.split("\n") if desc 49 | body += inclusion.split("\n") if inclusion 50 | body += default.split("\n") if default 51 | a << {head: head, body: body} 52 | end 53 | a 54 | end 55 | 56 | def names_of(df) 57 | case df 58 | when Optarg::DefinitionMixins::Argument 59 | df.metadata.display_name 60 | else 61 | df.names.join(", ") 62 | end 63 | end 64 | 65 | def variable_name_of(df) 66 | md = df.metadata 67 | case df 68 | when Optarg::Definitions::StringOption, Optarg::Definitions::StringArrayOption 69 | if md.responds_to?(:variable_name) 70 | md.variable_name 71 | end 72 | end 73 | end 74 | 75 | def description_of(df) 76 | case df 77 | when Optarg::Definitions::NotOption 78 | "disable #{df.bool.key}" 79 | else 80 | md = df.metadata 81 | if md.responds_to?(:description) 82 | md.description 83 | end 84 | end 85 | end 86 | 87 | def default_of(df) 88 | case df 89 | when Optarg::DefinitionMixins::Value 90 | case v = df.default_value.get? 91 | when String 92 | "(default: #{v})" 93 | when Array(String) 94 | "(default: #{v.join(", ")})" 95 | when Bool 96 | case df 97 | when Optarg::Definitions::BoolOption 98 | "(enabled as default)" if v 99 | when Optarg::Definitions::NotOption 100 | "(disabled as default)" if v 101 | end 102 | end 103 | end 104 | end 105 | 106 | def array_size_of(df) 107 | case df 108 | when Optarg::DefinitionMixins::ArrayValue 109 | min = df.minimum_length_of_array 110 | min > 0 ? "at least #{min}" : "multiple" 111 | end 112 | end 113 | 114 | def inclusion_of(df) 115 | case df 116 | when Optarg::Definitions::StringOption 117 | if incl = df.validations.find{|i| i.is_a?(::Optarg::Definitions::StringOption::Validations::Inclusion)} 118 | inclusion_of2(incl.as(::Optarg::Definitions::StringOption::Validations::Inclusion)) 119 | end 120 | when Optarg::Definitions::StringArgument 121 | if incl = df.validations.find{|i| i.is_a?(::Optarg::Definitions::StringArgument::Validations::Inclusion)} 122 | inclusion_of2(incl.as(::Optarg::Definitions::StringArgument::Validations::Inclusion)) 123 | end 124 | end 125 | end 126 | 127 | def inclusion_of2(incl) 128 | a = incl.values.map do |v| 129 | desc = if md = v.metadata.as?(OptionValueMetadata(String)) 130 | md.description 131 | end 132 | Description.new(head: v.metadata.string, body: desc ? desc.split("\n") : %w()) 133 | end 134 | indent = " " * @indent 135 | "one of:\n" + join_description2(a).to_s 136 | end 137 | 138 | def arguments 139 | return nil if @arguments == false 140 | if lines = join_description(:argument) 141 | @arguments = ["Arguments:", lines].join("\n") 142 | else 143 | @arguments = false 144 | nil 145 | end 146 | end 147 | 148 | def options 149 | return nil if @options == false 150 | if lines = join_description(:option, :handler) 151 | @options = ["Options:", lines].join("\n") 152 | else 153 | @options = false 154 | nil 155 | end 156 | end 157 | 158 | def join_description(*types) 159 | descs = [] of Description 160 | types.each{|t| descs += self.class.sort_description(definition_descriptions[t])} 161 | return nil if descs.empty? 162 | join_description2 descs 163 | end 164 | 165 | def join_description2(descs) 166 | lines = %w() 167 | left_width = descs.map{|i| i[:head].size}.max + @indent 168 | indent = " " * @indent 169 | descs.each do |desc| 170 | entry = %w() 171 | if desc[:body].empty? 172 | entry << "#{indent}#{desc[:head]}" 173 | else 174 | entry << "#{indent}#{desc[:head].ljust(left_width)}#{desc[:body][0]}" 175 | end 176 | if desc[:body].size >= 2 177 | desc[:body][1..-1].each do |i| 178 | entry << "#{indent}#{" " * left_width}#{i}" 179 | end 180 | end 181 | lines << entry.join("\n") 182 | end 183 | lines.empty? ? nil : lines.join("\n") 184 | end 185 | 186 | def text 187 | @text ||= render 188 | end 189 | 190 | def self.sort_description(description) 191 | description.sort do |a, b| 192 | a = normalize_definition_name(a[:head]) 193 | b = normalize_definition_name(b[:head]) 194 | n = a.downcase <=> b.downcase 195 | n == 0 ? reverse_case(a) <=> reverse_case(b) : n 196 | end 197 | end 198 | 199 | def self.normalize_definition_name(name) 200 | name.split(/\=/)[0].sub(/^-*/, "") 201 | end 202 | 203 | def self.reverse_case(s) 204 | s.split("").map{|i| i =~ /[A-Z]/ ? i.downcase : i.upcase}.join("") 205 | end 206 | end 207 | end 208 | -------------------------------------------------------------------------------- /src/lib/helps/command.cr: -------------------------------------------------------------------------------- 1 | module Cli::Helps 2 | class Command < Base 3 | def render 4 | a = %w() 5 | s = nil 6 | a << (@command.title? || @command.default_title) 7 | a << s if s = @command.header? 8 | a << s if s = arguments 9 | a << s if s = options 10 | a << s if s = @command.footer? 11 | a.join("\n\n") 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/lib/helps/supercommand.cr: -------------------------------------------------------------------------------- 1 | module Cli::Helps 2 | class Supercommand < Base 3 | @subcommands_lines : ::Array(::Tuple(::String, ::String?))? 4 | def subcommands_lines 5 | @subcommands_lines ||= begin 6 | a = [] of ::Tuple(::String, ::String?) 7 | @command.subcommands.keys.sort.each do |name| 8 | subcommand = @command.subcommands[name] 9 | name_column = name 10 | name_column += " (default)" if @command.default_subcommand_name? == name 11 | caption_column = if al = subcommand.as?(CommandClass::Alias) 12 | "alias for #{al.real_name}" 13 | else 14 | subcommand.caption? 15 | end 16 | a << {name_column, caption_column} 17 | end 18 | a 19 | end 20 | end 21 | 22 | @subcommands : (::String | ::Bool)? 23 | def subcommands 24 | return nil if @subcommands == false 25 | @subcommands = if subcommands_lines.empty? 26 | @subcommands = false 27 | return nil 28 | else 29 | lines = %w() 30 | lines << "Subcommands:" 31 | entries = %w() 32 | left_width = subcommands_lines.map{|i| i[0].size}.max + @indent 33 | indent = " " * @indent 34 | subcommands_lines.each do |line| 35 | l = if line[1] 36 | "#{indent}#{line[0].ljust(left_width)}#{line[1]}" 37 | else 38 | "#{indent}#{line[0]}" 39 | end 40 | lines << l 41 | end 42 | lines.join("\n") 43 | end 44 | end 45 | 46 | def render 47 | a = %w() 48 | s = nil 49 | a << (@command.title? || @command.default_title) 50 | a << s if s = @command.header? 51 | a << s if s = subcommands 52 | a << s if s = options 53 | a << s if s = @command.footer? 54 | a.join("\n\n") 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /src/lib/io_hash.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # Contains named IOs. 3 | class IoHash 4 | @hash = {} of String => IO 5 | 6 | alias NameArg = String | Symbol 7 | 8 | # Sets a named IO. 9 | def []=(name : NameArg, writer : IO) 10 | @hash[name.to_s] = writer 11 | end 12 | 13 | # Gets a named IO. 14 | def [](name : NameArg) 15 | @hash[name.to_s] 16 | end 17 | 18 | # :nodoc: 19 | def remove(name : NameArg) 20 | @hash.delete name.to_s 21 | end 22 | 23 | # :nodoc: 24 | def renew 25 | IoHash.new.tap do |o| 26 | @hash.each do |k, v| 27 | o[k] = renew_io(v) 28 | end 29 | end 30 | end 31 | 32 | # :nodoc: 33 | def renew_io(io) 34 | case io 35 | when Ios::Pipe 36 | io.renew 37 | else 38 | io 39 | end 40 | end 41 | 42 | # :nodoc: 43 | def each 44 | @hash.each do |kv| 45 | yield kv[1] 46 | end 47 | end 48 | 49 | # :nodoc: 50 | def close_writer 51 | each do |io| 52 | case io 53 | when Ios::Pipe 54 | io.close_writer 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /src/lib/ios.cr: -------------------------------------------------------------------------------- 1 | require "./ios/*" 2 | -------------------------------------------------------------------------------- /src/lib/ios/pipe.cr: -------------------------------------------------------------------------------- 1 | module Cli::Ios 2 | class Pipe < IO 3 | # :nodoc: 4 | class Closed < Exception 5 | def initialize(io) 6 | super "Piped #{io} already closed." 7 | end 8 | end 9 | 10 | @reader : IO::FileDescriptor? 11 | @writer : IO::FileDescriptor? 12 | @read_blocking : Bool 13 | @write_blocking : Bool 14 | 15 | # :nodoc: 16 | def initialize(@read_blocking = false, @write_blocking = false) 17 | @reader, @writer = IO.pipe(read_blocking: @read_blocking, write_blocking: @write_blocking) 18 | end 19 | 20 | # Implements `IO#read`. 21 | def read(slice : Slice(UInt8)) 22 | if io = @reader 23 | io.read slice 24 | else 25 | raise Closed.new("reader") 26 | end 27 | end 28 | 29 | # Implements `IO#write`. 30 | def write(slice : Slice(UInt8)) 31 | if io = @writer 32 | io.write slice 33 | else 34 | raise Closed.new("writer") 35 | end 36 | end 37 | 38 | # :nodoc: 39 | def close 40 | @reader.try(&.close) 41 | @reader = nil 42 | close_writer 43 | end 44 | 45 | # :nodoc: 46 | def close_writer 47 | @writer.try(&.close) 48 | @writer = nil 49 | end 50 | 51 | # :nodoc: 52 | def finalize 53 | close 54 | end 55 | 56 | # :nodoc: 57 | def renew 58 | Pipe.new(@read_blocking, @write_blocking) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /src/lib/macros.cr: -------------------------------------------------------------------------------- 1 | require "./macros/*" 2 | -------------------------------------------------------------------------------- /src/lib/macros/__any_item_of.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # :nodoc: 3 | macro __any_item_of(df, params) 4 | {% if params.class_name == "TupleLiteral" %} 5 | {% 6 | type = "#{df.id}::Typed::ElementType".id 7 | typed_value = "#{df.id}::Typed::ElementValue".id 8 | %} 9 | ([ 10 | {% for e, i in params %} 11 | ::{{typed_value}}.new( 12 | {{e[0]}}, 13 | {% if e[1].class_name == "NamedTupleLiteral" %} 14 | metadata: ::Cli::OptionValueMetadata(::{{type}}).new(**{{e[1]}}) 15 | {% else %} 16 | metadata: ::Cli::OptionValueMetadata(::{{type}}).new(desc: {{e[1]}}) 17 | {% end %} 18 | ), 19 | {% end %} 20 | ]) 21 | {% else %} 22 | {{params}} 23 | {% end %} 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /src/lib/macros/__any_of.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # :nodoc: 3 | macro __any_of(df, params) 4 | {% if params.class_name == "TupleLiteral" %} 5 | {% 6 | type = "#{df.id}::Typed::Type".id 7 | typed_value = "#{df.id}::Typed::Value".id 8 | %} 9 | ([ 10 | {% for e, i in params %} 11 | ::{{typed_value}}.new( 12 | {{e[0]}}, 13 | {% if e[1].class_name == "NamedTupleLiteral" %} 14 | metadata: ::Cli::OptionValueMetadata(::{{type}}).new(**{{e[1]}}) 15 | {% else %} 16 | metadata: ::Cli::OptionValueMetadata(::{{type}}).new(desc: {{e[1]}}) 17 | {% end %} 18 | ), 19 | {% end %} 20 | ]) 21 | {% else %} 22 | {{params}} 23 | {% end %} 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /src/lib/option_metadata.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # :nodoc: 3 | class OptionMetadata < Optarg::Metadata 4 | getter description : ::String? 5 | getter variable_name : ::String? 6 | 7 | def initialize(@description = nil, @variable_name = nil) 8 | end 9 | 10 | def display_name 11 | if definition.is_a?(Optarg::DefinitionMixins::Argument) 12 | definition.key.upcase 13 | else 14 | super 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/lib/option_model.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # Inherits the Optarg::Model class for parsing command-line arguments. 3 | abstract class OptionModel < ::Optarg::Model 4 | @__cli_command : CommandBase? 5 | 6 | # Returns a related command instance. 7 | def command; __cli_command; end 8 | 9 | # :nodoc: 10 | def initialize(argv, @__cli_command) 11 | initialize argv 12 | end 13 | end 14 | end 15 | 16 | require "./option_model/*" 17 | -------------------------------------------------------------------------------- /src/lib/option_model/dsl.cr: -------------------------------------------------------------------------------- 1 | class Cli::OptionModel 2 | # Defines a String option model item. 3 | macro string(names, stop = nil, default = nil, required = nil, desc = nil, var = nil, any_of = nil, complete = nil, &block) 4 | ::Optarg::Model.string( 5 | {{names}}, 6 | default: {{default}}, 7 | required: {{required}}, 8 | stop: {{stop}}, 9 | metadata: ::Cli::OptionMetadata.new(description: {{desc}}, variable_name: {{var}}), 10 | any_of: ::Cli.__any_of(::Optarg::Definitions::StringOption, {{any_of}}), 11 | complete: {{complete}} 12 | ) {{block}} 13 | end 14 | 15 | # Defines a Bool option model item. 16 | macro bool(names, stop = nil, default = nil, not = nil, desc = nil, &block) 17 | ::Optarg::Model.bool( 18 | {{names}}, 19 | stop: {{stop}}, 20 | default: {{default}}, 21 | not: {{not}}, 22 | metadata: ::Cli::OptionMetadata.new(description: {{desc}}) 23 | ) {{block}} 24 | end 25 | 26 | # Defines an Array(String) option model item. 27 | macro array(names, default = nil, min = nil, desc = nil, var = nil, any_item_of = nil, complete = nil, &block) 28 | ::Optarg::Model.array( 29 | {{names}}, 30 | default: {{default}}, 31 | min: {{min}}, 32 | metadata: ::Cli::OptionMetadata.new(description: {{desc}}, variable_name: {{var}}), 33 | any_item_of: ::Cli.__any_item_of(::Optarg::Definitions::StringArrayOption, {{any_item_of}}), 34 | complete: {{complete}} 35 | ) {{block}} 36 | end 37 | 38 | # Defines a String argument model item. 39 | macro arg(name, stop = nil, default = nil, required = nil, desc = nil, any_of = nil, complete = nil, &block) 40 | ::Optarg::Model.arg( 41 | {{name}}, 42 | stop: {{stop}}, 43 | default: {{default}}, 44 | required: {{required}}, 45 | metadata: ::Cli::OptionMetadata.new(description: {{desc}}), 46 | any_of: ::Cli.__any_of(::Optarg::Definitions::StringArgument, {{any_of}}), 47 | complete: {{complete}} 48 | ) {{block}} 49 | end 50 | 51 | # Defines an Array(String) argument model item. 52 | macro arg_array(names, default = nil, min = nil, desc = nil, var = nil, any_item_of = nil, complete = nil, &block) 53 | ::Optarg::Model.arg_array( 54 | {{names}}, 55 | default: {{default}}, 56 | min: {{min}}, 57 | metadata: ::Cli::OptionMetadata.new(description: {{desc}}, variable_name: {{var}}), 58 | any_item_of: ::Cli.__any_item_of(::Optarg::Definitions::StringArrayArgument, {{any_item_of}}), 59 | complete: {{complete}} 60 | ) {{block}} 61 | end 62 | 63 | # Defines a handler model item. 64 | macro on(names, desc = nil, &block) 65 | ::Optarg::Model.on({{names}}, metadata: ::Cli::OptionMetadata.new(description: {{desc}})) {{block}} 66 | end 67 | 68 | # Defines an unknown model item. 69 | macro unknown(desc = nil, &block) 70 | ::Optarg::Model.unknown(metadata: ::Cli::OptionMetadata.new(description: {{desc}})) {{block}} 71 | end 72 | 73 | # Defines a handler model item for printing a help message. 74 | macro help(names = nil, desc = nil) 75 | {% 76 | names = names || %w(-h --help) 77 | %} 78 | on({{names}}, desc: ({{desc}} || "show this help")) { __cli_command.help! } 79 | end 80 | 81 | # Defines a handler model item for printing a version string. 82 | macro version(names = nil, desc = nil) 83 | {% 84 | names = names || %w(-v --version) 85 | %} 86 | on({{names}}, desc: ({{desc}} || "show version")) { __cli_command.version! } 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /src/lib/option_model_definitions.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module Cli::OptionModelDefinitions 3 | end 4 | 5 | require "./option_model_definitions/*" 6 | -------------------------------------------------------------------------------- /src/lib/option_model_definitions/subcommand.cr: -------------------------------------------------------------------------------- 1 | module Cli::OptionModelDefinitions 2 | class Subcommand < Optarg::Definitions::StringArgument 3 | alias Model = OptionModel 4 | 5 | getter command_class : CommandClass 6 | 7 | def initialize(@command_class) 8 | super "subcommand", metadata: OptionMetadata.new, stop: true, required: true 9 | end 10 | 11 | def subclassify(model) 12 | if model.responds_to?(:__cli_command) 13 | Subcommand.new(model.__cli_command.__klass) 14 | else 15 | self 16 | end 17 | end 18 | 19 | def completion_words(gen) 20 | command_class.subcommands.select{|k,v| v.completable?}.map{|i| i[0]} 21 | end 22 | 23 | def completion_next_models_by_value(gen) 24 | ({} of String => Optarg::ModelClass).tap do |h| 25 | command_class.subcommands.each do |k, v| 26 | next unless v.completable? 27 | h[k] = v.options 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /src/lib/option_model_mixin.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # :nodoc: 3 | module OptionModelMixin 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/lib/option_value_metadata.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | # :nodoc: 3 | class OptionValueMetadata(T) < Optarg::ValueMetadata(T) 4 | getter description : ::String? 5 | 6 | def initialize(desc) 7 | @description = desc 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/lib/supercommand.cr: -------------------------------------------------------------------------------- 1 | require "./command_base" 2 | 3 | module Cli 4 | # The base of supercommand classes. 5 | abstract class Supercommand < CommandBase 6 | # Sets subcommand attributes. 7 | # 8 | # ### Parameters 9 | # * name (String) : a target subcommand name 10 | # * default (Bool) : if true, it makes the target subcommand a default subcommand. 11 | # * aliased (String) : makes the target subcommand an alias of the other subcommand that has the *aliased* name. 12 | macro command(name, default = false, aliased = nil) 13 | {% 14 | s = aliased || name 15 | a = s.strip.split(" ") 16 | a = a.map{|i| i.split("-").join("_").split("_").map{|j| j.capitalize}.join("")} 17 | %} 18 | 19 | {% if default %} 20 | __klass.default_subcommand_name = {{name}} 21 | __klass.subcommand_option_model_definition.unrequire_value! 22 | {% end %} 23 | 24 | {% if aliased %} 25 | __klass.define_subcommand_alias {{name}}, {{aliased}} 26 | {% end %} 27 | end 28 | 29 | # :nodoc: 30 | def run 31 | if subcommand = __klass.resolve_subcommand(__option_data[String]["subcommand"]?) 32 | subcommand.run(self, unparsed_args.dup) 33 | else 34 | help! 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /src/lib/util.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module Cli::Util 3 | end 4 | 5 | require "./util/*" 6 | -------------------------------------------------------------------------------- /src/lib/util/var.cr: -------------------------------------------------------------------------------- 1 | module Cli::Util 2 | struct Var(T) 3 | @var : T? 4 | 5 | def var 6 | @var 7 | end 8 | 9 | def var=(var) 10 | @var = var 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /src/spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec/*" 2 | -------------------------------------------------------------------------------- /src/spec/helper.cr: -------------------------------------------------------------------------------- 1 | module Cli::Spec 2 | # Extends modules for testing commands. 3 | module Helper 4 | macro included 5 | extend ::Cli::Spec::Helper 6 | end 7 | 8 | # Returns a new Crystal Spec expectation that asserts how a command exits. 9 | # 10 | # ### Parameters 11 | # 12 | # * *output* : An expected output. If nil, the test is omitted. 13 | # * *error* : An expected error output. If nil, the test is omitted. 14 | # * *code* : An expected exit code. If nil, the test is omitted. 15 | def exit_command(output : (String | Regex | Nil) = nil, error : (String | Regex | Nil) = nil, code : Int32? = nil) 16 | Expectation.new(output, error, code) 17 | end 18 | 19 | # :nodoc: 20 | class Expectation 21 | alias Message = String | Regex 22 | 23 | getter! output : Message? 24 | getter! error : Message? 25 | getter! code : Int32? 26 | 27 | def initialize(@output, @error, @code) 28 | end 29 | 30 | def match(actual) 31 | case actual 32 | when Exit 33 | match_output(actual) && match_error(actual) && match_code(actual) 34 | else 35 | raise "The actual value must be Cli::Exit but #{actual.class.name}." 36 | end 37 | end 38 | 39 | def match_output(actual) 40 | output?.nil? || match_string(actual_output(actual), output) 41 | end 42 | 43 | def match_error(actual) 44 | error?.nil? || match_string(actual_error(actual), error) 45 | end 46 | 47 | def match_string(actual, expected) 48 | case expected 49 | when Regex 50 | expected =~ actual 51 | else 52 | actual == expected 53 | end 54 | end 55 | 56 | def match_code(actual) 57 | code?.nil? || actual.exit_code == code 58 | end 59 | 60 | def failure_message(actual) 61 | case actual 62 | when Exit 63 | return failure_message_for_output(actual) unless match_output(actual) 64 | return failure_message_for_error(actual) unless match_error(actual) 65 | return failure_message_for_code(actual) unless match_code(actual) 66 | end 67 | raise "Internal error." 68 | end 69 | 70 | def negative_failure_message(actual) 71 | case actual 72 | when Exit 73 | return negative_failure_message_for_output(actual) if match_output(actual) 74 | return negative_failure_message_for_error(actual) if match_error(actual) 75 | return negative_failure_message_for_code(actual) if match_code(actual) 76 | end 77 | raise "Internal error." 78 | end 79 | 80 | def failure_message_for_output(actual) 81 | <<-EOS 82 | Unmatched output. 83 | expected:#{indent(output, 4)} 84 | got:#{indent(actual_output(actual), 4)} 85 | EOS 86 | end 87 | 88 | def failure_message_for_error(actual) 89 | <<-EOS 90 | Unmatched error output. 91 | expected:#{indent(error, 4)} 92 | got:#{indent(actual_error(actual), 4)} 93 | EOS 94 | end 95 | 96 | def failure_message_for_code(actual) 97 | "Unmatched exit code: #{code} is expected but got #{actual.exit_code}." 98 | end 99 | 100 | def negative_failure_message_for_output(actual) 101 | <<-EOS 102 | Matched output.#{indent(output, 2)} 103 | EOS 104 | end 105 | 106 | def negative_failure_message_for_error(actual) 107 | <<-EOS 108 | Matched error output.#{indent(error, 2)} 109 | EOS 110 | end 111 | 112 | def negative_failure_message_for_code(actual) 113 | "Matched exit code: #{code}" 114 | end 115 | 116 | def actual_output(actual) 117 | actual.success? ? actual.message : nil 118 | end 119 | 120 | def actual_error(actual) 121 | actual.error? ? actual.message : nil 122 | end 123 | 124 | def indent(s, spaces) 125 | indent = " " * spaces 126 | s = s.to_s 127 | return "" if s.empty? 128 | "\n" + s.split("\n").map{|i| "#{indent}#{i}"}.join("\n") 129 | end 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /src/version.cr: -------------------------------------------------------------------------------- 1 | module Cli 2 | VERSION = "0.7.0" 3 | end 4 | --------------------------------------------------------------------------------