├── .circleci └── config.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin ├── deploy_doc └── test ├── shard.yml ├── spec ├── classes │ ├── argument_value_container_spec.cr │ └── option_value_container_spec.cr ├── features │ ├── accessor_spec.cr │ ├── argument_array_spec.cr │ ├── arguments_spec.cr │ ├── array_spec.cr │ ├── boolean_spec.cr │ ├── concatenation_spec.cr │ ├── custom_initializer_spec.cr │ ├── default_value_spec.cr │ ├── handler_spec.cr │ ├── inheritance_spec.cr │ ├── minimum_length_of_array_spec.cr │ ├── named_arguments_spec.cr │ ├── negation_spec.cr │ ├── nilable_accessor_spec.cr │ ├── required_arguments_and_options_spec.cr │ ├── stop_spec.cr │ ├── synonyms_spec.cr │ └── termination_spec.cr ├── fix │ ├── inherit_definition_spec.cr │ └── value_type_is_not_nilable_spec.cr ├── internal │ ├── argument_display_name_spec.cr │ ├── array_default_is_empty_spec.cr │ ├── array_default_spec.cr │ ├── no_accessor_spec.cr │ ├── raise_unknown_option_if_concatenated_options_not_matched_spec.cr │ ├── raise_unknown_option_regardless_of_arg_spec.cr │ ├── raise_unsupported_concatenation_spec.cr │ ├── required_argument_error_preserves_object_spec.cr │ ├── snakify_completion_identifiers_spec.cr │ ├── stopper_spec.cr │ ├── terminator_does_not_affect_another_model_spec.cr │ └── unknown_spec.cr ├── internal_spec.cr ├── spec_helper.cr └── wiki │ ├── accessing_values_spec.cr │ ├── builtin_validations_spec.cr │ └── custom_validation_spec.cr └── src ├── lib ├── completion.cr ├── completion │ ├── function.cr │ ├── functions.cr │ ├── functions │ │ ├── act.cr │ │ ├── add.cr │ │ ├── any.cr │ │ ├── arg.cr │ │ ├── cur.cr │ │ ├── end.cr │ │ ├── found.cr │ │ ├── inc.cr │ │ ├── key.cr │ │ ├── keyerr.cr │ │ ├── len.cr │ │ ├── ls.cr │ │ ├── lskey.cr │ │ ├── main.cr │ │ ├── next.cr │ │ ├── tag.cr │ │ └── word.cr │ └── text_formatter.cr ├── completion_generators.cr ├── completion_generators │ ├── base.cr │ ├── bash.cr │ └── zsh.cr ├── definition_mixins.cr ├── definition_mixins │ ├── argument.cr │ ├── array_value.cr │ ├── array_value_argument.cr │ ├── array_value_option.cr │ ├── completion.cr │ ├── option.cr │ ├── scalar_value.cr │ ├── scalar_value_argument.cr │ ├── scalar_value_option.cr │ ├── value.cr │ ├── value_argument.cr │ ├── value_option.cr │ ├── visit.cr │ └── visit_concatenated.cr ├── definition_set.cr ├── definitions.cr ├── definitions │ ├── base.cr │ ├── bool_option.cr │ ├── handler.cr │ ├── not_option.cr │ ├── string_argument.cr │ ├── string_array_argument.cr │ ├── string_array_option.cr │ ├── string_option.cr │ ├── terminator.cr │ └── unknown.cr ├── exceptions.cr ├── metadata.cr ├── model.cr ├── model │ ├── dsl.cr │ ├── dsl │ │ ├── arg.cr │ │ ├── arg_array.cr │ │ ├── array.cr │ │ ├── bool.cr │ │ ├── handler.cr │ │ ├── string.cr │ │ ├── terminator.cr │ │ └── unknown.cr │ ├── macros.cr │ └── macros │ │ ├── handler.cr │ │ └── value.cr ├── model_class.cr ├── parser.cr ├── util.cr ├── util │ └── var.cr ├── validation_context.cr ├── value.cr ├── value_container.cr ├── value_hash.cr ├── value_metadata.cr ├── value_types.cr ├── value_types │ ├── base.cr │ ├── bool.cr │ ├── string.cr │ └── string_array.cr └── value_validation.cr ├── optarg.cr └── version.cr /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | workflows: 3 | version: 2 4 | test: 5 | jobs: 6 | - test_crystal_0_26_0 7 | # - test_crystal_nightly 8 | jobs: 9 | test_crystal_0_26_0: 10 | docker: 11 | - image: 'crystallang/crystal:0.26.0' 12 | working_directory: ~/repo 13 | steps: 14 | - checkout 15 | - restore_cache: 16 | keys: 17 | - 'v1-dependencies-{{ checksum "shard.yml" }}' 18 | - v1-dependencies- 19 | - run: shards 20 | - save_cache: 21 | paths: 22 | - .shards 23 | - lib 24 | key: 'v1-dependencies-{{ checksum "shard.yml" }}' 25 | - run: bin/test 26 | test_crystal_nightly: 27 | docker: 28 | - image: 'crystallang/crystal:nightly' 29 | working_directory: ~/repo 30 | steps: 31 | - checkout 32 | - restore_cache: 33 | keys: 34 | - 'v1-dependencies-{{ checksum "shard.yml" }}' 35 | - v1-dependencies- 36 | - run: shards 37 | - save_cache: 38 | paths: 39 | - .shards 40 | - lib 41 | key: 'v1-dependencies-{{ checksum "shard.yml" }}' 42 | - run: bin/test 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /.crystal/ 3 | /.shards/ 4 | /.crystal-version 5 | /lib 6 | 7 | # Libraries don't need dependency lock 8 | # Dependencies will be locked in application that uses them 9 | /shard.lock 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog :) 3 | 4 | ## 0.6.0 5 | 6 | * Crystal 0.26.0! 7 | * #2 Use Hash composition over inheritance in ValueHash, by @Sija :) 8 | -------------------------------------------------------------------------------- /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 | # optarg 2 | 3 | Yet another Crystal library for parsing command-line options and arguments. 4 | 5 | optarg is good enough for parsing options. However there's no feature for formatting help, subcommands... etc. If you prefer a more feature-rich library, try [cli](https://github.com/mosop/cli). 6 | 7 | [![CircleCI](https://circleci.com/gh/mosop/optarg.svg?style=shield)](https://circleci.com/gh/mosop/optarg) 8 | 9 | ## Installation 10 | 11 | Add this to your application's `shard.yml`: 12 | 13 | ```yaml 14 | dependencies: 15 | optarg: 16 | github: mosop/optarg 17 | ``` 18 | 19 | 20 | 21 | ## Code Samples 22 | 23 | ### Accessor 24 | 25 | ```crystal 26 | class Model < Optarg::Model 27 | string "--foo" 28 | end 29 | 30 | result = Model.parse(%w(--foo bar)) 31 | result.foo # => "bar" 32 | ``` 33 | 34 | ### Nilable Accessor 35 | 36 | ```crystal 37 | class Model < Optarg::Model 38 | string "--foo" 39 | end 40 | 41 | result = Model.parse(%w()) 42 | result.foo? # => nil 43 | result.foo # raises KeyError 44 | ``` 45 | 46 | ### Synonyms 47 | 48 | ```crystal 49 | class Model < Optarg::Model 50 | string %w(-f --file) 51 | end 52 | 53 | result = Model.parse(%w(-f foo.cr)) 54 | result.f # => "foo.cr" 55 | result.file # => "foo.cr" 56 | ``` 57 | 58 | ### Boolean 59 | 60 | ```crystal 61 | class Model < Optarg::Model 62 | bool "-b" 63 | end 64 | 65 | result = Model.parse(%w(-b)) 66 | result.b? # => true 67 | ``` 68 | 69 | ### Array 70 | 71 | ```crystal 72 | class Model < Optarg::Model 73 | array "-e" 74 | end 75 | 76 | result = Model.parse(%w(-e foo -e bar -e baz)) 77 | result.e # => ["foo", "bar", "baz"] 78 | ``` 79 | 80 | ### Concatenation 81 | 82 | ```crystal 83 | class Model < Optarg::Model 84 | bool "-a" 85 | bool "-b" 86 | end 87 | 88 | result = Model.parse(%w(-ab)) 89 | result.a? # => true 90 | result.b? # => true 91 | ``` 92 | 93 | ### Default Value 94 | 95 | ```crystal 96 | class Model < Optarg::Model 97 | string "-s", default: "string" 98 | bool "-b", default: true 99 | array "-a", default: %w(1 2 3) 100 | end 101 | 102 | result = Model.parse(%w()) 103 | result.s # => "string" 104 | result.b? # => true 105 | result.a # => ["1", "2", "3"] 106 | ``` 107 | 108 | ### Negation 109 | 110 | ```crystal 111 | class Model < Optarg::Model 112 | bool "-b", default: true, not: "-B" 113 | end 114 | 115 | result = Model.parse(%w(-B)) 116 | result.b? # => false 117 | ``` 118 | 119 | ### Arguments 120 | 121 | ```crystal 122 | class Model < Optarg::Model 123 | string "-s" 124 | bool "-b" 125 | end 126 | 127 | result = Model.parse(%w(foo -s string bar -b baz)) 128 | result.args # => ["foo", "bar", "baz"] 129 | ``` 130 | 131 | ### Named Arguments 132 | 133 | ```crystal 134 | class Model < Optarg::Model 135 | arg "src_dir" 136 | arg "build_dir" 137 | end 138 | 139 | result = Model.parse(%w(/path/to/src /path/to/build and more)) 140 | result.args.src_dir # => "/path/to/src" 141 | result.args.build_dir # => "/path/to/build" 142 | result.args # => ["/path/to/src", "/path/to/build", "and", "more"] 143 | result.named_args # => {"src_dir" => "/path/to/src", "build_dir" => "/path/to/build"} 144 | result.nameless_args # => ["and", "more"] 145 | ``` 146 | 147 | ### Argument Array 148 | 149 | ```crystal 150 | class Model < Optarg::Model 151 | arg "arg" 152 | arg_array "item" 153 | end 154 | 155 | result = Model.parse(%w(foo bar baz)) 156 | result.arg # => "foo" 157 | result.item # => ["bar", "baz"] 158 | ``` 159 | 160 | ### Inheritance (Reusing Models) 161 | 162 | ```crystal 163 | abstract class Animal < Optarg::Model 164 | bool "--sleep" 165 | end 166 | 167 | class Cat < Animal 168 | bool "--mew" 169 | end 170 | 171 | class Dog < Animal 172 | bool "--woof" 173 | end 174 | 175 | Cat.parse(%w()).responds_to?(:sleep?) # => true 176 | Dog.parse(%w()).responds_to?(:sleep?) # => true 177 | ``` 178 | 179 | ### Handler 180 | 181 | ```crystal 182 | class Model < Optarg::Model 183 | on("--goodbye") { goodbye! } 184 | 185 | def goodbye! 186 | raise "Goodbye, world!" 187 | end 188 | end 189 | 190 | Model.parse %w(--goodbye) # raises "Goodbye, world!" 191 | ``` 192 | 193 | ### Required Arguments and Options 194 | 195 | ```crystal 196 | class Profile < Optarg::Model 197 | string "--birthday", required: true 198 | end 199 | 200 | Profile.parse %w() # raises a RequiredOptionError exception. 201 | ``` 202 | 203 | ```crystal 204 | class Compile < Optarg::Model 205 | arg "source_file", required: true 206 | end 207 | 208 | Compile.parse %w() # raises a RequiredArgumentError exception. 209 | ``` 210 | 211 | ### Minimum Length of Array 212 | 213 | ```crystal 214 | class Multiply < Optarg::Model 215 | array "-n", min: 2 216 | 217 | def run 218 | puts options.n.reduce{|n1, n2| n1 * n2} 219 | end 220 | end 221 | 222 | Multiply.parse %w(-n 794) # raises a MinimumLengthError exception. 223 | ``` 224 | 225 | ### Custom Initializer 226 | 227 | ```crystal 228 | class The 229 | def message 230 | "Someday again!" 231 | end 232 | end 233 | 234 | class Model < Optarg::Model 235 | def initialize(argv, @the : The) 236 | super argv 237 | end 238 | 239 | on("--goodbye") { raise @the.message } 240 | end 241 | 242 | Model.parse(%w(--goodbye), The.new) # raises "Someday again!" 243 | ``` 244 | 245 | ### Stop and Termination 246 | 247 | ```crystal 248 | class Model < Optarg::Model 249 | bool "-b", stop: true 250 | end 251 | 252 | result = Model.parse(%w(foo -b bar)) 253 | result.b? # => true 254 | result.args # => ["foo"] 255 | result.unparsed_args # => ["bar"] 256 | ``` 257 | 258 | ```crystal 259 | class Model < Optarg::Model 260 | terminator "--" 261 | end 262 | 263 | result = Model.parse(%w(foo -- bar)) 264 | result.args # => ["foo"] 265 | result.unparsed_args # => ["bar"] 266 | ``` 267 | 268 | ### Validating Inclusion 269 | 270 | ```crystal 271 | class Trip < Optarg::Model 272 | arg "somewhere_warm", any_of: %w(tahiti okinawa hawaii) 273 | end 274 | 275 | Trip.parse(%w(gotland)) # => raises an error 276 | ``` 277 | 278 | ### Custom Validation 279 | 280 | ```crystal 281 | class Hello < Optarg::Model 282 | arg "smiley" 283 | 284 | Parser.on_validate do |parser| 285 | parser.invalidate! "That's not a smile." if parser.args.smiley != ":)" 286 | end 287 | end 288 | 289 | Hello.parse %w(:P) # => raises "That's not a smile." 290 | ``` 291 | 292 | ## Usage 293 | 294 | ```crystal 295 | require "optarg" 296 | ``` 297 | 298 | and see: 299 | 300 | * [Code Samples](#code_samples) 301 | * [Wiki](https://github.com/mosop/optarg/wiki) 302 | * [API Document](http://mosop.me/optarg/Optarg.html) 303 | 304 | ## Release Notes 305 | 306 | See [Releases](https://github.com/mosop/optarg/releases). 307 | -------------------------------------------------------------------------------- /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/optarg.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/optarg.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/optarg.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/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 -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: optarg 2 | version: 0.6.0 3 | 4 | authors: 5 | - mosop 6 | 7 | license: MIT 8 | 9 | crystal: 0.26.0 10 | 11 | dependencies: 12 | callback: 13 | github: mosop/callback 14 | version: ~> 0.6.3 15 | string_inflection: 16 | github: mosop/string_inflection 17 | version: ~> 0.2.1 18 | -------------------------------------------------------------------------------- /spec/classes/argument_value_container_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargArgumentValueContainerClassFeature 4 | class Model < Optarg::Model 5 | end 6 | 7 | it "#[]" do 8 | model = Model.new(%w()) 9 | model.__parser.parsed_args << "arg" 10 | model[0].should eq "arg" 11 | end 12 | 13 | it "#[]?" do 14 | model = Model.new(%w()) 15 | model[0]?.should be_nil 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/classes/option_value_container_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargOptionValueContainerClassFeature 4 | class Model < Optarg::Model 5 | end 6 | 7 | it "#[]" do 8 | model = Model.new(%w()) 9 | model[String].class.should eq Optarg::Definitions::StringOption::Typed::ValueHash 10 | model[Bool].class.should eq Optarg::Definitions::BoolOption::Typed::ValueHash 11 | model[Array(String)].class.should eq Optarg::Definitions::StringArrayOption::Typed::ValueHash 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/features/accessor_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargAccessorFeature 4 | class Model < Optarg::Model 5 | string "--foo" 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w(--foo bar)) 10 | result.foo.should eq "bar" 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/features/argument_array_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargArgumentArrayFeature 4 | class Model < Optarg::Model 5 | arg "arg" 6 | arg_array "item" 7 | end 8 | 9 | it name do 10 | result = Model.parse(%w(foo bar baz)) 11 | result.arg.should eq "foo" 12 | result.item.should eq ["bar", "baz"] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/features/arguments_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargArgumentsFeature 4 | class Model < Optarg::Model 5 | string "-s" 6 | bool "-b" 7 | end 8 | 9 | it name do 10 | result = Model.parse(%w(foo -s string -b bar baz)) 11 | result.nameless_args.should eq %w(foo bar baz) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/features/array_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargArrayFeature 4 | class Model < Optarg::Model 5 | array "-e" 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w(-e foo -e bar -e baz)) 10 | result.e.should eq %w(foo bar baz) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/features/boolean_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargBooleanFeature 4 | class Model < Optarg::Model 5 | bool "-b" 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w(-b)) 10 | result.b?.should be_true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/features/concatenation_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargConcatenationFeature 4 | class Model < Optarg::Model 5 | bool "-a" 6 | bool "-b" 7 | end 8 | 9 | it name do 10 | result = Model.parse(%w(-ab)) 11 | result.a?.should be_true 12 | result.b?.should be_true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/features/custom_initializer_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargCustomInitializerFeature 4 | class The 5 | def message 6 | "Someday again!" 7 | end 8 | end 9 | 10 | class Model < Optarg::Model 11 | def initialize(argv, @the : The) 12 | super argv 13 | end 14 | 15 | on("--goodbye") { raise @the.message } 16 | end 17 | 18 | it name do 19 | argv = %w(--goodbye) 20 | the = The.new 21 | expect_raises(Exception, "Someday again!") { Model.parse(argv, the) } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/features/default_value_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargDefaultValueFeature 4 | class Model < Optarg::Model 5 | string "-s", default: "string" 6 | bool "-b", default: false 7 | array "-a", default: %w(1 2 3) 8 | arg "arg", default: "arg" 9 | end 10 | 11 | it name do 12 | result = Model.parse(%w()) 13 | result.s.should eq "string" 14 | result.b?.should be_false 15 | result.a.should eq %w(1 2 3) 16 | result.arg.should eq "arg" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/features/handler_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargHandlerFeature 4 | class Model < Optarg::Model 5 | on("--goodbye") { goodbye! } 6 | 7 | def goodbye! 8 | raise "Goodbye, world!" 9 | end 10 | end 11 | 12 | it name do 13 | expect_raises(Exception, "Goodbye, world!") { Model.parse(%w(--goodbye)) } 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/inheritance_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInheritanceFeature 4 | abstract class Animal < Optarg::Model 5 | bool "--sleep" 6 | end 7 | 8 | class Cat < Animal 9 | bool "--mew" 10 | end 11 | 12 | class Dog < Animal 13 | bool "--woof" 14 | end 15 | 16 | it name do 17 | Cat.parse(%w()).responds_to?(:sleep?).should be_true 18 | Dog.parse(%w()).responds_to?(:sleep?).should be_true 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/features/minimum_length_of_array_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargMinimumLengthOfArrayFeature 4 | class Multiply < Optarg::Model 5 | array "-n", min: 2 6 | end 7 | 8 | it name do 9 | expect_raises(Optarg::Definitions::StringArrayOption::Validations::MinimumLengthOfArray::Error) { Multiply.parse %w(-n 794) } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/features/named_arguments_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargNamedArgumentsFeature 4 | class Model < Optarg::Model 5 | arg "src_dir" 6 | arg "build_dir" 7 | end 8 | 9 | it name do 10 | result = Model.parse(%w(/path/to/src /path/to/build and more)) 11 | result.src_dir.should eq "/path/to/src" 12 | result.build_dir.should eq "/path/to/build" 13 | result[String].should eq({"src_dir" => "/path/to/src", "build_dir" => "/path/to/build"}) 14 | result.nameless_args.should eq ["and", "more"] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/features/negation_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargNegationFeature 4 | class Model < Optarg::Model 5 | bool "-b", default: true, not: "-B" 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w(-B)) 10 | result.b?.should be_false 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/features/nilable_accessor_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargNilableAccessorFeature 4 | class Model < Optarg::Model 5 | string "--foo" 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w()) 10 | result.foo?.should be_nil 11 | expect_raises(KeyError) { result.foo } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/features/required_arguments_and_options_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargRequiredArgumentsAndOptionsFeature 4 | class Compile < Optarg::Model 5 | arg "source_file", required: true 6 | end 7 | 8 | class Profile < Optarg::Model 9 | string "--birthday", required: true 10 | end 11 | 12 | describe name do 13 | it "Arguments" do 14 | expect_raises(Optarg::Definitions::StringArgument::Validations::Existence::Error) { Compile.parse %w() } 15 | end 16 | 17 | it "Options" do 18 | expect_raises(Optarg::Definitions::StringOption::Validations::Existence::Error) { Profile.parse %w() } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/features/stop_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargStopFeature 4 | class Model < Optarg::Model 5 | bool "-b", stop: true 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w(foo -b bar)) 10 | result.b?.should be_true 11 | result.nameless_args.should eq %w(foo) 12 | result.unparsed_args.should eq %w(bar) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/features/synonyms_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargSynonymsFeature 4 | class Model < Optarg::Model 5 | string %w(-f --file) 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w(-f foo.cr)) 10 | result.f.should eq "foo.cr" 11 | result.file.should eq "foo.cr" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/features/termination_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargTerminationFeature 4 | class Model < Optarg::Model 5 | terminator "--" 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w(foo -- bar)) 10 | result.nameless_args.should eq %w(foo) 11 | result.unparsed_args.should eq %w(bar) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/fix/inherit_definition_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargFixInheritDefinitionFeature 4 | class Model < Optarg::Model 5 | arg "arg" 6 | string "-s" 7 | bool "-b" 8 | on("-h") {} 9 | terminator "--" 10 | end 11 | 12 | class Sub < Model 13 | end 14 | 15 | it name do 16 | Sub.__klass.definitions.all.size.should eq 5 17 | Sub.__klass.definitions.arguments.size.should eq 1 18 | Sub.__klass.definitions.options.size.should eq 3 19 | Sub.__klass.definitions.value_options.size.should eq 2 20 | Sub.__klass.definitions.values.size.should eq 3 21 | Sub.__klass.definitions.handlers.size.should eq 1 22 | Sub.__klass.definitions.terminators.size.should eq 1 23 | Sub.__klass.definitions.argument_list.size.should eq 1 24 | 25 | Sub.__klass.definitions.all["arg"]?.should_not be_nil 26 | Sub.__klass.definitions.all["-s"]?.should_not be_nil 27 | Sub.__klass.definitions.all["-b"]?.should_not be_nil 28 | Sub.__klass.definitions.all["-h"]?.should_not be_nil 29 | Sub.__klass.definitions.all["--"]?.should_not be_nil 30 | Sub.__klass.definitions.arguments["arg"]?.should_not be_nil 31 | Sub.__klass.definitions.options["-s"]?.should_not be_nil 32 | Sub.__klass.definitions.options["-b"]?.should_not be_nil 33 | Sub.__klass.definitions.options["-h"]?.should_not be_nil 34 | Sub.__klass.definitions.value_options["-s"]?.should_not be_nil 35 | Sub.__klass.definitions.value_options["-b"]?.should_not be_nil 36 | Sub.__klass.definitions.values["arg"]?.should_not be_nil 37 | Sub.__klass.definitions.values["-s"]?.should_not be_nil 38 | Sub.__klass.definitions.values["-b"]?.should_not be_nil 39 | Sub.__klass.definitions.handlers["-h"]?.should_not be_nil 40 | Sub.__klass.definitions.terminators["--"]?.should_not be_nil 41 | Sub.__klass.definitions.argument_list[0]?.should_not be_nil 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/fix/value_type_is_not_nilable_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargFixValueTypeIsNotNilableFeature 4 | class Model < Optarg::Model 5 | end 6 | 7 | it name do 8 | model = Model.new(%w()) 9 | model.__parser.parsed_args.class.should eq Array(String) 10 | model.__parser.args[String].class.should eq Optarg::ValueTypes::String::ValueHash 11 | model.__parser.args[Bool].class.should eq Optarg::ValueTypes::Bool::ValueHash 12 | model.__parser.args[Array(String)].class.should eq Optarg::ValueTypes::StringArray::ValueHash 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/internal/argument_display_name_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalArugmentDisplayNameFeature 4 | class Metadata < Optarg::Metadata 5 | def display_name 6 | if definition.is_a?(Optarg::DefinitionMixins::Argument) 7 | super.upcase 8 | else 9 | super 10 | end 11 | end 12 | end 13 | 14 | class Model < Optarg::Model 15 | arg "arg" 16 | end 17 | 18 | class Upcase < Optarg::Model 19 | arg "arg", metadata: Metadata.new 20 | string "--option" 21 | end 22 | 23 | it name do 24 | Model.__klass.definitions.arguments["arg"].metadata.display_name.should eq "arg" 25 | Upcase.__klass.definitions.arguments["arg"].metadata.display_name.should eq "ARG" 26 | Upcase.__klass.definitions.options["--option"].metadata.display_name.should eq "--option" 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/internal/array_default_is_empty_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalArugmentDisplayNameFeature 4 | class Model < Optarg::Model 5 | array "-a" 6 | end 7 | 8 | it name do 9 | result = Model.parse(%w()) 10 | result.a.should eq %w() 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/internal/array_default_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalArrayDefaultFeature 4 | class Model < Optarg::Model 5 | array "-a", default: %w(default) 6 | end 7 | 8 | it name do 9 | Model.parse(%w()).a.should eq %w(default) 10 | Model.parse(%w(-a specified)).a.should eq %w(specified) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/internal/no_accessor_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalNoAccessorFeature 4 | class ForArguments < ::Optarg::Model 5 | arg "to_s" 6 | arg "same" 7 | arg "unparsed_args" 8 | end 9 | 10 | class ForOptions < ::Optarg::Model 11 | string "--to_s" 12 | bool "--same" 13 | array "--unparsed_args" 14 | end 15 | 16 | class ForStringSame < ::Optarg::Model 17 | string "--same" 18 | end 19 | 20 | describe name do 21 | it "args" do 22 | result = ForArguments.parse(%w()) 23 | model_methods = {{ ForArguments.methods.map{|i| i.name.stringify} }} 24 | model_methods.includes?("to_s").should be_false 25 | model_methods.includes?("same?").should be_false 26 | model_methods.includes?("unparsed_args").should be_false 27 | end 28 | 29 | it "string --to_s, bool --same, array --unparsed_args" do 30 | result = ForOptions.parse(%w()) 31 | model_methods = {{ ForOptions.methods.map{|i| i.name.stringify} }} 32 | model_methods.includes?("to_s").should be_false 33 | model_methods.includes?("same?").should be_false 34 | model_methods.includes?("unparsed_args").should be_false 35 | end 36 | 37 | it "string --same" do 38 | result = ForStringSame.parse(%w()) 39 | model_methods = {{ ForStringSame.methods.map{|i| i.name.stringify} }} 40 | model_methods.includes?("same?").should be_false 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/internal/raise_unknown_option_if_concatenated_options_not_matched_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalRaiseUnknownOptionIfConcatenatedOptionsNotMatchedFeature 4 | class Model < Optarg::Model 5 | end 6 | 7 | it name do 8 | model = Model.new(%w(-ab)) 9 | expect_raises(Optarg::UnknownOption, Optarg::UnknownOption.new(model.__parser, "-a").message) { model.__parse } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/internal/raise_unknown_option_regardless_of_arg_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalRaiseUnknownOptionRegardlessOfArgFeature 4 | class Model < Optarg::Model 5 | arg "arg" 6 | end 7 | 8 | it name do 9 | expect_raises(Optarg::UnknownOption) { Model.parse %w(--unknown)} 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/internal/raise_unsupported_concatenation_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalRaiseUnsupportedConcatenationFeature 4 | class Model < Optarg::Model 5 | string "-s" 6 | bool "-b" 7 | end 8 | 9 | it name do 10 | model = Model.new(%w(-sa)) 11 | expect_raises(Optarg::UnsupportedConcatenation, Optarg::UnsupportedConcatenation.new(model.__parser, Model.__klass.definitions.options["-s"]).message) { model.__parse} 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/internal/required_argument_error_preserves_object_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalRequiredArgumentErrorPreservesObjectFeature 4 | class Model < Optarg::Model 5 | arg "arg1", required: true 6 | end 7 | 8 | it name do 9 | error = nil 10 | begin 11 | Model.parse %w() 12 | rescue ex 13 | error = ex 14 | ensure 15 | end 16 | error.as(Optarg::Definitions::StringArgument::Validations::Existence::Error).definition.should be Model.__klass.definitions.arguments["arg1"] 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/internal/snakify_completion_identifiers_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalSnakifyCompletionIdentifiersFeature 4 | class Model < Optarg::Model 5 | end 6 | 7 | it name do 8 | Model.__klass.name = "ke-ba-b" 9 | gen = Model.__klass.bash_completion.new_generator("prefix") 10 | gen.next_completion_for(Model.__klass).new_generator(gen).result.chomp.should eq <<-EOS 11 | prefix__ke_ba_b___keys=() 12 | prefix__ke_ba_b___args=() 13 | prefix__ke_ba_b___acts=() 14 | prefix__ke_ba_b___cmds=() 15 | prefix__ke_ba_b___lens=() 16 | prefix__ke_ba_b___nexts=() 17 | prefix__ke_ba_b___occurs=() 18 | prefix__ke_ba_b___tags=() 19 | prefix__ke_ba_b___words=() 20 | 21 | function prefix__ke_ba_b() { 22 | prefix___k=-1 23 | prefix___ai=0 24 | prefix___f=() 25 | while ! prefix__ke_ba_b___tag stop; do 26 | prefix___word 27 | prefix__ke_ba_b___key 28 | if prefix__ke_ba_b___tag term; then 29 | prefix___inc 30 | break 31 | fi 32 | if prefix___end; then 33 | prefix__ke_ba_b___ls 34 | return $? 35 | fi 36 | if [[ $prefix___w =~ ^- ]]; then 37 | if [ $prefix___k -eq -1 ]; then 38 | prefix___any 39 | return $? 40 | fi 41 | prefix___found 42 | prefix___inc 43 | prefix__ke_ba_b___len 44 | if [ $prefix___l -eq 1 ]; then 45 | continue 46 | fi 47 | if prefix___end; then 48 | prefix__ke_ba_b___lskey 49 | return $? 50 | fi 51 | prefix___inc 52 | else 53 | if prefix__ke_ba_b___arg; then 54 | if prefix___end; then 55 | prefix__ke_ba_b___lskey 56 | return $? 57 | fi 58 | fi 59 | prefix___inc 60 | fi 61 | done 62 | if [[ "${prefix__ke_ba_b___nexts[$prefix___k]}" != "" ]]; then 63 | prefix__ke_ba_b___next 64 | else 65 | prefix___any 66 | fi 67 | return $? 68 | } 69 | 70 | function prefix__ke_ba_b___arg() { 71 | if [ $prefix___ai -lt ${#prefix__ke_ba_b___args[@]} ]; then 72 | prefix___k=${prefix__ke_ba_b___args[$prefix___ai]} 73 | if ! prefix__ke_ba_b___tag varg; then 74 | let prefix___ai+=1 75 | fi 76 | return 0 77 | fi 78 | return 1 79 | } 80 | 81 | function prefix__ke_ba_b___key() { 82 | local i 83 | i=0 84 | while [ $i -lt ${#prefix__ke_ba_b___keys[@]} ]; do 85 | if [[ ${prefix__ke_ba_b___keys[$i]} == *' '$prefix___w' '* ]]; then 86 | prefix___k=$i 87 | return 0 88 | fi 89 | let i+=1 90 | done 91 | prefix___k=-1 92 | return 1 93 | } 94 | 95 | function prefix__ke_ba_b___len() { 96 | if prefix___keyerr; then return 1; fi 97 | prefix___l=${prefix__ke_ba_b___lens[$prefix___k]} 98 | return 0 99 | } 100 | 101 | function prefix__ke_ba_b___ls() { 102 | local a i max found arg act cmd 103 | a=() 104 | if [[ "$prefix___w" =~ ^- ]]; then 105 | i=0 106 | while [ $i -lt ${#prefix__ke_ba_b___keys[@]} ]; do 107 | if prefix__ke_ba_b___tag arg $i; then 108 | let i+=1 109 | continue 110 | fi 111 | found=${prefix___f[$i]} 112 | if [[ "$found" == "" ]]; then 113 | found=0 114 | fi 115 | max=${prefix__ke_ba_b___occurs[$i]} 116 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 117 | a+=($(echo "${prefix__ke_ba_b___keys[$i]}")) 118 | fi 119 | let i+=1 120 | done 121 | else 122 | if [ $prefix___ai -lt ${#prefix__ke_ba_b___args[@]} ]; then 123 | arg=${prefix__ke_ba_b___args[$prefix___ai]} 124 | act=${prefix__ke_ba_b___acts[$arg]} 125 | cmd=${prefix__ke_ba_b___cmds[$arg]} 126 | if [[ "$act" != "" ]]; then 127 | prefix___act $act 128 | return 0 129 | elif [[ "$cmd" != "" ]]; then 130 | a=($(eval $cmd)) 131 | else 132 | a=($(echo "${prefix__ke_ba_b___words[$arg]}")) 133 | fi 134 | fi 135 | fi 136 | if [ ${#a[@]} -gt 0 ]; then 137 | prefix___add "${a[@]}" 138 | return 0 139 | fi 140 | prefix___any 141 | return $? 142 | } 143 | 144 | function prefix__ke_ba_b___lskey() { 145 | if ! prefix___keyerr; then 146 | local act cmd a 147 | act=${prefix__ke_ba_b___acts[$prefix___k]} 148 | cmd=${prefix__ke_ba_b___cmds[$prefix___k]} 149 | if [[ "$act" != "" ]]; then 150 | : 151 | elif [[ "$cmd" != "" ]]; then 152 | a=($(eval $cmd)) 153 | else 154 | a=($(echo "${prefix__ke_ba_b___words[$prefix___k]}")) 155 | fi 156 | if [[ "$act" != "" ]]; then 157 | prefix___act $act 158 | return 0 159 | elif [ ${#a[@]} -gt 0 ]; then 160 | prefix___add "${a[@]}" 161 | return 0 162 | fi 163 | fi 164 | prefix___any 165 | return $? 166 | } 167 | 168 | function prefix__ke_ba_b___tag() { 169 | local k 170 | if [[ "$2" == "" ]]; then 171 | if prefix___keyerr; then return 1; fi 172 | k=$prefix___k 173 | else 174 | k=$2 175 | fi 176 | if [[ ${prefix__ke_ba_b___tags[$k]} == *' '$1' '* ]]; then 177 | return 0 178 | fi 179 | return 1 180 | } 181 | EOS 182 | end 183 | end 184 | -------------------------------------------------------------------------------- /spec/internal/stopper_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalStopperFeature 4 | class ArgumentModel < Optarg::Model 5 | arg "a", stop: true 6 | arg "arg" 7 | end 8 | 9 | class StringModel < Optarg::Model 10 | string "-s", stop: true 11 | arg "arg" 12 | end 13 | 14 | class BoolModel < Optarg::Model 15 | bool "-b", stop: true 16 | arg "arg" 17 | end 18 | 19 | class HandlerModel < Optarg::Model 20 | on("-h", stop: true) {} 21 | arg "arg" 22 | end 23 | 24 | macro test(model, args) 25 | it {{model}}.name do 26 | result = {{model}}.parse({{args}}) 27 | result.arg?.should be_nil 28 | result.unparsed_args.should eq \%w(arg) 29 | end 30 | end 31 | 32 | describe name do 33 | test ArgumentModel, %w(a arg) 34 | test StringModel, %w(-s s arg) 35 | test BoolModel, %w(-b arg) 36 | test HandlerModel, %w(-h arg) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/internal/terminator_does_not_affect_another_model_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalTerminatorDoesNotAffectAnotherModelFeature 4 | class Terminated < Optarg::Model 5 | terminator "--" 6 | end 7 | 8 | class NotTerminated < Optarg::Model 9 | end 10 | 11 | it name do 12 | expect_raises(Optarg::UnknownOption) { NotTerminated.parse(%w(--)) } 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/internal/unknown_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargInternalSpec::Unknown 4 | class Model < Optarg::Model 5 | string "-s" 6 | unknown 7 | end 8 | 9 | describe name do 10 | it "with option" do 11 | result = Model.parse(%w(-s foo bar)) 12 | result.s?.should eq "foo" 13 | result.unparsed_args.should eq %w(bar) 14 | end 15 | 16 | it "without different option" do 17 | result = Model.parse(%w(-b foo bar)) 18 | result.unparsed_args.should eq %w(-b foo bar) 19 | end 20 | 21 | it "without option" do 22 | result = Model.parse(%w(foo bar)) 23 | result.unparsed_args.should eq %w(foo bar) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/internal_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | module OptargInternalFeature 4 | class ParseModel < Optarg::Model 5 | string "-s" 6 | bool "-b" 7 | arg "arg" 8 | terminator "--" 9 | end 10 | 11 | it "-s v -b arg parsed -- unparsed" do 12 | argv = %w{-s v -b arg parsed -- unparsed} 13 | result = ParseModel.parse(argv) 14 | result.arg.should eq "arg" 15 | result.s.should eq "v" 16 | result.s?.should eq "v" 17 | result.b?.should be_true 18 | result.__parser.parsed_args.should eq %w(arg parsed) 19 | result[String].should eq({"-s" => "v", "arg" => "arg"}) 20 | result.nameless_args.should eq %w(parsed) 21 | result.unparsed_args.should eq %w(unparsed) 22 | result.__parser.parsed_nodes[0].should eq Optarg::Parser.new_node(%w(-s v), ParseModel.__klass.definitions.options["-s"]) 23 | result.__parser.parsed_nodes[1].should eq Optarg::Parser.new_node(%w(-b), ParseModel.__klass.definitions.options["-b"]) 24 | result.__parser.parsed_nodes[2].should eq Optarg::Parser.new_node(%w(arg), ParseModel.__klass.definitions.arguments["arg"]) 25 | result.__parser.parsed_nodes[3].should eq Optarg::Parser.new_node(%w(parsed)) 26 | end 27 | 28 | it "parses nothing" do 29 | argv = %w{} 30 | result = ParseModel.parse(argv) 31 | expect_raises(KeyError) { result.s } 32 | result.s?.should be_nil 33 | result.b?.should be_false 34 | result.nameless_args.should eq %w() 35 | result.__parser.parsed_args.should eq %w() 36 | result.unparsed_args.should eq %w() 37 | result.__parser.parsed_nodes.should eq [] of Array(String) 38 | end 39 | 40 | class Supermodel < Optarg::Model 41 | string "-s" 42 | bool "-b" 43 | end 44 | 45 | class Submodel < Supermodel 46 | string "--string" 47 | bool "--bool" 48 | end 49 | 50 | class Supermodel2 < Optarg::Model 51 | arg "arg1" 52 | end 53 | 54 | class Submodel2 < Supermodel2 55 | arg "argument" 56 | end 57 | 58 | describe "Inheritance 1" do 59 | it "inherits options" do 60 | argv = %w{-s v -b --string value --bool} 61 | result = Submodel.parse(argv) 62 | result.s.should eq "v" 63 | result.b?.should be_true 64 | result.string.should eq "value" 65 | result.bool?.should be_true 66 | end 67 | 68 | it "does not apply to parent" do 69 | argv = %w{--string} 70 | expect_raises(Optarg::UnknownOption) { Supermodel.parse(argv) } 71 | argv = %w{--bool} 72 | expect_raises(Optarg::UnknownOption) { Supermodel.parse(argv) } 73 | end 74 | end 75 | 76 | describe "Inheritance 2" do 77 | it "inherits arguments" do 78 | argv = %w(foo bar) 79 | result = Submodel2.parse(argv) 80 | result.arg1.should eq "foo" 81 | result.argument.should eq "bar" 82 | end 83 | 84 | it "does not apply to parent" do 85 | result = Supermodel2.parse(%w(foo bar)) 86 | result.responds_to?(:argument).should be_false 87 | end 88 | end 89 | 90 | class EmptyModel < Optarg::Model 91 | end 92 | 93 | describe "Unknown Option" do 94 | it "--unknown" do 95 | argv = %w(--unknown) 96 | expect_raises(Optarg::UnknownOption) { EmptyModel.parse(argv) } 97 | end 98 | end 99 | 100 | class MissingModel < Optarg::Model 101 | string "-s" 102 | array "-a" 103 | end 104 | 105 | describe "Missing Value" do 106 | it "-s" do 107 | argv = %w(-s) 108 | expect_raises(Optarg::MissingValue, "The -s option has no value") { MissingModel.parse(argv) } 109 | end 110 | 111 | it "-a" do 112 | argv = %w(-a) 113 | expect_raises(Optarg::MissingValue, "The -a option has no value") { MissingModel.parse(argv) } 114 | end 115 | end 116 | 117 | class DefaultModel < Optarg::Model 118 | string "--default-string", default: "default" 119 | bool "--default-bool", default: true, not: "--Default-bool" 120 | end 121 | 122 | describe "Default Value" do 123 | it "sets default value" do 124 | argv = %w() 125 | result = DefaultModel.parse(argv) 126 | result.default_string.should eq "default" 127 | result.default_bool?.should be_true 128 | end 129 | 130 | it "--default-string notdefault --Default-bool" do 131 | argv = %w(--default-string notdefault --Default-bool) 132 | result = DefaultModel.parse(argv) 133 | result.default_string.should eq "notdefault" 134 | result.default_bool?.should be_false 135 | end 136 | end 137 | 138 | class SynonymsModel < Optarg::Model 139 | string %w(-s --string) 140 | bool %w(-b --bool) 141 | end 142 | 143 | describe "Synonyms" do 144 | it "defines multiple accessors" do 145 | argv = %w(-s v --bool) 146 | result = SynonymsModel.parse(argv) 147 | result.responds_to?(:s).should be_true 148 | result.responds_to?(:string).should be_true 149 | result.responds_to?(:b?).should be_true 150 | result.responds_to?(:bool?).should be_true 151 | result.s.should eq "v" if result.responds_to?(:s) 152 | result.string.should eq "v" if result.responds_to?(:string) 153 | result.b?.should be_true if result.responds_to?(:b?) 154 | result.bool?.should be_true if result.responds_to?(:bool?) 155 | end 156 | end 157 | 158 | class MetadataModel < Optarg::Model 159 | class Metadata < Optarg::Metadata 160 | getter :data 161 | 162 | def initialize(@data : ::String) 163 | end 164 | end 165 | 166 | string "-s", metadata: Metadata.new("string") 167 | bool "-b", Metadata.new("bool") 168 | array "-a", Metadata.new("array") 169 | arg "arg", metadata: Metadata.new("arg") 170 | on("--help", metadata: Metadata.new("handler")) {} 171 | terminator "--", metadata: Metadata.new("terminator") 172 | end 173 | 174 | describe "Metadata" do 175 | it "preserves metadata" do 176 | MetadataModel.__klass.definitions.options["-s"].metadata.as(MetadataModel::Metadata).data.should eq "string" 177 | MetadataModel.__klass.definitions.options["-b"].metadata.as(MetadataModel::Metadata).data.should eq "bool" 178 | MetadataModel.__klass.definitions.options["-a"].metadata.as(MetadataModel::Metadata).data.should eq "array" 179 | MetadataModel.__klass.definitions.arguments["arg"].metadata.as(MetadataModel::Metadata).data.should eq "arg" 180 | MetadataModel.__klass.definitions.handlers["--help"].metadata.as(MetadataModel::Metadata).data.should eq "handler" 181 | MetadataModel.__klass.definitions.terminators["--"].metadata.as(MetadataModel::Metadata).data.should eq "terminator" 182 | end 183 | end 184 | end 185 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/optarg" 3 | -------------------------------------------------------------------------------- /spec/wiki/accessing_values_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargAccessingValuesWikiFeature 4 | module AccessingNamedValues 5 | module ValueAccessors 6 | module Section1 7 | class Model < Optarg::Model 8 | string "-s" 9 | bool "-b" 10 | array "-a" 11 | arg "arg" 12 | arg_array "args" 13 | end 14 | 15 | it name do 16 | result = Model.parse(%w(-s foo -b -a bar -a baz blep blah boop)) 17 | result.s.should eq "foo" 18 | result.b?.should be_true 19 | result.a.should eq ["bar", "baz"] 20 | result.arg.should eq "blep" 21 | result.args.should eq ["blah", "boop"] 22 | end 23 | end 24 | 25 | module Section2 26 | class Model < Optarg::Model 27 | bool "-b" 28 | end 29 | 30 | it name do 31 | result = Model.parse(%w()) 32 | result.b?.should be_false 33 | end 34 | end 35 | 36 | module Section3 37 | class Model < Optarg::Model 38 | string "-s" 39 | end 40 | 41 | it name do 42 | result = Model.parse(%w()) 43 | expect_raises(KeyError) { result.s } 44 | result.s?.should be_nil 45 | end 46 | end 47 | 48 | module Section4 49 | class Model < Optarg::Model 50 | bool %w(-f --force) 51 | end 52 | 53 | it name do 54 | result = Model.parse(%w(--force)) 55 | result.f?.should be_true 56 | result.force?.should be_true 57 | end 58 | end 59 | end 60 | 61 | module ValueHashes 62 | module Section1 63 | class Model < Optarg::Model 64 | string "-s" 65 | bool "-b" 66 | array "-a" 67 | arg "arg" 68 | arg_array "args" 69 | end 70 | 71 | it name do 72 | result = Model.parse(%w(-s foo -b -a bar -a baz blep blah boop)) 73 | result[String].should eq({"-s" => "foo", "arg" => "blep"}) 74 | result[Bool].should eq({"-b" => true}) 75 | result[Array(String)].should eq({"-a" => ["bar", "baz"], "args" => ["blah", "boop"]}) 76 | end 77 | end 78 | 79 | module Section2 80 | class Model < Optarg::Model 81 | bool %w(-f --force) 82 | end 83 | 84 | it name do 85 | result = Model.parse(%w(--force)) 86 | result[Bool]["-f"].should be_true 87 | expect_raises(KeyError) { result[Bool]["--force"] } 88 | end 89 | end 90 | end 91 | 92 | module AvoidingOverridingMethods 93 | class Model < Optarg::Model 94 | string "--class" 95 | end 96 | 97 | it name do 98 | result = Model.parse(%w(--class foo)) 99 | result.class.should eq Model 100 | result[String]["--class"].should eq "foo" 101 | end 102 | end 103 | end 104 | 105 | module AccessingNamelessArguments 106 | class Model < Optarg::Model 107 | arg "arg" 108 | end 109 | 110 | it name do 111 | result = Model.parse(%w(foo bar baz)) 112 | result.arg.should eq "foo" 113 | result.nameless_args.should eq ["bar", "baz"] 114 | end 115 | end 116 | 117 | module AccessingUnparsedArguments 118 | module Section1 119 | class Model < Optarg::Model 120 | arg "arg", stop: true 121 | end 122 | 123 | it name do 124 | result = Model.parse(%w(foo bar baz)) 125 | result.arg.should eq "foo" 126 | result.unparsed_args.should eq ["bar", "baz"] 127 | end 128 | end 129 | 130 | module Section2 131 | class Model < Optarg::Model 132 | terminator "--" 133 | end 134 | 135 | it name do 136 | result = Model.parse(%w(foo -- bar baz)) 137 | result.nameless_args.should eq ["foo"] 138 | result.unparsed_args.should eq ["bar", "baz"] 139 | end 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /spec/wiki/builtin_validations_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargBuiltinValidationsWikiFeature 4 | module Inclusion 5 | class SomewhereWarm < Optarg::Model 6 | arg "island", any_of: %w(tahiti okinawa hawaii) 7 | end 8 | 9 | it name do 10 | expect_raises(Optarg::Definitions::StringArgument::Validations::Inclusion::Error, "The island argument must be one of tahiti, okinawa, hawaii.") { 11 | SomewhereWarm.parse %w(gotland) 12 | } 13 | end 14 | end 15 | 16 | module ElementInclusion 17 | class EuropeanWallet < Optarg::Model 18 | arg_array "bill", any_item_of: %w(€5 €10 €20 €50 €100 €200 €500) 19 | end 20 | 21 | it name do 22 | expect_raises(Optarg::Definitions::StringArrayArgument::Validations::ElementInclusion::Error, "Each element of the bill argument must be one of €5, €10, €20, €50, €100, €200, €500.") { 23 | EuropeanWallet.parse %w(€10 $50 €100) 24 | } 25 | end 26 | end 27 | 28 | module MinimumLengthOfArray 29 | class TeamOfCanoePolo < Optarg::Model 30 | arg_array "member", min: 5 31 | end 32 | 33 | it name do 34 | expect_raises(Optarg::Definitions::StringArrayArgument::Validations::MinimumLengthOfArray::Error, "The count of the member arguments is 4, but 5 or more is expected.") { 35 | TeamOfCanoePolo.parse %w(freddie brian roger john) 36 | } 37 | end 38 | end 39 | 40 | module Existence 41 | class GoToProm < Optarg::Model 42 | string "--dress" 43 | string "--partner", required: true 44 | end 45 | 46 | it name do 47 | expect_raises(Optarg::Definitions::StringOption::Validations::Existence::Error, "The --partner option is required.") { 48 | GoToProm.parse %w(--dress brand-new-tuxedo) 49 | } 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/wiki/custom_validation_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | module OptargCustomValidationWikiFeature 4 | class Hello < Optarg::Model 5 | arg "smiley" 6 | 7 | Parser.on_validate do |parser, data| 8 | parser.invalidate! "That's not a smile." if data.smiley != ":)" 9 | end 10 | end 11 | 12 | it name do 13 | expect_raises(Optarg::ValidationError, "That's not a smile.") { Hello.parse %w(:P) } 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/lib/completion.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | class Completion 4 | getter type : Symbol 5 | getter model : ModelClass 6 | 7 | def initialize(@type, @model) 8 | end 9 | 10 | def generator_class 11 | case type 12 | when :bash 13 | CompletionGenerators::Bash 14 | when :zsh 15 | CompletionGenerators::Zsh 16 | else 17 | raise "Unknown type: #{type}" 18 | end 19 | end 20 | 21 | def new_generator(prefix : String) 22 | generator_class.new(self, prefix) 23 | end 24 | 25 | def new_generator(previous : CompletionGenerators::Base) 26 | generator_class.new(previous, self) 27 | end 28 | end 29 | end 30 | 31 | require "./completion/text_formatter" 32 | require "./completion/*" 33 | -------------------------------------------------------------------------------- /src/lib/completion/function.cr: -------------------------------------------------------------------------------- 1 | require "./text_formatter" 2 | 3 | class Optarg::Completion 4 | abstract class Function 5 | include TextFormatter 6 | 7 | macro inherited 8 | def name 9 | "#{prefix}{{@type.name.split("::")[-1].underscore.id}}" 10 | end 11 | end 12 | 13 | getter g : CompletionGenerators::Base 14 | getter header = %w() 15 | getter body = %w() 16 | getter footer = %w() 17 | 18 | def initialize(@g) 19 | if zsh? 20 | header << <<-EOS 21 | setopt localoptions ksharrays 22 | EOS 23 | end 24 | make 25 | end 26 | 27 | def zsh? 28 | g.zsh? 29 | end 30 | 31 | def <<(line : String) 32 | body << line 33 | end 34 | 35 | @text : String? 36 | def text 37 | @text ||= combine 38 | end 39 | 40 | def prefix 41 | g.prefix 42 | end 43 | 44 | def global 45 | g.first.prefix 46 | end 47 | 48 | def combine 49 | sections = %w() 50 | sections << header.join("\n\n") unless header.empty? 51 | sections << body.join("\n") unless body.empty? 52 | sections << footer.join("\n\n") unless footer.empty? 53 | a = %w() 54 | a << "function #{name}() {" 55 | a << indent(sections.join("\n\n")) 56 | a << "}" 57 | a.join("\n") 58 | end 59 | 60 | macro f(name) 61 | {% if name == :act %} 62 | "#{global}act" 63 | {% elsif name == :add %} 64 | "#{global}add" 65 | {% elsif name == :any %} 66 | "#{global}any" 67 | {% elsif name == :arg %} 68 | "#{prefix}arg" 69 | {% elsif name == :cur %} 70 | "#{global}cur" 71 | {% elsif name == :end %} 72 | "#{global}end" 73 | {% elsif name == :found %} 74 | "#{global}found" 75 | {% elsif name == :inc %} 76 | "#{global}inc" 77 | {% elsif name == :key %} 78 | "#{prefix}key" 79 | {% elsif name == :keyerr %} 80 | "#{global}keyerr" 81 | {% elsif name == :ls %} 82 | "#{prefix}ls" 83 | {% elsif name == :lskey %} 84 | "#{prefix}lskey" 85 | {% elsif name == :len %} 86 | "#{prefix}len" 87 | {% elsif name == :next %} 88 | "#{prefix}next" 89 | {% elsif name == :reply %} 90 | "#{global}reply" 91 | {% elsif name == :tag %} 92 | "#{prefix}tag" 93 | {% elsif name == :word %} 94 | "#{global}word" 95 | {% else %} 96 | {% raise "No function: #{name}" %} 97 | {% end %} 98 | end 99 | 100 | def key 101 | "#{global}k" 102 | end 103 | 104 | def len 105 | "#{global}l" 106 | end 107 | 108 | def found 109 | "#{global}f" 110 | end 111 | 112 | def word 113 | "#{global}w" 114 | end 115 | 116 | def index 117 | "#{global}i" 118 | end 119 | 120 | def arg_index 121 | "#{global}ai" 122 | end 123 | 124 | def cursor 125 | "#{global}c" 126 | end 127 | 128 | def keys 129 | "#{prefix}keys" 130 | end 131 | 132 | def tags 133 | "#{prefix}tags" 134 | end 135 | 136 | def occurs 137 | "#{prefix}occurs" 138 | end 139 | 140 | def lens 141 | "#{prefix}lens" 142 | end 143 | 144 | def args 145 | "#{prefix}args" 146 | end 147 | 148 | def words 149 | "#{prefix}words" 150 | end 151 | 152 | def cmds 153 | "#{prefix}cmds" 154 | end 155 | 156 | def acts 157 | "#{prefix}acts" 158 | end 159 | 160 | def nexts 161 | "#{prefix}nexts" 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /src/lib/completion/functions.cr: -------------------------------------------------------------------------------- 1 | require "./function" 2 | require "./functions/*" 3 | -------------------------------------------------------------------------------- /src/lib/completion/functions/act.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Act < Function 3 | def make 4 | if zsh? 5 | body << <<-EOS 6 | local -a a jids 7 | case $1 in 8 | alias) 9 | a=( "${(k)aliases[@]}" ) ;; 10 | arrayvar) 11 | a=( "${(k@)parameters[(R)array*]}" ) 12 | ;; 13 | binding) 14 | a=( "${(k)widgets[@]}" ) 15 | ;; 16 | builtin) 17 | a=( "${(k)builtins[@]}" "${(k)dis_builtins[@]}" ) 18 | ;; 19 | command) 20 | a=( "${(k)commands[@]}" "${(k)aliases[@]}" "${(k)builtins[@]}" "${(k)functions[@]}" "${(k)reswords[@]}") 21 | ;; 22 | directory) 23 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N-/) ) 24 | ;; 25 | disabled) 26 | a=( "${(k)dis_builtins[@]}" ) 27 | ;; 28 | enabled) 29 | a=( "${(k)builtins[@]}" ) 30 | ;; 31 | export) 32 | a=( "${(k)parameters[(R)*export*]}" ) 33 | ;; 34 | file) 35 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 36 | ;; 37 | function) 38 | a=( "${(k)functions[@]}" ) 39 | ;; 40 | group) 41 | _groups -U -O a 42 | ;; 43 | hostname) 44 | _hosts -U -O a 45 | ;; 46 | job) 47 | a=( "${savejobtexts[@]%% *}" ) 48 | ;; 49 | keyword) 50 | a=( "${(k)reswords[@]}" ) 51 | ;; 52 | running) 53 | a=() 54 | jids=( "${(@k)savejobstates[(R)running*]}" ) 55 | for job in "${jids[@]}"; do 56 | a+=( ${savejobtexts[$job]%% *} ) 57 | done 58 | ;; 59 | stopped) 60 | a=() 61 | jids=( "${(@k)savejobstates[(R)suspended*]}" ) 62 | for job in "${jids[@]}"; do 63 | a+=( ${savejobtexts[$job]%% *} ) 64 | done 65 | ;; 66 | setopt|shopt) 67 | a=( "${(k)options[@]}" ) 68 | ;; 69 | signal) 70 | a=( "SIG${^signals[@]}" ) 71 | ;; 72 | user) 73 | a=( "${(k)userdirs[@]}" ) 74 | ;; 75 | variable) 76 | a=( "${(k)parameters[@]}" ) 77 | ;; 78 | *) 79 | a=( ${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N) ) 80 | ;; 81 | esac 82 | compadd -- "${a[@]}" 83 | return 0 84 | EOS 85 | else 86 | body << <<-EOS 87 | #{f(:cur)} 88 | COMPREPLY=( $(compgen -A $1 -- "${#{cursor}}") ) 89 | return 0 90 | EOS 91 | end 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /src/lib/completion/functions/add.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Add < Function 3 | def make 4 | if zsh? 5 | body << <<-EOS 6 | compadd -- "${@:1}" 7 | return 0 8 | EOS 9 | else 10 | body << <<-EOS 11 | #{f(:cur)} 12 | COMPREPLY=( $(compgen -W "$(echo ${@:1})" -- "${#{cursor}}") ) 13 | return 0 14 | EOS 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/lib/completion/functions/any.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Any < Function 3 | def make 4 | body << <<-EOS 5 | #{f(:act)} file 6 | return $? 7 | EOS 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/lib/completion/functions/arg.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Arg < Function 3 | def make 4 | body << <<-EOS 5 | if [ $#{arg_index} -lt ${##{args}[@]} ]; then 6 | #{key}=${#{args}[$#{arg_index}]} 7 | if ! #{f(:tag)} varg; then 8 | let #{arg_index}+=1 9 | fi 10 | return 0 11 | fi 12 | return 1 13 | EOS 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /src/lib/completion/functions/cur.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Cur < Function 3 | def make 4 | body << <<-EOS 5 | #{cursor}="${COMP_WORDS[COMP_CWORD]}" 6 | return 0 7 | EOS 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/lib/completion/functions/end.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class End < Function 3 | def make 4 | body << <<-EOS 5 | if [ $#{index} -lt $COMP_CWORD ]; then 6 | return 1 7 | fi 8 | return 0 9 | EOS 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/lib/completion/functions/found.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Found < Function 3 | def make 4 | body << <<-EOS 5 | if #{f(:keyerr)}; then return 1; fi 6 | local n 7 | n=${#{found}[$#{key}]} 8 | if [[ "$n" == "" ]]; then 9 | n=1 10 | else 11 | let n+=1 12 | fi 13 | #{found}[$#{key}]=$n 14 | return 0 15 | EOS 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/lib/completion/functions/inc.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Inc < Function 3 | def make 4 | body << <<-EOS 5 | let #{index}+=1 6 | return 0 7 | EOS 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/lib/completion/functions/key.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Key < Function 3 | def make 4 | body << <<-EOS 5 | local i 6 | i=0 7 | while [ $i -lt ${##{keys}[@]} ]; do 8 | if [[ ${#{keys}[$i]} == *' '$#{word}' '* ]]; then 9 | #{key}=$i 10 | return 0 11 | fi 12 | let i+=1 13 | done 14 | #{key}=-1 15 | return 1 16 | EOS 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/lib/completion/functions/keyerr.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Keyerr < Function 3 | def make 4 | body << <<-EOS 5 | if [[ "$#{key}" == "" ]] || [ $#{key} -lt 0 ]; then 6 | return 0 7 | fi 8 | return 1 9 | EOS 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/lib/completion/functions/len.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Len < Function 3 | def make 4 | body << <<-EOS 5 | if #{f(:keyerr)}; then return 1; fi 6 | #{len}=${#{lens}[$#{key}]} 7 | return 0 8 | EOS 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/lib/completion/functions/ls.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Ls < Function 3 | def make 4 | body << <<-EOS 5 | local a i max found arg act cmd 6 | a=() 7 | if [[ "$#{word}" =~ ^- ]]; then 8 | i=0 9 | while [ $i -lt ${##{keys}[@]} ]; do 10 | if #{f(:tag)} arg $i; then 11 | let i+=1 12 | continue 13 | fi 14 | found=${#{found}[$i]} 15 | if [[ "$found" == "" ]]; then 16 | found=0 17 | fi 18 | max=${#{occurs}[$i]} 19 | if [ $max -lt 0 ] || [ $found -lt $max ]; then 20 | a+=($(echo "${#{keys}[$i]}")) 21 | fi 22 | let i+=1 23 | done 24 | else 25 | if [ $#{arg_index} -lt ${##{args}[@]} ]; then 26 | arg=${#{args}[$#{arg_index}]} 27 | act=${#{acts}[$arg]} 28 | cmd=${#{cmds}[$arg]} 29 | if [[ "$act" != "" ]]; then 30 | #{f(:act)} $act 31 | return 0 32 | elif [[ "$cmd" != "" ]]; then 33 | a=($(eval $cmd)) 34 | else 35 | a=($(echo "${#{words}[$arg]}")) 36 | fi 37 | fi 38 | fi 39 | if [ ${#a[@]} -gt 0 ]; then 40 | #{f(:add)} "${a[@]}" 41 | return 0 42 | fi 43 | #{f(:any)} 44 | return $? 45 | EOS 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /src/lib/completion/functions/lskey.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Lskey < Function 3 | def make 4 | body << <<-EOS 5 | if ! #{f(:keyerr)}; then 6 | local act cmd a 7 | act=${#{acts}[$#{key}]} 8 | cmd=${#{cmds}[$#{key}]} 9 | if [[ "$act" != "" ]]; then 10 | : 11 | elif [[ "$cmd" != "" ]]; then 12 | a=($(eval $cmd)) 13 | else 14 | a=($(echo "${#{words}[$#{key}]}")) 15 | fi 16 | if [[ "$act" != "" ]]; then 17 | #{f(:act)} $act 18 | return 0 19 | elif [ ${#a[@]} -gt 0 ]; then 20 | #{f(:add)} "${a[@]}" 21 | return 0 22 | fi 23 | fi 24 | #{f(:any)} 25 | return $? 26 | EOS 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /src/lib/completion/functions/main.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Main < Function 3 | def make 4 | if g.first? 5 | if zsh? 6 | body << <<-EOS 7 | (( COMP_CWORD = CURRENT - 1 )) 8 | COMP_WORDS=($(echo ${words[@]})) 9 | EOS 10 | end 11 | body << <<-EOS 12 | #{index}=1 13 | EOS 14 | end 15 | body << <<-EOS 16 | #{key}=-1 17 | #{arg_index}=0 18 | #{found}=() 19 | while ! #{f(:tag)} stop; do 20 | #{f(:word)} 21 | #{f(:key)} 22 | if #{f(:tag)} term; then 23 | #{f(:inc)} 24 | break 25 | fi 26 | if #{f(:end)}; then 27 | #{f(:ls)} 28 | return $? 29 | fi 30 | if [[ $#{word} =~ ^- ]]; then 31 | if [ $#{key} -eq -1 ]; then 32 | #{f(:any)} 33 | return $? 34 | fi 35 | #{f(:found)} 36 | #{f(:inc)} 37 | #{f(:len)} 38 | if [ $#{len} -eq 1 ]; then 39 | continue 40 | fi 41 | if #{f(:end)}; then 42 | #{f(:lskey)} 43 | return $? 44 | fi 45 | #{f(:inc)} 46 | else 47 | if #{f(:arg)}; then 48 | if #{f(:end)}; then 49 | #{f(:lskey)} 50 | return $? 51 | fi 52 | fi 53 | #{f(:inc)} 54 | fi 55 | done 56 | if [[ "${#{nexts}[$#{key}]}" != "" ]]; then 57 | #{f(:next)} 58 | else 59 | #{f(:any)} 60 | fi 61 | return $? 62 | EOS 63 | end 64 | 65 | def name 66 | g.entry_point 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /src/lib/completion/functions/next.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Next < Function 3 | @data : Hash(String, ModelClass) 4 | 5 | def initialize(g, @data) 6 | super g 7 | end 8 | 9 | def make 10 | body << <<-EOS 11 | case $#{word} in 12 | EOS 13 | @data.each do |k, v| 14 | gen = g.next_completion_for(v).new_generator(g) 15 | cond = <<-EOS 16 | #{string(k)}) 17 | #{gen.entry_point} 18 | ;; 19 | EOS 20 | body << indent(cond) 21 | end 22 | body << <<-EOS 23 | *) 24 | #{f(:any)} 25 | ;; 26 | esac 27 | return $? 28 | EOS 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /src/lib/completion/functions/tag.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Tag < Function 3 | def make 4 | body << <<-EOS 5 | local k 6 | if [[ "$2" == "" ]]; then 7 | if #{f(:keyerr)}; then return 1; fi 8 | k=$#{key} 9 | else 10 | k=$2 11 | fi 12 | if [[ ${#{tags}[$k]} == *' '$1' '* ]]; then 13 | return 0 14 | fi 15 | return 1 16 | EOS 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/lib/completion/functions/word.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Completion::Functions 2 | class Word < Function 3 | def make 4 | body << <<-EOS 5 | #{word}="${COMP_WORDS[$#{index}]}" 6 | return 0 7 | EOS 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/lib/completion/text_formatter.cr: -------------------------------------------------------------------------------- 1 | class Optarg::Completion 2 | module TextFormatter 3 | def indent(s, spaces = " ", first = true) 4 | a = %w() 5 | s.split("\n").each_with_index do |e, i| 6 | if i == 0 && !first 7 | a << "#{e}" 8 | else 9 | a << "#{spaces}#{e}" 10 | end 11 | end 12 | a.join("\n") 13 | end 14 | 15 | def string(s : String?) 16 | s ||= "" 17 | "'" + s.gsub(/(\\|')/, "'\\\\\\1'") + "'" 18 | end 19 | 20 | def strings_in_string(a : Array(String)?) 21 | a ||= %w() 22 | string(a.map{|i| string(i)}.join(" ")) 23 | end 24 | 25 | def matching_words(a : Array(String)?) 26 | a ||= %w() 27 | s = a.join(" ") 28 | string(s.empty? ? s : " #{s} ") 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /src/lib/completion_generators.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module Optarg::CompletionGenerators 3 | end 4 | 5 | require "./completion_generators/base" 6 | require "./completion_generators/*" 7 | -------------------------------------------------------------------------------- /src/lib/completion_generators/base.cr: -------------------------------------------------------------------------------- 1 | module Optarg::CompletionGenerators 2 | abstract class Base 3 | include Completion::TextFormatter 4 | 5 | getter! previous : Base? 6 | @completion : Completion 7 | getter base_prefix : String 8 | @keymap = {} of String => Int32 9 | 10 | @function_data = [] of Completion::Function 11 | @key_data = {} of Int32 => Array(String) 12 | @arg_data = {} of Int32 => Int32 13 | @act_data = {} of Int32 => String? 14 | @cmd_data = {} of Int32 => String? 15 | @len_data = {} of Int32 => Int32 16 | @next_data = {} of Int32 => Array(String) 17 | @occur_data = {} of Int32 => Int32 18 | @tag_data = {} of Int32 => Array(String) 19 | @word_data = {} of Int32 => Array(String)? 20 | 21 | @header = %w() 22 | @constants = %w() 23 | @functions = %w() 24 | @next_libs = %w() 25 | @sections = %w() 26 | @result : String? 27 | 28 | def initialize(previous : Base, completion : Completion) 29 | @previous = previous 30 | initialize completion, previous.entry_point 31 | end 32 | 33 | def initialize(@completion : Completion, @base_prefix : String) 34 | end 35 | 36 | def zsh? 37 | @completion.type == :zsh 38 | end 39 | 40 | def next_completion_for(model) 41 | case @completion.type 42 | when :zsh 43 | model.zsh_completion 44 | else 45 | model.bash_completion 46 | end 47 | end 48 | 49 | @entry_point : String? 50 | def entry_point 51 | @entry_point ||= StringInflection.snake(first? ? @base_prefix : "#{@base_prefix}__#{model.name}") 52 | end 53 | 54 | @prefix : String? 55 | def prefix 56 | @prefix ||= "#{entry_point}___" 57 | end 58 | 59 | def model 60 | @completion.model 61 | end 62 | 63 | def first? 64 | previous?.nil? 65 | end 66 | 67 | def first 68 | first? ? self : previous.first 69 | end 70 | 71 | def result 72 | @result ||= make 73 | end 74 | 75 | def make 76 | add_header 77 | add_functions 78 | add_each_definition 79 | add_each_arg 80 | make_constants 81 | make_functions 82 | add_section @header, "\n\n" 83 | add_section @constants, "\n" 84 | add_section @functions, "\n\n" 85 | add_section @next_libs, "\n\n" 86 | @sections.join("\n\n") 87 | end 88 | 89 | def add_section(a, separator = "\n") 90 | @sections << a.join(separator) unless a.empty? 91 | end 92 | 93 | def add_header 94 | end 95 | 96 | def add_each_definition 97 | model.definitions.all.each do |kv| 98 | make_definition kv[1] 99 | end 100 | end 101 | 102 | def make_definition(df) 103 | return if df.is_a?(Definitions::Unknown) 104 | @keymap[df.key] = @keymap.size 105 | add_key df 106 | add_act df 107 | add_cmd df 108 | add_len df 109 | add_next df 110 | add_occur df 111 | add_word df 112 | add_tag df 113 | end 114 | 115 | def add_key(df) 116 | @key_data[@keymap[df.key]] = df.names 117 | end 118 | 119 | def add_len(df) 120 | @len_data[@keymap[df.key]] = df.completion_length(self) 121 | end 122 | 123 | def add_occur(df) 124 | @occur_data[@keymap[df.key]] = df.completion_max_occurs(self) 125 | end 126 | 127 | def add_word(df) 128 | @word_data[@keymap[df.key]] = df.completion_words(self) 129 | end 130 | 131 | def add_cmd(df) 132 | @cmd_data[@keymap[df.key]] = df.completion_command(self) 133 | end 134 | 135 | ACTIONS = %i( 136 | alias 137 | arrayvar 138 | binding 139 | builtin 140 | command 141 | directory 142 | disabled 143 | enabled 144 | export 145 | file 146 | function 147 | group 148 | helptopic 149 | hostname 150 | job 151 | keyword 152 | running 153 | service 154 | setopt 155 | shopt 156 | signal 157 | stopped 158 | user 159 | variable 160 | ) 161 | 162 | def add_act(df) 163 | @act_data[@keymap[df.key]] = if sym = df.completion_action(self) 164 | sym.to_s 165 | end 166 | end 167 | 168 | def add_next(df) 169 | if data = df.completion_next_models_by_value(self) 170 | if data.size > 0 171 | @next_data[@keymap[df.key]] = data.keys 172 | @function_data << Completion::Functions::Next.new(self, data) 173 | data.each do |k, v| 174 | @next_libs << next_completion_for(v).new_generator(self).result 175 | end 176 | end 177 | end 178 | end 179 | 180 | def add_tag(df) 181 | key = @keymap[df.key] 182 | @tag_data[key] = %w() 183 | @tag_data[key] << "term" if df.is_a?(Definitions::Terminator) 184 | @tag_data[key] << "opt" if df.is_a?(DefinitionMixins::Option) 185 | @tag_data[key] << "arg" if df.is_a?(DefinitionMixins::Argument) 186 | @tag_data[key] << "varg" if df.is_a?(Definitions::StringArrayArgument) 187 | @tag_data[key] << "stop" if df.stops? 188 | end 189 | 190 | def add_each_arg 191 | model.definitions.arguments.each_with_index do |kv, i| 192 | add_arg i, kv[1] 193 | end 194 | end 195 | 196 | def add_arg(i, df) 197 | @arg_data[i] = @keymap[df.key] 198 | end 199 | 200 | def make_constants 201 | make_constant :keys, @key_data {|v| matching_words(v)} 202 | make_constant :args, @arg_data {|v| v.to_s} 203 | make_constant :acts, @act_data {|v| string(v)} 204 | make_constant :cmds, @cmd_data {|v| string(v)} 205 | make_constant :lens, @len_data {|v| v.to_s} 206 | make_constant :nexts, @next_data {|v| matching_words(v)} 207 | make_constant :occurs, @occur_data {|v| v.to_s} 208 | make_constant :tags, @tag_data {|v| matching_words(v)} 209 | make_constant :words, @word_data {|v| matching_words(v)} 210 | end 211 | 212 | def make_constant(name, data) 213 | a = %w() 214 | (0..(@keymap.size-1)).each do |key| 215 | a << yield data[key]? 216 | end 217 | @constants << "#{prefix}#{name}=(" + a.join(" ") + ")" 218 | end 219 | 220 | def make_functions 221 | @function_data.each do |f| 222 | @functions << f.text 223 | end 224 | end 225 | 226 | def add_functions 227 | if first? 228 | @function_data << Completion::Functions::Act.new(self) 229 | @function_data << Completion::Functions::Add.new(self) 230 | @function_data << Completion::Functions::Any.new(self) 231 | @function_data << Completion::Functions::Cur.new(self) 232 | @function_data << Completion::Functions::End.new(self) 233 | @function_data << Completion::Functions::Found.new(self) 234 | @function_data << Completion::Functions::Inc.new(self) 235 | @function_data << Completion::Functions::Keyerr.new(self) 236 | @function_data << Completion::Functions::Word.new(self) 237 | end 238 | @function_data << Completion::Functions::Main.new(self) 239 | @function_data << Completion::Functions::Arg.new(self) 240 | @function_data << Completion::Functions::Key.new(self) 241 | @function_data << Completion::Functions::Len.new(self) 242 | @function_data << Completion::Functions::Ls.new(self) 243 | @function_data << Completion::Functions::Lskey.new(self) 244 | @function_data << Completion::Functions::Tag.new(self) 245 | end 246 | end 247 | end 248 | -------------------------------------------------------------------------------- /src/lib/completion_generators/bash.cr: -------------------------------------------------------------------------------- 1 | module Optarg::CompletionGenerators 2 | class Bash < Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /src/lib/completion_generators/zsh.cr: -------------------------------------------------------------------------------- 1 | module Optarg::CompletionGenerators 2 | class Zsh < Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /src/lib/definition_mixins.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module Optarg::DefinitionMixins 3 | end 4 | 5 | require "./definition_mixins/*" 6 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/argument.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module Argument 3 | macro included 4 | include ::Optarg::DefinitionMixins::Visit 5 | 6 | module ArgumentModule 7 | abstract def visitable?(parser) : Bool 8 | 9 | def completion_length(gen) 10 | 1 11 | end 12 | 13 | def completion_max_occurs(gen) 14 | 1 15 | end 16 | end 17 | 18 | include ArgumentModule 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/array_value.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module ArrayValue 3 | macro included 4 | include ::Optarg::DefinitionMixins::Value 5 | 6 | module ArrayValueModule 7 | def initialize_array_value(default, min : Int32?, any_item_of : ::Array(Typed::ElementValue) | ::Array(Typed::ElementType) | Nil) 8 | initialize_value default: default 9 | validations << new_minimum_length_of_array_validation(min) if min 10 | validations << new_element_inclusion_validation(any_item_of) if any_item_of 11 | end 12 | 13 | def fallback_value(parser) 14 | set_value parser, Typed::Type.new 15 | end 16 | 17 | def get_typed_element_values(parser) 18 | a = [] of Typed::ElementValue 19 | if v = get_value?(parser) 20 | v.each do |i| 21 | a << Typed::ElementValue.new(i) 22 | end 23 | end 24 | a 25 | end 26 | 27 | def value_required? 28 | validations.any? do |v| 29 | if v = v.as?(Validations::MinimumLengthOfArray) 30 | v.min > 0 31 | end 32 | end 33 | end 34 | 35 | module Validations 36 | class MinimumLengthOfArray < Validation 37 | getter min : Int32 38 | 39 | def initialize(@min) 40 | end 41 | 42 | def valid?(parser, df) 43 | df.get_value(parser).size >= min 44 | end 45 | end 46 | 47 | class ElementInclusion < Validation 48 | getter values : ::Array(Typed::ElementValue) 49 | 50 | def initialize(@values : ::Array(Typed::ElementValue)) 51 | end 52 | 53 | def initialize(values : ::Array(Typed::ElementType)) 54 | initialize values.map{|i| Typed::ElementValue.new(i)} 55 | end 56 | 57 | def valid?(parser, df) 58 | typed = df.get_typed_element_values(parser) 59 | typed.all?{|i| values.any?{|j| i == j } } 60 | end 61 | end 62 | end 63 | 64 | def minimum_length_of_array 65 | validations.each do |i| 66 | if v = i.as?(Validations::MinimumLengthOfArray) 67 | return v.min 68 | end 69 | end 70 | 0 71 | end 72 | 73 | def initialize_after_parse(parser) 74 | set_default_value_on_after_parse(parser) 75 | super 76 | end 77 | 78 | def set_default_value_on_after_parse(parser) 79 | a = get_value(parser) 80 | set_default_value parser if a.empty? 81 | end 82 | 83 | def completion_words(gen) 84 | super || begin 85 | a = \%w() 86 | validations.each do |v| 87 | if v = v.as?(Validations::ElementInclusion) 88 | v.values.each do |val| 89 | if s = val.string 90 | a << s 91 | end 92 | end 93 | end 94 | end 95 | a.uniq! 96 | end 97 | end 98 | end 99 | 100 | include ArrayValueModule 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/array_value_argument.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module ArrayValueArgument 3 | macro included 4 | include ::Optarg::DefinitionMixins::ValueArgument 5 | include ::Optarg::DefinitionMixins::ArrayValue 6 | 7 | module ArrayValueArgumentModule 8 | def validation_error_message_for_minimum_length_of_array(parser, validation) 9 | "The count of the #{metadata.display_name} arguments is #{get_value(parser).size}, but #{validation.min} or more is expected." 10 | end 11 | 12 | def validation_error_message_for_element_inclusion(parser, validation) 13 | "Each element of the #{metadata.display_name} argument must be one of #{validation.values.map{|i| i.metadata.string}.join(", ")}." 14 | end 15 | end 16 | 17 | include ArrayValueArgumentModule 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/array_value_option.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module ArrayValueOption 3 | macro included 4 | include ::Optarg::DefinitionMixins::ValueOption 5 | include ::Optarg::DefinitionMixins::ArrayValue 6 | 7 | module ArrayValueOptionModule 8 | def validation_error_message_for_minimum_length_of_array(parser, validation) 9 | "The count of the #{metadata.display_name} options is #{get_value(parser).size}, but #{validation.min} or more is expected." 10 | end 11 | 12 | def validation_error_message_for_element_inclusion(parser, validation) 13 | "Each element of the #{metadata.display_name} option must be one of #{validation.values.map{|i| i.metadata.string}.join(", ")}." 14 | end 15 | 16 | def visit_concatenated(parser, name) 17 | raise UnsupportedConcatenation.new(parser, self) 18 | end 19 | end 20 | 21 | include ArrayValueOptionModule 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/completion.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module Completion 3 | macro included 4 | module CompletionModule 5 | abstract def completion_length(gen) : Int32 6 | abstract def completion_max_occurs(gen) : Int32 7 | 8 | @completion_words : Array(String)? 9 | @completion_command : String? 10 | @completion_action : Symbol? 11 | 12 | def initialize_completion(complete) 13 | case complete 14 | when Array(String) 15 | @completion_words = complete 16 | when String 17 | @completion_command = complete 18 | when Symbol 19 | @completion_action = complete 20 | when Nil 21 | else 22 | raise "Unknown completion type: #{complete}" 23 | end 24 | end 25 | 26 | def completion_words(gen) : Array(String)? 27 | @completion_words 28 | end 29 | 30 | def completion_command(gen) : String? 31 | @completion_command 32 | end 33 | 34 | def completion_action(gen) : Symbol? 35 | @completion_action 36 | end 37 | 38 | def completion_next_models_by_value(gen) : Hash(String, ModelClass)? 39 | end 40 | end 41 | 42 | include CompletionModule 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/option.cr: -------------------------------------------------------------------------------- 1 | require "./visit" 2 | require "./visit_concatenated" 3 | 4 | module Optarg::DefinitionMixins 5 | module Option 6 | macro included 7 | include ::Optarg::DefinitionMixins::Visit 8 | include ::Optarg::DefinitionMixins::VisitConcatenated 9 | 10 | module OptionModule 11 | def completion_max_occurs(gen) 12 | 1 13 | end 14 | end 15 | 16 | include OptionModule 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/scalar_value.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module ScalarValue 3 | macro included 4 | include ::Optarg::DefinitionMixins::Value 5 | 6 | module ScalarValueModule 7 | def initialize_scalar_value(default, required : Bool?, any_of : ::Array(Typed::Value) | ::Array(Typed::Type) | Nil) 8 | initialize_value default 9 | require_value! if required 10 | any_value_of! any_of if any_of 11 | end 12 | 13 | def fallback_value(parser) 14 | set_default_value parser 15 | end 16 | 17 | def require_value! 18 | validations << new_existence_validation unless value_required? 19 | end 20 | 21 | def unrequire_value! 22 | validations.reject! do |v| 23 | v.is_a?(Validations::Existence) 24 | end 25 | end 26 | 27 | def any_value_of!(values) 28 | validations << new_inclusion_validation(values) 29 | require_value! 30 | end 31 | 32 | def value_required? 33 | validations.any? do |i| 34 | i.is_a?(Validations::Existence) 35 | end 36 | end 37 | 38 | module Validations 39 | class Existence < Validation 40 | def valid?(parser, df) 41 | df.get_typed_value(parser).exists? 42 | end 43 | end 44 | 45 | class Inclusion < Validation 46 | getter values : ::Array(Typed::Value) 47 | 48 | def initialize(@values : ::Array(Typed::Value)) 49 | end 50 | 51 | def initialize(values : ::Array(Typed::Type)) 52 | initialize values.map{|i| Typed::Value.new(i)} 53 | end 54 | 55 | def valid?(parser, df) 56 | typed = df.get_typed_value(parser) 57 | values.any?{|i| i == typed} 58 | end 59 | end 60 | end 61 | 62 | def completion_words(gen) 63 | super || begin 64 | a = \%w() 65 | validations.each do |v| 66 | if v = v.as?(Validations::Inclusion) 67 | v.values.each do |val| 68 | if s = val.string 69 | a << s 70 | end 71 | end 72 | end 73 | end 74 | a.uniq! 75 | end 76 | end 77 | end 78 | 79 | include ScalarValueModule 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/scalar_value_argument.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module ScalarValueArgument 3 | macro included 4 | include ::Optarg::DefinitionMixins::ScalarValue 5 | include ::Optarg::DefinitionMixins::ValueArgument 6 | 7 | module ScalarValueArgumentModule 8 | def initialize_scalar_value_argument(default, required, any_of) 9 | initialize_scalar_value default: default, required: required, any_of: any_of 10 | end 11 | 12 | def validation_error_message_for_existence(parser, validation) 13 | "The #{metadata.display_name} argument is required." 14 | end 15 | 16 | def validation_error_message_for_inclusion(parser, validation) 17 | "The #{metadata.display_name} argument must be one of #{validation.values.map{|i| i.metadata.string}.join(", ")}." 18 | end 19 | end 20 | 21 | include ScalarValueArgumentModule 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/scalar_value_option.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module ScalarValueOption 3 | macro included 4 | include ::Optarg::DefinitionMixins::ValueOption 5 | include ::Optarg::DefinitionMixins::ScalarValue 6 | 7 | module ScalarValueOptionModule 8 | def initialize_scalar_value_option(default, required, any_of) 9 | initialize_scalar_value default: default, required: required, any_of: any_of 10 | end 11 | 12 | def validation_error_message_for_existence(parser, validation) 13 | "The #{metadata.display_name} option is required." 14 | end 15 | 16 | def validation_error_message_for_inclusion(parser, validation) 17 | "The #{metadata.display_name} option must be one of #{validation.values.map{|i| i.metadata.string}.join(", ")}." 18 | end 19 | end 20 | 21 | include ScalarValueOptionModule 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/value.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module Value 3 | macro included 4 | module ValueModule 5 | getter! default_value : Typed::Value 6 | 7 | def initialize_value(default : Typed::Type | Typed::Value | Nil = nil) 8 | @default_value = Typed::Value.new(default) 9 | end 10 | 11 | def value_key 12 | key 13 | end 14 | 15 | def get_typed_value(parser) 16 | Typed::Value.new(get_value?(parser)) 17 | end 18 | 19 | def get_value(parser) 20 | get_value?(parser).as(Typed::Type) 21 | end 22 | 23 | def set_default_value(parser) 24 | set_value parser, default_value.dup_value! if default_value.exists? 25 | end 26 | 27 | getter validations = [] of Validation 28 | 29 | def validate(parser) 30 | validations.each{|i| i.validate(parser, self)} 31 | end 32 | end 33 | 34 | include ValueModule 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/value_argument.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module ValueArgument 3 | macro included 4 | include ::Optarg::DefinitionMixins::Argument 5 | 6 | module ValueArgumentModule 7 | def get_value?(parser) 8 | parser.args[Typed::Type][value_key]? 9 | end 10 | 11 | def set_value(parser, value) 12 | parser.args[Typed::Type][value_key] = value 13 | end 14 | end 15 | 16 | include ValueArgumentModule 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/value_option.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module ValueOption 3 | macro included 4 | include ::Optarg::DefinitionMixins::Option 5 | 6 | module ValueOptionModule 7 | def get_value?(parser) 8 | parser.args[Typed::Type][value_key]? 9 | end 10 | 11 | def set_value(parser, value) 12 | parser.args[Typed::Type][value_key] = value 13 | end 14 | end 15 | 16 | include ValueOptionModule 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/visit.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module Visit 3 | abstract def visit(parser) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/lib/definition_mixins/visit_concatenated.cr: -------------------------------------------------------------------------------- 1 | module Optarg::DefinitionMixins 2 | module VisitConcatenated 3 | abstract def visit_concatenated(parser, name) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/lib/definition_set.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | class DefinitionSet 4 | macro __set(types, list, array = nil) 5 | {% 6 | types = [types] unless types.class_name == "ArrayLiteral" 7 | a = %w() 8 | %} 9 | {% for e, i in types %} 10 | {% if i == 0 %} 11 | {% 12 | a << "if o = df.as?(#{e})" 13 | %} 14 | {% else %} 15 | {% 16 | a << "elsif o = df.as?(#{e})" 17 | %} 18 | {% end %} 19 | {% 20 | a << "#{list}[o.key] = o" 21 | a << "#{array} << o" if array 22 | %} 23 | {% end %} 24 | {% 25 | a << "end" 26 | %} 27 | {{a.join("\n").id}} 28 | end 29 | 30 | def <<(df : Definitions::Base) 31 | all[df.key] = df 32 | __set DefinitionMixins::Option, options 33 | __set DefinitionMixins::ValueOption, value_options 34 | __set DefinitionMixins::Argument, arguments, array: argument_list 35 | __set Definitions::Handler, handlers 36 | __set Definitions::Terminator, terminators 37 | __set DefinitionMixins::Value, values 38 | __set Definitions::StringArrayArgument, string_array_arguments 39 | __set Definitions::Unknown, unknowns 40 | end 41 | 42 | getter all = {} of String => Definitions::Base 43 | getter arguments = {} of String => DefinitionMixins::Argument 44 | getter options = {} of String => DefinitionMixins::Option 45 | getter value_options = {} of String => DefinitionMixins::ValueOption 46 | getter handlers = {} of String => Definitions::Handler 47 | getter terminators = {} of String => Definitions::Terminator 48 | getter unknowns = {} of String => Definitions::Unknown 49 | getter values = {} of String => DefinitionMixins::Value 50 | getter string_array_arguments = {} of String => Definitions::StringArrayArgument 51 | 52 | getter argument_list = [] of DefinitionMixins::Argument 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/lib/definitions.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module Optarg::Definitions 3 | end 4 | 5 | require "./definitions/base" 6 | require "./definitions/*" 7 | -------------------------------------------------------------------------------- /src/lib/definitions/base.cr: -------------------------------------------------------------------------------- 1 | require "../definition_mixins/completion" 2 | 3 | module Optarg::Definitions 4 | abstract class Base 5 | include DefinitionMixins::Completion 6 | 7 | getter names : Array(String) 8 | getter metadata : Metadata 9 | 10 | def initialize(names : String | Array(String), metadata : Metadata? = nil, stop : Bool? = nil, terminate : Bool? = nil, complete : String | Symbol | Array(String) | Nil = nil, unknown : Bool? = nil) 11 | @names = case names 12 | when String 13 | [names] 14 | else 15 | names 16 | end 17 | @metadata = metadata || Metadata.new 18 | @metadata.definition = self 19 | @stops = !!stop 20 | @terminates = !!terminate 21 | @unknown = !!unknown 22 | initialize_completion complete: (complete || "") 23 | end 24 | 25 | @stops : Bool? 26 | def stops? 27 | @stops.as(Bool) 28 | end 29 | 30 | @terminates : Bool? 31 | def terminates? 32 | @terminates.as(Bool) 33 | end 34 | 35 | @unknown : Bool? 36 | def unknown? 37 | @unknown.as(Bool) 38 | end 39 | 40 | def key 41 | @names[0] 42 | end 43 | 44 | def matches?(name) 45 | @names.includes?(name) 46 | end 47 | 48 | def subclassify(model) 49 | self 50 | end 51 | 52 | def initialize_before_parse(parser) 53 | end 54 | 55 | def initialize_after_parse(parser) 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /src/lib/definitions/bool_option.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | class BoolOption < Base 3 | include ValueTypes::Bool::Definition 4 | include DefinitionMixins::ScalarValueOption 5 | 6 | def initialize(names, metadata = nil, stop = nil, default = nil) 7 | super names, metadata: metadata, stop: stop 8 | initialize_scalar_value_option default: default, required: nil, any_of: nil 9 | end 10 | 11 | def visit(parser, name = nil) 12 | parser.args[Bool][value_key] = true 13 | Parser.new_node(parser[0..0], self) 14 | end 15 | 16 | def visit_concatenated(parser, name) 17 | visit parser, name 18 | end 19 | 20 | def completion_length(gen) 21 | 1 22 | end 23 | 24 | def completion_max_occurs(gen) 25 | default_value.get? == true ? 0 : 1 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /src/lib/definitions/handler.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | abstract class Handler < Base 3 | include DefinitionMixins::Option 4 | 5 | def completion_length(gen) 6 | 1 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/lib/definitions/not_option.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | class NotOption < Base 3 | include ValueTypes::Bool::Definition 4 | include DefinitionMixins::ScalarValueOption 5 | 6 | getter bool : BoolOption 7 | 8 | def initialize(names, @bool : BoolOption, metadata = nil, stop = nil, default = nil) 9 | super names, metadata: metadata, stop: stop 10 | initialize_scalar_value_option default: default, required: nil, any_of: nil 11 | end 12 | 13 | def value_key 14 | bool.value_key 15 | end 16 | 17 | def visit(parser, name = nil) 18 | parser.args[Bool][value_key] = false 19 | Parser.new_node(parser[0..0], self) 20 | end 21 | 22 | def visit_concatenated(parser, name) 23 | visit parser, name 24 | end 25 | 26 | def completion_length(gen) 27 | 1 28 | end 29 | 30 | def completion_max_occurs(gen) 31 | bool.default_value.get? == true ? 1 : 0 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/lib/definitions/string_argument.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | class StringArgument < Base 3 | include ValueTypes::String::Definition 4 | include DefinitionMixins::ScalarValueArgument 5 | 6 | def initialize(names, metadata = nil, stop = nil, default = nil, required = nil, any_of = nil, complete = nil) 7 | super names, metadata: metadata, stop: stop, complete: complete 8 | initialize_scalar_value_argument default: default, required: required, any_of: any_of 9 | end 10 | 11 | def visitable?(parser) 12 | !parser.args[Typed::Type].has_key?(value_key) 13 | end 14 | 15 | def visit(parser) 16 | parser.args[Typed::Type][value_key] = parser[0] 17 | parser.parsed_args << parser[0] 18 | Parser.new_node(parser[0..0], self) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/lib/definitions/string_array_argument.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | class StringArrayArgument < Base 3 | include ValueTypes::StringArray::Definition 4 | include DefinitionMixins::ArrayValueArgument 5 | 6 | def initialize(names, metadata = nil, default = nil, min = nil, any_item_of = nil, complete = nil) 7 | super names, metadata: metadata, complete: complete 8 | initialize_array_value default: default, min: min, any_item_of: any_item_of 9 | initialize_completion complete 10 | end 11 | 12 | def visitable?(parser) 13 | true 14 | end 15 | 16 | def visit(parser) 17 | parser.args[Typed::Type][value_key] << parser[0] 18 | parser.parsed_args << parser[0] 19 | Parser.new_node(parser[0..0], self) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /src/lib/definitions/string_array_option.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | class StringArrayOption < Base 3 | include ValueTypes::StringArray::Definition 4 | include DefinitionMixins::ArrayValueOption 5 | 6 | def initialize(names, metadata = nil, stop = nil, default = nil, min = nil, any_item_of = nil, complete = nil) 7 | super names, metadata: metadata, stop: stop, complete: complete 8 | initialize_array_value default: default, min: min, any_item_of: any_item_of 9 | end 10 | 11 | def visit(parser) 12 | raise MissingValue.new(parser, self, parser[0]) if parser.left < 2 13 | parser.args[Typed::Type][value_key] << parser[1] 14 | Parser.new_node(parser[0..1], self) 15 | end 16 | 17 | def completion_length(gen) 18 | 2 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/lib/definitions/string_option.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | class StringOption < Base 3 | include ValueTypes::String::Definition 4 | include DefinitionMixins::ScalarValueOption 5 | 6 | def initialize(names, metadata = nil, stop = nil, default = nil, required = nil, any_of = nil, complete = nil) 7 | super names, metadata: metadata, stop: stop, complete: complete 8 | initialize_scalar_value_option default: default, required: required, any_of: any_of 9 | end 10 | 11 | def visit(parser) 12 | raise MissingValue.new(parser, self, parser[0]) if parser.left < 2 13 | parser.args[String][value_key] = parser[1] 14 | Parser.new_node(parser[0..1], self) 15 | end 16 | 17 | def visit_concatenated(parser, name) 18 | raise UnsupportedConcatenation.new(parser, self) 19 | end 20 | 21 | def completion_length(gen) 22 | 2 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /src/lib/definitions/terminator.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | class Terminator < Base 3 | include DefinitionMixins::Visit 4 | 5 | def initialize(names, metadata = nil) 6 | super names, metadata: metadata, terminate: true 7 | end 8 | 9 | def visit(parser) 10 | Parser.new_node(parser[0..0], self) 11 | end 12 | 13 | def completion_length(gen) 14 | 1 15 | end 16 | 17 | def completion_max_occurs(gen) 18 | 1 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/lib/definitions/unknown.cr: -------------------------------------------------------------------------------- 1 | module Optarg::Definitions 2 | class Unknown < Base 3 | def initialize(metadata = nil) 4 | super "@unknown", metadata: metadata, unknown: true 5 | end 6 | 7 | def completion_length(gen) 8 | 1 9 | end 10 | 11 | def completion_max_occurs(gen) 12 | 1 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/lib/exceptions.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class ParsingError < Exception 3 | # :nodoc: 4 | getter parser : Parser 5 | 6 | # :nodoc: 7 | def initialize(@parser, message) 8 | super message 9 | end 10 | end 11 | 12 | # :nodoc: 13 | class UnknownOption < ParsingError 14 | def initialize(parser, name) 15 | super parser, "The #{name} option is unknown." 16 | end 17 | end 18 | 19 | # :nodoc: 20 | class MissingValue < ParsingError 21 | getter option : DefinitionMixins::Option 22 | 23 | def initialize(parser, @option, name) 24 | super parser, "The #{name} option has no value." 25 | end 26 | end 27 | 28 | # :nodoc: 29 | class UnsupportedConcatenation < ParsingError 30 | getter option : DefinitionMixins::Option 31 | 32 | def initialize(parser, option : DefinitionMixins::Option) 33 | @option = option 34 | super parser, "The #{option.metadata.display_name} option can not be concatenated." 35 | end 36 | end 37 | 38 | class ValidationError < ParsingError 39 | # :nodoc: 40 | def initialize(parser, message) 41 | super parser, message 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /src/lib/metadata.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | class Metadata 4 | @definition : Definitions::Base? 5 | def definition=(value) 6 | @definition = value 7 | end 8 | 9 | def definition 10 | @definition.as(Definitions::Base) 11 | end 12 | 13 | getter tags = %w() 14 | 15 | def display_name 16 | definition.key 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/lib/model.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # The base of model classes. 3 | abstract class Model 4 | macro inherited 5 | {% 6 | last_name = @type.name.split("::").last.id 7 | %} 8 | {% if @type.superclass == ::Optarg::Model %} 9 | {% 10 | is_root = true 11 | supermodel_class = "Optarg::ModelClass".id 12 | superparser = "Optarg::Parser".id 13 | superlast_concrete = nil 14 | %} 15 | {% else %} 16 | {% 17 | is_root = false 18 | supermodel_class = "#{@type.superclass}::Class".id 19 | superparser = "#{@type.superclass}::Parser".id 20 | superclass_id = @type.superclass.name.underscore.split("_").join("__").id 21 | superlast_concrete = @type.superclass.constant("LAST_CONCRETE___#{superclass_id}") 22 | %} 23 | {% end %} 24 | 25 | @@__klass = ::Optarg::ModelClass.new( 26 | supermodel: {{ is_root ? nil : "::#{@type.superclass}.__klass".id }}, 27 | name: {{@type.name.split("::")[-1].underscore}}, 28 | abstract: {{@type.abstract?}} 29 | ) 30 | # :nodoc: 31 | def self.__klass; @@__klass; end 32 | 33 | {% unless is_root %} 34 | ::{{@type.superclass}}.__klass.definitions.all.each do |kv| 35 | @@__klass.definitions << kv[1].subclassify(::{{@type}}) 36 | end 37 | {% end %} 38 | 39 | # The dedicated Optarg::Parser subclass for the `{{last_name}}` class. 40 | # 41 | # This class is automatically defined by the optarg library. 42 | {% if @type.abstract? %} 43 | abstract class Parser < ::{{superparser}} 44 | inherit_callback_group :validate, ::Proc(::{{@type}}, ::Nil) 45 | end 46 | {% else %} 47 | class Parser < ::{{superparser}} 48 | inherit_callback_group :validate, ::Proc(::{{@type}}, ::Nil) 49 | 50 | # Returns a target model instance. 51 | def data : ::{{@type}} 52 | @data.as(::{{@type}}) 53 | end 54 | end 55 | 56 | # :nodoc: 57 | def __parser 58 | (@__parser.var ||= Parser.new(self)).as(Parser) 59 | end 60 | 61 | # :nodoc: 62 | class DefinitionContext 63 | @definition : ::Optarg::Definitions::Base 64 | 65 | def initialize(@definition) 66 | end 67 | 68 | def on_validate(&block : (::Optarg::ValidationContext, ::{{@type}}) ->) 69 | this = self 70 | Parser.on_validate do |parser| 71 | block.call ::Optarg::ValidationContext.new(parser, @definition), parser.data.as(::{{@type}}) 72 | nil 73 | end 74 | end 75 | end 76 | 77 | # :nodoc: 78 | def self.__with_definition(df) 79 | yield DefinitionContext.new(df) 80 | end 81 | {% end %} 82 | end 83 | 84 | # :nodoc: 85 | def __klass; self.class.__klass; end 86 | 87 | @__parser = Util::Var(Parser).new 88 | 89 | # :nodoc: 90 | getter __argv : Array(String) 91 | 92 | # :nodoc: 93 | def initialize(@__argv) 94 | end 95 | 96 | # :nodoc: 97 | def self.__parse(argv, *args) 98 | new(argv, *args).tap do |o| 99 | o.__parse 100 | end 101 | end 102 | 103 | # Creates a new model instance and parses the *argv* arguments. 104 | # 105 | # Returns the created instance. 106 | def self.parse(argv : Array(String), *args) 107 | __parse(argv, *args) 108 | end 109 | 110 | # :nodoc: 111 | def __parse 112 | __parser.parse 113 | end 114 | 115 | # Returns an array that contains nameless argument values. 116 | def nameless_args; __parser.nameless_args; end 117 | 118 | # Returns an array that contains unparsed argument values. 119 | def unparsed_args; __parser.unparsed_args; end 120 | 121 | # Returns a value hash for String-type options and arguments. 122 | def [](klass : String.class) 123 | __parser.args[klass] 124 | end 125 | 126 | # Returns a value hash for Bool-type options and arguments. 127 | def [](klass : Bool.class) 128 | __parser.args[klass] 129 | end 130 | 131 | # Returns a value hash for Array(String)-type options and arguments. 132 | def [](klass : Array(String).class) 133 | __parser.args[klass] 134 | end 135 | 136 | # Returns an argument value at the *index*. 137 | def [](index : Int32) 138 | __parser.parsed_args[index] 139 | end 140 | 141 | # Returns an argument value at the *index*. 142 | # 143 | # Returns nil if the *index* is out of range. 144 | def []?(index : Int32) 145 | __parser.parsed_args[index]? 146 | end 147 | 148 | # Iterates argument values. 149 | def each 150 | __parser.parsed_args.each do |i| 151 | yield i 152 | end 153 | end 154 | 155 | # :nodoc: 156 | def self.__with_self(*args) 157 | with self yield *args 158 | end 159 | end 160 | end 161 | 162 | require "./model/*" 163 | -------------------------------------------------------------------------------- /src/lib/model/dsl.cr: -------------------------------------------------------------------------------- 1 | require "./dsl/*" 2 | -------------------------------------------------------------------------------- /src/lib/model/dsl/arg.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # Defines a String argument model item. 4 | macro arg(names, metadata = nil, stop = nil, default = nil, required = nil, any_of = nil, complete = nil, _mixin = nil, &block) 5 | __define_static_value :argument, :nilable, ::Optarg::Definitions::StringArgument, {{names}}, nil, {{_mixin}} do |klass| 6 | arg = klass.new({{names}}, metadata: {{metadata}}, stop: {{stop}}, required: {{required}}, default: {{default}}, any_of: {{any_of}}, complete: {{complete}}) 7 | @@__klass.definitions << arg 8 | {% if block %} 9 | __with_definition(option) {{block}} 10 | {% end %} 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/lib/model/dsl/arg_array.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # Defines an Array(String) argument model item. 4 | macro arg_array(names, metadata = nil, default = nil, min = nil, any_item_of = nil, complete = nil, _mixin = nil, &block) 5 | __define_static_value :argument, :array, ::Optarg::Definitions::StringArrayArgument, {{names}}, nil, {{_mixin}} do |klass| 6 | option = klass.new({{names}}, metadata: {{metadata}}, default: {{default}}, min: {{min}}, any_item_of: {{any_item_of}}, complete: {{complete}}) 7 | @@__klass.definitions << option 8 | {% if block %} 9 | __with_definition(option) {{block}} 10 | {% end %} 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/lib/model/dsl/array.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # Defines an Array(String) option model item. 4 | macro array(names, metadata = nil, default = nil, min = nil, any_item_of = nil, complete = nil, _mixin = nil, &block) 5 | __define_static_value :option, :array, ::Optarg::Definitions::StringArrayOption, {{names}}, nil, {{_mixin}} do |klass| 6 | option = klass.new({{names}}, metadata: {{metadata}}, default: {{default}}, min: {{min}}, any_item_of: {{any_item_of}}, complete: {{complete}}) 7 | @@__klass.definitions << option 8 | {% if block %} 9 | __with_definition(option) {{block}} 10 | {% end %} 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/lib/model/dsl/bool.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # Defines a Bool option model item. 4 | macro bool(names, metadata = nil, stop = nil, default = nil, not = nil, _mixin = nil, &block) 5 | __define_static_value :option, :predicate, ::Optarg::Definitions::BoolOption, {{names}}, nil, {{_mixin}} do |klass| 6 | option = klass.new({{names}}, metadata: {{metadata}}, stop: {{stop}}, default: {{default}}) 7 | @@__klass.definitions << option 8 | {% if not %} 9 | not = ::Optarg::Definitions::NotOption.new({{not}}, option, metadata: nil, stop: {{stop}}) 10 | @@__klass.definitions << not 11 | {% end %} 12 | {% if block %} 13 | __with_definition(option) {{block}} 14 | {% end %} 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/lib/model/dsl/handler.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # Defines a handler model item. 4 | macro on(names, metadata = nil, stop = nil, &block) 5 | __define_static_handler nil, {{names}} {{block}} 6 | %handler = __create_static_handler({{names}}, metadata: {{metadata}}, stop: {{stop}}) 7 | @@__klass.definitions << %handler 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/lib/model/dsl/string.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # Defines a String option model item. 4 | macro string(names, metadata = nil, stop = nil, default = nil, required = nil, any_of = nil, complete = nil, _mixin = nil, &block) 5 | __define_static_value :option, :nilable, ::Optarg::Definitions::StringOption, {{names}}, nil, {{_mixin}} do |klass| 6 | option = klass.new({{names}}, metadata: {{metadata}}, stop: {{stop}}, default: {{default}}, required: {{required}}, any_of: {{any_of}}, complete: {{complete}}) 7 | @@__klass.definitions << option 8 | {% if block %} 9 | __with_definition(option) {{block}} 10 | {% end %} 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /src/lib/model/dsl/terminator.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # Defines a terminator model item. 4 | macro terminator(names, metadata = nil) 5 | %term = ::Optarg::Definitions::Terminator.new({{names}}, metadata: {{metadata}}) 6 | @@__klass.definitions << %term 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/lib/model/dsl/unknown.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # Defines an unknown model item. 4 | macro unknown(metadata = nil) 5 | %unknown = ::Optarg::Definitions::Unknown.new(metadata: {{metadata}}) 6 | @@__klass.definitions << %unknown 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/lib/model/macros.cr: -------------------------------------------------------------------------------- 1 | require "./macros/*" 2 | -------------------------------------------------------------------------------- /src/lib/model/macros/handler.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # :nodoc: 4 | macro __define_static_handler(type, names, &block) 5 | {% 6 | names = [names] unless names.class_name == "ArrayLiteral" 7 | method_names = names.map{|i| i.split("=")[0].gsub(/^-*/, "").gsub(/-/, "_")} 8 | name = method_names[0].id 9 | df_class = "Handler__#{name}".id 10 | %} 11 | 12 | # :nodoc: 13 | def __call_handler_for__{{name}} 14 | {{block.body}} 15 | end 16 | 17 | # :nodoc: 18 | class {{df_class}} < ::Optarg::Definitions::Handler 19 | def visit(parser) 20 | if data = parser.data.as?(::{{@type}}) 21 | data.__call_handler_for__{{name}} 22 | Parser.new_node(parser[0..0], self) 23 | end 24 | end 25 | 26 | def visit_concatenated(parser, name) 27 | visit parser 28 | end 29 | end 30 | end 31 | 32 | # :nodoc: 33 | macro __create_static_handler(names, metadata, stop) 34 | {% 35 | names = [names] unless names.class_name == "ArrayLiteral" 36 | method_names = names.map{|i| i.split("=")[0].gsub(/^-*/, "").gsub(/-/, "_")} 37 | name = method_names[0].id 38 | df_class = "Handler__#{name}".id 39 | %} 40 | {{df_class}}.new({{names}}, metadata: {{metadata}}, stop: {{stop}}) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /src/lib/model/macros/value.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | class Model 3 | # :nodoc: 4 | macro __define_static_value(kind, access_type, metaclass, names, value_key, _mixin, &block) 5 | {% 6 | kind = kind.id 7 | metaclass = metaclass.resolve 8 | names = [names] unless names.class_name == "ArrayLiteral" 9 | method_names = names.map{|i| i.split("=")[0].gsub(/^-*/, "").gsub(/-/, "_").id} 10 | value_key = value_key || names[0] 11 | key = names[0].id 12 | model_reserved = (::Optarg::Model.methods + ::Reference.methods + ::Object.methods).map{|i| i.name} 13 | type_id = @type.name.split("(")[0].split("::").join("_").id 14 | snake_type_id = type_id.underscore 15 | definer = "#{snake_type_id}__define_static_#{kind}__#{method_names[0]}".id 16 | %} 17 | 18 | {% if kind == :argument %} 19 | {% 20 | value_name = key.upcase 21 | container_method = "self".id 22 | df_getter = "#{method_names[0]}_arg".id 23 | %} 24 | {% else %} 25 | {% 26 | value_name = key 27 | container_method = "self".id 28 | df_getter = "#{method_names[0]}_option".id 29 | %} 30 | {% end %} 31 | 32 | {% for method_name, index in method_names %} 33 | {% if access_type == :predicate %} 34 | {% unless model_reserved.includes?("#{method_name}?".id) %} 35 | # Returns the {{value_name}} {{kind.id}} value. 36 | # 37 | # This method is automatically defined by the optarg library. 38 | def {{method_name}}? 39 | !!{{container_method}}[::{{metaclass}}::Typed::Type][{{value_key}}]? 40 | end 41 | {% end %} 42 | {% end %} 43 | {% if access_type == :nilable || access_type == :array %} 44 | {% unless model_reserved.includes?(method_name) %} 45 | # Returns the {{value_name}} {{kind.id}} value. 46 | # 47 | # This method is automatically defined by the optarg library. 48 | def {{method_name}} 49 | {{container_method}}[::{{metaclass}}::Typed::Type][{{value_key}}] 50 | end 51 | {% end %} 52 | {% end %} 53 | {% if access_type == :nilable %} 54 | {% unless model_reserved.includes?("#{method_name}?".id) %} 55 | # Returns the {{value_name}} {{kind.id}} value. 56 | # 57 | # Returns nil, if the value is undefined. 58 | # 59 | # This method is automatically defined by the optarg library. 60 | def {{method_name}}? 61 | {{container_method}}[::{{metaclass}}::Typed::Type][{{value_key}}]? 62 | end 63 | {% end %} 64 | {% end %} 65 | {% end %} 66 | 67 | ::{{@type}}.__with_self(::{{metaclass}}) {{block}} 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /src/lib/model_class.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | class ModelClass 4 | property name : String 5 | getter? abstract : Bool 6 | getter! supermodel : ModelClass? 7 | getter definitions = DefinitionSet.new 8 | 9 | def initialize(@supermodel, @name, @abstract) 10 | end 11 | 12 | def supermodel 13 | supermodel?.not_nil! 14 | end 15 | 16 | @bash_completion : Completion? 17 | def bash_completion 18 | @bash_completion ||= Completion.new(:bash, self) 19 | end 20 | 21 | @zsh_completion : Completion? 22 | def zsh_completion 23 | @zsh_completion ||= Completion.new(:zsh, self) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /src/lib/parser.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | abstract class Parser 3 | ::Callback.enable 4 | define_callback_group :validate, Proc(Model, Nil) 5 | 6 | # :nodoc: 7 | alias Node = NamedTuple(args: Array(String), definitions: Array(Definitions::Base)) 8 | 9 | # Returns a target model instance. 10 | getter data : Model 11 | 12 | # :nodoc: 13 | getter parsed_nodes = [] of Node 14 | 15 | # :nodoc: 16 | getter nameless_args = %w() 17 | 18 | # :nodoc: 19 | getter parsed_args = %w() 20 | 21 | # :nodoc: 22 | getter unparsed_args = %w() 23 | 24 | @argument_index = 0 25 | # :nodoc: 26 | getter index = 0 27 | 28 | # :nodoc: 29 | def initialize(data) 30 | @data = data 31 | @args = ValueContainer.new(self) 32 | end 33 | 34 | @args : ValueContainer? 35 | # :nodoc: 36 | def args 37 | @args.not_nil! 38 | end 39 | 40 | # :nodoc: 41 | def input_args 42 | data.__argv 43 | end 44 | 45 | # :nodoc: 46 | def self.new_node(args = %w(), *definitions) 47 | node = {args: args, definitions: [] of Definitions::Base} 48 | definitions.each do |df| 49 | node[:definitions] << df 50 | end 51 | node 52 | end 53 | 54 | @definitions : DefinitionSet? 55 | # :nodoc: 56 | def definitions 57 | @definitions ||= data.__klass.definitions 58 | end 59 | 60 | # :nodoc: 61 | def stopped? 62 | return false if parsed_nodes.size == 0 63 | return parsed_nodes.last[:definitions].any?{|i| i.stops? || i.terminates? || i.unknown?} 64 | end 65 | 66 | on_validate do |o| 67 | o.definitions.values.each do |kv| 68 | kv[1].validate(o) 69 | end 70 | end 71 | 72 | # :nodoc: 73 | def parse 74 | definitions.all.each do |kv| 75 | kv[1].initialize_before_parse(self) 76 | end 77 | resume 78 | definitions.all.each do |kv| 79 | kv[1].initialize_after_parse(self) 80 | end 81 | run_callbacks_for_validate(data) do 82 | end 83 | end 84 | 85 | # :nodoc: 86 | def eol? 87 | @index == input_args.size 88 | end 89 | 90 | # :nodoc: 91 | def resume 92 | until eol? || stopped? 93 | visit 94 | end 95 | ensure 96 | @unparsed_args = self[0..-1] if left > 0 97 | @index = input_args.size 98 | end 99 | 100 | # :nodoc: 101 | def visit 102 | arg = self[0] 103 | if visit_terminator 104 | return 105 | end 106 | if arg =~ /^-\w\w/ 107 | visit_concatenated_options 108 | return 109 | end 110 | if arg =~ /^-/ 111 | begin 112 | visit_option 113 | return 114 | rescue ex : UnknownOption 115 | raise ex if definitions.unknowns.empty? 116 | end 117 | end 118 | visit_argument 119 | end 120 | 121 | # :nodoc: 122 | def visit_terminator 123 | name = self[0] 124 | if node = find_with_def(definitions.terminators, self[0]){|df| df.visit(self)} 125 | parsed_nodes << node 126 | @index += node[:args].size 127 | end 128 | end 129 | 130 | # :nodoc: 131 | def find_with_def(dfs, name) 132 | dfs.each do |kv| 133 | next unless kv[1].matches?(name) 134 | node = yield kv[1] 135 | return node if node 136 | end 137 | nil 138 | end 139 | 140 | # :nodoc: 141 | def visit_concatenated_options 142 | names = self[0][1..-1].split("").map{|i| "-#{i}"} 143 | node = Parser.new_node([self[0]]) 144 | names.each do |name| 145 | if nd = find_with_def(definitions.options, name){|df| df.visit_concatenated(self, name)} 146 | node[:definitions] << nd[:definitions][0] 147 | else 148 | raise UnknownOption.new(self, name) 149 | end 150 | end 151 | parsed_nodes << node 152 | @index += 1 153 | end 154 | 155 | # :nodoc: 156 | def visit_option 157 | name = self[0] 158 | if node = find_with_def(definitions.options, name){|df| df.visit(self)} 159 | parsed_nodes << node 160 | @index += node[:args].size 161 | else 162 | raise UnknownOption.new(self, self[0]) 163 | end 164 | end 165 | 166 | # :nodoc: 167 | def visit_argument 168 | if definitions.unknowns.empty? 169 | visit_argument2 170 | else 171 | visit_unknown 172 | end 173 | end 174 | 175 | # :nodoc: 176 | def visit_argument2 177 | while @argument_index < definitions.arguments.size 178 | df = definitions.argument_list[@argument_index] 179 | unless df.visitable?(self) 180 | @argument_index += 1 181 | next 182 | end 183 | node = df.visit(self) 184 | @parsed_nodes << node 185 | @index += node[:args].size 186 | return 187 | end 188 | arg = self[0] 189 | @nameless_args << arg 190 | @parsed_args << arg 191 | @parsed_nodes << Parser.new_node([arg]) 192 | @index += 1 193 | end 194 | 195 | # :nodoc: 196 | def visit_unknown 197 | node = Parser.new_node(%w(), definitions.unknowns.first[1]) 198 | @parsed_nodes << node 199 | end 200 | 201 | # :nodoc: 202 | def [](*args) 203 | input_args[@index..-1][*args] 204 | end 205 | 206 | # :nodoc: 207 | def left 208 | input_args.size - @index 209 | end 210 | 211 | # Creates and raises a new `ValidationError` with the *message*. 212 | def invalidate!(message : String) 213 | invalidate! ValidationError.new(self, message) 214 | end 215 | 216 | # :nodoc: 217 | def invalidate!(ex : ValidationError) 218 | raise ex 219 | end 220 | end 221 | end 222 | -------------------------------------------------------------------------------- /src/lib/util.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module Optarg::Util 3 | end 4 | 5 | require "./util/*" 6 | -------------------------------------------------------------------------------- /src/lib/util/var.cr: -------------------------------------------------------------------------------- 1 | module Optarg::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/lib/validation_context.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | class ValidationContext 4 | @parser : Parser 5 | @definition : Definitions::Base 6 | 7 | def initialize(@parser, @definition) 8 | end 9 | 10 | def validate_element_inclusion(*args) 11 | df = @definition 12 | if df.responds_to?(:validate_element_inclusion) 13 | df.validate_element_inclusion @parser, *args 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/lib/value.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | abstract class Value(T) 4 | include ::Comparable(Value(T)) 5 | 6 | getter metadata : ValueMetadata(T) 7 | 8 | def initialize(@value = nil, metadata = nil) 9 | @metadata = metadata || ValueMetadata(T).new 10 | @metadata.value = self 11 | end 12 | 13 | @value : T? 14 | def get? 15 | @value 16 | end 17 | 18 | def get 19 | @value.as(T) 20 | end 21 | 22 | def set(value : T?) 23 | @value = value 24 | end 25 | 26 | def exists? 27 | !@value.nil? 28 | end 29 | 30 | def dup_value! 31 | self.class.dup_value! @value 32 | end 33 | 34 | def dup_value 35 | self.class.dup_value @value 36 | end 37 | 38 | def self.dup_value!(value) 39 | dup_value(value).as(T) 40 | end 41 | 42 | def self.dup_value(value) 43 | if value.nil? 44 | nil 45 | elsif value.responds_to?(:dup) 46 | value.dup 47 | else 48 | value 49 | end 50 | end 51 | 52 | def <=>(other : Value(T)) 53 | if get?.nil? 54 | other.get?.nil? ? 0 : -1 55 | else 56 | other.get?.nil? ? 1 : compare_to(other) 57 | end 58 | end 59 | 60 | def string 61 | self.class.encode_to_string(@value) 62 | end 63 | 64 | abstract def compare_to(other) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /src/lib/value_container.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | class ValueContainer 4 | getter strings : ValueTypes::String::ValueHash 5 | getter bools : ValueTypes::Bool::ValueHash 6 | getter string_arrays : ValueTypes::StringArray::ValueHash 7 | 8 | def initialize(@parser : Parser) 9 | @strings = ValueTypes::String::ValueHash.new(parser) 10 | @bools = ValueTypes::Bool::ValueHash.new(parser) 11 | @string_arrays = ValueTypes::StringArray::ValueHash.new(parser) 12 | end 13 | 14 | def [](klass : String.class) 15 | @strings 16 | end 17 | 18 | def [](klass : Bool.class) 19 | @bools 20 | end 21 | 22 | def [](klass : Array(String).class) 23 | @string_arrays 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /src/lib/value_hash.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | abstract class ValueHash(V) 4 | @raw = {} of String => V 5 | forward_missing_to @raw 6 | 7 | @fallbacked = {} of String => Bool 8 | @parser : Parser 9 | 10 | def initialize(@parser) 11 | end 12 | 13 | def ==(other : Hash) 14 | @raw == other 15 | end 16 | 17 | def [](key) 18 | fallback key 19 | @raw[key] 20 | end 21 | 22 | def []?(key) 23 | fallback key 24 | @raw[key]? 25 | end 26 | 27 | def fallback(key) 28 | return if @raw.has_key?(key) 29 | return if @fallbacked.has_key?(key) 30 | @fallbacked[key] = true 31 | if fb = @parser.definitions.values[key]? 32 | fb.fallback_value @parser 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /src/lib/value_metadata.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | class ValueMetadata(T) 4 | @value : Value(T)? 5 | 6 | def value 7 | @value.as(Value(T)) 8 | end 9 | 10 | def value=(value : Value(T)) 11 | @value = value 12 | end 13 | 14 | def string 15 | value.get?.to_s 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/lib/value_types.cr: -------------------------------------------------------------------------------- 1 | # :nodoc: 2 | module Optarg::ValueTypes 3 | end 4 | 5 | require "./value_types/*" 6 | -------------------------------------------------------------------------------- /src/lib/value_types/base.cr: -------------------------------------------------------------------------------- 1 | require "../definitions/base" 2 | 3 | module Optarg::ValueTypes 4 | abstract class Base 5 | macro __concrete(t, et = nil) 6 | {% 7 | is_root = @type.superclass.name == "Optarg::ValueTypes::Base" 8 | %} 9 | alias Type = {{t}} 10 | 11 | class Value < ::Optarg::Value({{t}}) 12 | end 13 | 14 | class ValueHash < ::Optarg::ValueHash({{t}}) 15 | end 16 | 17 | {% if et %} 18 | alias ElementType = {{et}} 19 | alias ElementValue = ::Optarg::ValueTypes::{{et.id}}::Value 20 | {% end %} 21 | 22 | module Definition 23 | macro included 24 | ::Optarg::ValueTypes::Base.__include_definition ::{{@type}}, \{{@type}} 25 | end 26 | end 27 | end 28 | 29 | macro __include_definition(value_type, type) 30 | {% 31 | value_type = value_type.resolve 32 | type = type.resolve 33 | is_root = type.superclass.name == "Optarg::Definitions::Base" 34 | superlocal = type.superclass.name.split("::").last.id 35 | supertyped = "#{type.superclass}::Typed".id 36 | %} 37 | 38 | {% if is_root %} 39 | alias Typed = ::{{value_type}} 40 | 41 | abstract class Validation < ::Optarg::ValueValidation 42 | macro inherited 43 | \{% 44 | is_root = @type.superclass.name == "{{type}}::Validation" 45 | local = @type.name.split("::").last.id 46 | snake = local.underscore.id 47 | validator = "validate_#{snake}".id 48 | constructor = "new_#{snake}_validation".id 49 | %}\ 50 | 51 | \{% if is_root %} 52 | def new_error(parser, df, message) 53 | Error.new(parser, df, self, message) 54 | end 55 | 56 | # :nodoc: 57 | class Error < ::Optarg::ValidationError 58 | getter definition : ::{{type}} 59 | getter validation : ::\{{@type}} 60 | 61 | def initialize(parser, @definition, @validation, message) 62 | super parser, message 63 | end 64 | end 65 | 66 | def validate(parser, df) 67 | unless valid?(parser, df) 68 | message = df.validation_error_message_for_\{{snake}}(parser, self) 69 | raise new_error(parser, df, message) 70 | end 71 | end 72 | 73 | class ::{{type}} 74 | def \{{constructor}}(*args) 75 | Validations::\{{local}}.new(*args) 76 | end 77 | 78 | def \{{validator}}(parser, *args) 79 | ::{{type}}::Validations::\{{local}}.new(*args).validate(parser, self) 80 | end 81 | end 82 | \{% end %} 83 | end 84 | end 85 | {% end %} 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /src/lib/value_types/bool.cr: -------------------------------------------------------------------------------- 1 | module Optarg::ValueTypes 2 | class Bool < Base 3 | __concrete ::Bool 4 | 5 | class Value 6 | def self.encode_to_string(b) 7 | if b.nil? 8 | nil 9 | else 10 | b.to_s 11 | end 12 | end 13 | 14 | def compare_to(other) 15 | (get ? 1 : 0) <=> (other.get ? 1 : 0) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/lib/value_types/string.cr: -------------------------------------------------------------------------------- 1 | module Optarg::ValueTypes 2 | class String < Base 3 | __concrete ::String 4 | 5 | class Value 6 | def self.encode_to_string(b) 7 | if b.nil? 8 | nil 9 | else 10 | b.to_s 11 | end 12 | end 13 | 14 | def self.decode(s) 15 | s 16 | end 17 | 18 | def compare_to(other) 19 | get <=> other.get 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /src/lib/value_types/string_array.cr: -------------------------------------------------------------------------------- 1 | module Optarg::ValueTypes 2 | class StringArray < Base 3 | __concrete Array(::String), et: ::String 4 | 5 | class Value 6 | def compare_to(other) 7 | raise "Can't compare." 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/lib/value_validation.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | # :nodoc: 3 | abstract class ValueValidation 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/optarg.cr: -------------------------------------------------------------------------------- 1 | require "callback" 2 | require "string_inflection/snake" 3 | require "./version" 4 | require "./lib/exceptions" 5 | require "./lib/value" 6 | require "./lib/value_hash" 7 | require "./lib/value_types" 8 | require "./lib/value_validation" 9 | require "./lib/*" 10 | -------------------------------------------------------------------------------- /src/version.cr: -------------------------------------------------------------------------------- 1 | module Optarg 2 | VERSION = "0.6.0" 3 | end 4 | --------------------------------------------------------------------------------