command_mapper 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +27 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +25 -0
  7. data/Gemfile +15 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +369 -0
  10. data/Rakefile +12 -0
  11. data/commnad_mapper.gemspec +61 -0
  12. data/gemspec.yml +23 -0
  13. data/lib/command_mapper/arg.rb +75 -0
  14. data/lib/command_mapper/argument.rb +142 -0
  15. data/lib/command_mapper/command.rb +606 -0
  16. data/lib/command_mapper/exceptions.rb +19 -0
  17. data/lib/command_mapper/option.rb +282 -0
  18. data/lib/command_mapper/option_value.rb +21 -0
  19. data/lib/command_mapper/sudo.rb +73 -0
  20. data/lib/command_mapper/types/enum.rb +35 -0
  21. data/lib/command_mapper/types/hex.rb +82 -0
  22. data/lib/command_mapper/types/input_dir.rb +35 -0
  23. data/lib/command_mapper/types/input_file.rb +35 -0
  24. data/lib/command_mapper/types/input_path.rb +29 -0
  25. data/lib/command_mapper/types/key_value.rb +131 -0
  26. data/lib/command_mapper/types/key_value_list.rb +45 -0
  27. data/lib/command_mapper/types/list.rb +90 -0
  28. data/lib/command_mapper/types/map.rb +64 -0
  29. data/lib/command_mapper/types/num.rb +50 -0
  30. data/lib/command_mapper/types/str.rb +85 -0
  31. data/lib/command_mapper/types/type.rb +102 -0
  32. data/lib/command_mapper/types.rb +6 -0
  33. data/lib/command_mapper/version.rb +4 -0
  34. data/lib/command_mapper.rb +2 -0
  35. data/spec/arg_spec.rb +137 -0
  36. data/spec/argument_spec.rb +513 -0
  37. data/spec/commnad_spec.rb +1175 -0
  38. data/spec/exceptions_spec.rb +14 -0
  39. data/spec/option_spec.rb +882 -0
  40. data/spec/option_value_spec.rb +17 -0
  41. data/spec/spec_helper.rb +6 -0
  42. data/spec/sudo_spec.rb +24 -0
  43. data/spec/types/enum_spec.rb +31 -0
  44. data/spec/types/hex_spec.rb +158 -0
  45. data/spec/types/input_dir_spec.rb +30 -0
  46. data/spec/types/input_file_spec.rb +34 -0
  47. data/spec/types/input_path_spec.rb +32 -0
  48. data/spec/types/key_value_list_spec.rb +100 -0
  49. data/spec/types/key_value_spec.rb +272 -0
  50. data/spec/types/list_spec.rb +143 -0
  51. data/spec/types/map_spec.rb +62 -0
  52. data/spec/types/num_spec.rb +90 -0
  53. data/spec/types/str_spec.rb +232 -0
  54. data/spec/types/type_spec.rb +59 -0
  55. metadata +118 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a8f5edcef133633cddd3398cb7314514661a48f2803800f43410b1ccd2195679
4
+ data.tar.gz: 57dcbb7895fa2dff5ef8528439ae128fe9b86072674fb32da93f13e03c5d2572
5
+ SHA512:
6
+ metadata.gz: 5675fc6957f545350b54cb9a46003c9c059496366c1198b4bf2a1656dc7484d009d6e9bd896f56c14fdce222354fb788cedaf6b54b0f88c4835c28893124fc13
7
+ data.tar.gz: d3fbefad0bc588d75a4dfdf0e0f009d2162ed15562d2ee6ae44f08b9e1efc0815547f5d5fe5276548c921bad0b727e72a139b8ffbb8a09838ece1e19688676e7
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on: [ push, pull_request ]
4
+
5
+ jobs:
6
+ tests:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby:
12
+ - 2.6
13
+ - 2.7
14
+ - 3.0
15
+ - jruby
16
+ - truffleruby
17
+ name: Ruby ${{ matrix.ruby }}
18
+ steps:
19
+ - uses: actions/checkout@v2
20
+ - name: Set up Ruby
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby }}
24
+ - name: Install dependencies
25
+ run: bundle install --jobs 4 --retry 3
26
+ - name: Run tests
27
+ run: bundle exec rake test
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /Gemfile.lock
2
+ /coverage
3
+ /doc/
4
+ /pkg/
5
+ /vendor/bundle/
6
+ .DS_Store
7
+ .yardoc
8
+ *.log
9
+ *.swp
10
+ *~
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown --title "CommandMapper Documentation" --protected --quiet
data/ChangeLog.md ADDED
@@ -0,0 +1,25 @@
1
+ ### 0.1.0 / 2021-11-22
2
+
3
+ * Initial release:
4
+ * Added {CommandMapper::Error}.
5
+ * Added {CommandMapper::ValidationError}.
6
+ * Added {CommandMapper::ArgumentRequired}.
7
+ * Added {CommandMapper::Types::Type}.
8
+ * Added {CommandMapper::Types::Str}.
9
+ * Added {CommandMapper::Types::Num}.
10
+ * Added {CommandMapper::Types::Hex}.
11
+ * Added {CommandMapper::Types::Map}.
12
+ * Added {CommandMapper::Types::Enum}.
13
+ * Added {CommandMapper::Types::InputPath}.
14
+ * Added {CommandMapper::Types::InputFile}.
15
+ * Added {CommandMapper::Types::InputDir}.
16
+ * Added {CommandMapper::Types::List}.
17
+ * Added {CommandMapper::Types::KeyValue}.
18
+ * Added {CommandMapper::Types::KeyValueList}.
19
+ * Added {CommandMapper::Arg}.
20
+ * Added {CommandMapper::Argument}.
21
+ * Added {CommandMapper::OptionValue}.
22
+ * Added {CommandMapper::Option}.
23
+ * Added {CommandMapper::Command}.
24
+ * Added {CommandMapper::Sudo}.
25
+
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rake'
7
+ gem 'rubygems-tasks', '~> 0.2'
8
+ gem 'rspec', '~> 3.0'
9
+ gem 'simplecov', '~> 0.20', require: false
10
+ gem 'kramdown'
11
+ gem 'yard', '~> 0.9'
12
+ gem 'yard-spellcheck'
13
+
14
+ gem 'dead_end'
15
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2021 Hal Brodigan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,369 @@
1
+ # command_mapper
2
+
3
+ [![CI](https://github.com/postmodern/command_mapper.rb/actions/workflows/ruby.yml/badge.svg)](https://github.com/postmodern/command_mapper.rb/actions/workflows/ruby.yml)
4
+ [![Code Climate](https://codeclimate.com/github/postmodern/command_mapper.rb.svg)](https://codeclimate.com/github/postmodern/command_mapper.rb)
5
+
6
+ * [Source](https://github.com/postmodern/command_mapper)
7
+ * [Issues](https://github.com/postmodern/command_mapper/issues)
8
+ * [Documentation](http://rubydoc.info/gems/command_mapper/frames)
9
+
10
+ ## Description
11
+
12
+ Command Mapper maps a command's options and arguments to Class attributes to
13
+ allow safely and securely executing commands.
14
+
15
+ ## Features
16
+
17
+ * Supports defining commands as Ruby classes.
18
+ * Supports mapping in options and additional arguments.
19
+ * Supports common option types:
20
+ * `Str`: string values
21
+ * `Num`: numeric values
22
+ * `Hex`: hexadecimal values
23
+ * `Map`: maps `true`/`false` to `yes`/`no`, or `enabled`/`disabled`
24
+ (aka `--opt=yes|no` or `--opt=enabled|disabled` values).
25
+ * `Enum`: maps a finite set of Symbols to a finite set of Strings
26
+ (aka `--opt={foo|bar|baz}` values).
27
+ * `List`: comma-separated list (aka `--opt VALUE,...`).
28
+ * `KeyValue`: maps a Hash or Array to key:value Strings
29
+ (aka `--opt KEY:VALUE` or `--opt KEY=VALUE` values).
30
+ * `KeyValueList`: a key-value list
31
+ (aka `--opt KEY:VALUE,...` or `--opt KEY=VALUE;...` values).
32
+ * `InputPath`: a path to a pre-existing file or directory
33
+ * `InputFile`: a path to a pre-existing file
34
+ * `InputDir`: a path to a pre-existing directory
35
+ * Supports mapping in sub-commands.
36
+ * Allows running the command via `IO.popen` to read the command's output.
37
+ * Allows running commands with additional environment variables.
38
+ * Allows overriding the command name or path to the command.
39
+ * Allows running commands via `sudo`.
40
+ * Prevents command injection and option injection.
41
+
42
+ ## Examples
43
+
44
+ ```ruby
45
+ require 'command_mapper/command'
46
+
47
+ #
48
+ # Represents the `grep` command
49
+ #
50
+ class Grep < CommandMapper::Command
51
+
52
+ command "grep" do
53
+ option "--extended-regexp"
54
+ option "--fixed-strings"
55
+ option "--basic-regexp"
56
+ option "--perl-regexp"
57
+ option "--regexp", equals: true, value: true
58
+ option "--file", equals: true, value: true
59
+ option "--ignore-case"
60
+ option "--no-ignore-case"
61
+ option "--word-regexp"
62
+ option "--line-regexp"
63
+ option "--null-data"
64
+ option "--no-messages"
65
+ option "--invert-match"
66
+ option "--version"
67
+ option "--help"
68
+ option "--max-count", equals: true, value: {type: Num.new}
69
+ option "--byte-offset"
70
+ option "--line-number"
71
+ option "--line-buffered"
72
+ option "--with-filename"
73
+ option "--no-filename"
74
+ option "--label", equals: true, value: true
75
+ option "--only-matching"
76
+ option "--quiet"
77
+ option "--binary-files", equals: true, value: true
78
+ option "--text"
79
+ option "-I", name: # FIXME: name
80
+ option "--directories", equals: true, value: true
81
+ option "--devices", equals: true, value: true
82
+ option "--recursive"
83
+ option "--dereference-recursive"
84
+ option "--include", equals: true, value: true
85
+ option "--exclude", equals: true, value: true
86
+ option "--exclude-from", equals: true, value: true
87
+ option "--exclude-dir", equals: true, value: true
88
+ option "--files-without-match", value: true
89
+ option "--files-with-matches"
90
+ option "--count"
91
+ option "--initial-tab"
92
+ option "--null"
93
+ option "--before-context", equals: true, value: {type: Num.new}
94
+ option "--after-context", equals: true, value: {type: Num.new}
95
+ option "--context", equals: true, value: {type: Num.new}
96
+ option "--group-separator", equals: true, value: true
97
+ option "--no-group-separator"
98
+ option "--color", equals: :optional, value: {required: false}
99
+ option "--colour", equals: :optional, value: {required: false}
100
+ option "--binary"
101
+
102
+ argument :patterns
103
+ argument :file, required: false, repeats: true
104
+ end
105
+
106
+ end
107
+ ```
108
+
109
+ ### Defining Options
110
+
111
+ ```ruby
112
+ option "--opt"
113
+ ```
114
+
115
+ Define a short option:
116
+
117
+ ```ruby
118
+ option "-o", name: :opt
119
+ ```
120
+
121
+ Defines an option with a required value:
122
+
123
+ ```ruby
124
+ option "--output", value: {required: true}
125
+ ```
126
+
127
+ Defines an option that can be specified multiple times:
128
+
129
+ ```ruby
130
+ option "--include-dir", repeats: true
131
+ ```
132
+
133
+ Defines an option that accepts a numeric value:
134
+
135
+ ```ruby
136
+ option "--count", value: {type: Num.new}
137
+ ```
138
+
139
+ Defines an option that accepts a comma-separated list:
140
+
141
+ ```ruby
142
+ option "--list", value: {type: List.new}
143
+ ```
144
+
145
+ Defines an option that accepts a `key=value` pair:
146
+
147
+ ```ruby
148
+ option "--param", value: {type: KeyValue.new}
149
+ ```
150
+
151
+ Defines an option that accepts a `key:value` pair:
152
+
153
+ ```ruby
154
+ option "--param", value: {type: KeyValue.new(separator: ':')}
155
+ ```
156
+
157
+ Defines an option that accepts a finite number of values:
158
+
159
+ ```ruby
160
+ option "--type", value: {type: Enum[:foo, :bar, :baz]}
161
+ ```
162
+
163
+ Custom methods:
164
+
165
+ ```ruby
166
+ def foo
167
+ @foo || @bar
168
+ end
169
+
170
+ def foo=(value)
171
+ @foo = case value
172
+ when Hash then ...
173
+ when Array then ...
174
+ else value.to_s
175
+ end
176
+ end
177
+ ```
178
+
179
+ ### Defining Arguments
180
+
181
+ ```ruby
182
+ argument :host
183
+ ```
184
+
185
+ Define an optional argument:
186
+
187
+ ```ruby
188
+ argument :optional_output, required: false
189
+ ```
190
+
191
+ Define an argument that can be repeated:
192
+
193
+ ```ruby
194
+ argument :files, repeats: true
195
+ ```
196
+
197
+ Define an argument that accepts an existing file:
198
+
199
+ ```ruby
200
+ argument :file, type: InputFile.new
201
+ ```
202
+
203
+ Define an argument that accepts an existing directory:
204
+
205
+ ```ruby
206
+ argument :dir, type: InputDir.new
207
+ ```
208
+
209
+ Custom methods:
210
+
211
+ ```ruby
212
+ def foo
213
+ @foo || @bar
214
+ end
215
+
216
+ def foo=(value)
217
+ @foo = case value
218
+ when Hash then ...
219
+ when Array then ...
220
+ else value.to_s
221
+ end
222
+ end
223
+ ```
224
+
225
+ ### Custom Types
226
+
227
+ ```ruby
228
+ class PortRange < CommandMapper::Types::Type
229
+
230
+ def validate(value)
231
+ case value
232
+ when Integer
233
+ true
234
+ when Range
235
+ if value.begin.kind_of?(Integer)
236
+ true
237
+ else
238
+ [false, "port range can only contain Integers"]
239
+ end
240
+ else
241
+ [false, "port range must be an Integer or a Range of Integers"]
242
+ end
243
+ end
244
+
245
+ def format(value)
246
+ case value
247
+ when Integer
248
+ "#{value}"
249
+ when Range
250
+ "#{value.begin}-#{value.end}"
251
+ end
252
+ end
253
+
254
+ end
255
+
256
+ option :ports, value: {required: true, type: PortRange.new}
257
+ ```
258
+
259
+ ### Running
260
+
261
+ Keyword arguments:
262
+
263
+ ```ruby
264
+ Grep.run(ignore_case: true, patterns: "foo", file: "file.txt")
265
+ # ...
266
+ ```
267
+
268
+ With a block:
269
+
270
+ ```ruby
271
+ Grep.run do |grep|
272
+ grep.ignore_case = true
273
+ grep.patterns = "foo"
274
+ grep.file = "file.txt"
275
+ end
276
+ ```
277
+
278
+ ### Capturing output
279
+
280
+ ```ruby
281
+ Grep.capture(ignore_case: true, patterns: "foo", file: "file.txt")
282
+ # => "..."
283
+ ```
284
+
285
+ ### popen
286
+
287
+ ```ruby
288
+ io = Grep.popen(ignore_case: true, patterns: "foo", file: "file.txt")
289
+
290
+ io.each_line do |line|
291
+ # ...
292
+ end
293
+ ```
294
+
295
+ ### sudo
296
+
297
+ ```ruby
298
+ Grep.sudo(patterns: "Error", file: "/var/log/syslog")
299
+ # Password:
300
+ # ...
301
+ ```
302
+
303
+ ### Code Gen
304
+
305
+ [command_mapper-gen] can automatically generate command classes from a command's
306
+ `--help` output and/or man page.
307
+
308
+ [command_mapper-gen]: https://github.com/postmodern/command_mapper-gen.rb#readme
309
+
310
+ ```
311
+ $ gem install command_mapper-gen
312
+ $ command_mapper-gen cat
313
+ require 'command_mapper/command'
314
+
315
+ #
316
+ # Represents the `cat` command
317
+ #
318
+ class Cat < CommandMapper::Command
319
+
320
+ command "cat" do
321
+ option "--show-all"
322
+ option "--number-nonblank"
323
+ option "-e", name: # FIXME: name
324
+ option "--show-ends"
325
+ option "--number"
326
+ option "--squeeze-blank"
327
+ option "-t", name: # FIXME: name
328
+ option "--show-tabs"
329
+ option "-u", name: # FIXME: name
330
+ option "--show-nonprinting"
331
+ option "--help"
332
+ option "--version"
333
+
334
+ argument :file, required: false, repeats: true
335
+ end
336
+
337
+ end
338
+ ```
339
+
340
+ ## Requirements
341
+
342
+ * [ruby] >= 2.0.0
343
+
344
+ ## Install
345
+
346
+ ```shell
347
+ $ gem install command_mapper
348
+ ```
349
+
350
+ ### Gemfile
351
+
352
+ ```ruby
353
+ gem 'command_mapper', '~> 0.1'
354
+ ```
355
+
356
+ ### gemspec
357
+
358
+ ```ruby
359
+ gemspec.add_dependency 'command_mapper', '~> 0.1'
360
+ ```
361
+
362
+ ## License
363
+
364
+ Copyright (c) 2021 Hal Brodigan
365
+
366
+ See {file:LICENSE.txt} for license information.
367
+
368
+ [command_mapper]: https://github.com/postmodern/command_mapper.rb#readme
369
+ [ruby]: https://www.ruby-lang.org/
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+
3
+ require 'rubygems/tasks'
4
+ Gem::Tasks.new
5
+
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new
8
+ task :test => :spec
9
+ task :default => :spec
10
+
11
+ require 'yard'
12
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'command_mapper/version'
14
+ CommandMapper::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+ gem.metadata = gemspec['metadata'] if gemspec['metadata']
24
+
25
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
26
+
27
+ gem.files = `git ls-files`.split($/)
28
+ gem.files = glob[gemspec['files']] if gemspec['files']
29
+
30
+ gem.executables = gemspec.fetch('executables') do
31
+ glob['bin/*'].map { |path| File.basename(path) }
32
+ end
33
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
34
+
35
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
36
+ gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
37
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
38
+
39
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
40
+ %w[ext lib].select { |dir| File.directory?(dir) }
41
+ })
42
+
43
+ gem.requirements = gemspec['requirements']
44
+ gem.required_ruby_version = gemspec['required_ruby_version']
45
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
46
+ gem.post_install_message = gemspec['post_install_message']
47
+
48
+ split = lambda { |string| string.split(/,\s*/) }
49
+
50
+ if gemspec['dependencies']
51
+ gemspec['dependencies'].each do |name,versions|
52
+ gem.add_dependency(name,split[versions])
53
+ end
54
+ end
55
+
56
+ if gemspec['development_dependencies']
57
+ gemspec['development_dependencies'].each do |name,versions|
58
+ gem.add_development_dependency(name,split[versions])
59
+ end
60
+ end
61
+ end
data/gemspec.yml ADDED
@@ -0,0 +1,23 @@
1
+ name: command_mapper
2
+ summary: Safe and secure execution of commands.
3
+ description:
4
+ Command Mapper maps a command's arguments to Class attributes to allow safely
5
+ and securely executing commands.
6
+
7
+ license: MIT
8
+ authors: Postmodern
9
+ email: postmodern.mod3@gmail.com
10
+ homepage: https://github.com/postmodern/command_mapper.rb#readme
11
+ has_yard: true
12
+
13
+ metadata:
14
+ documentation_uri: https://rubydoc.info/gems/command_mapper
15
+ source_code_uri: https://github.com/postmodern/command_mapper.rb
16
+ bug_tracker_uri: https://github.com/postmodern/command_mapper.rb/issues
17
+ changelog_uri: https://github.com/postmodern/command_mapper.rb/blob/master/ChangeLog.md
18
+ rubygems_mfa_required: 'true'
19
+
20
+ required_ruby_version: ">= 2.0.0"
21
+
22
+ development_dependencies:
23
+ bundler: ~> 2.0
@@ -0,0 +1,75 @@
1
+ require 'command_mapper/types/type'
2
+ require 'command_mapper/types/str'
3
+
4
+ module CommandMapper
5
+ #
6
+ # The base class for both {Option options} and {Argument arguments}.
7
+ #
8
+ class Arg
9
+ # The argument's arg's type.
10
+ #
11
+ # @return [Types::Type, nil]
12
+ attr_reader :type
13
+
14
+ #
15
+ # Initializes the argument.
16
+ #
17
+ # @param [Boolean] required
18
+ # Specifies whether the argument is required or can be omitted.
19
+ #
20
+ # @param [Types::Type, Hash, nil] type
21
+ #
22
+ # @raise [ArgumentError]
23
+ # The `type` keyword argument was given a `nil` value.
24
+ #
25
+ def initialize(required: true, type: Types::Str.new)
26
+ @required = required
27
+
28
+ if type.nil?
29
+ raise(ArgumentError,"type: keyword cannot be nil")
30
+ end
31
+
32
+ @type = Types::Type(type)
33
+ end
34
+
35
+ #
36
+ # Specifies whether the argument value is required.
37
+ #
38
+ # @return [Boolean]
39
+ #
40
+ def required?
41
+ @required
42
+ end
43
+
44
+ #
45
+ # Specifies whether the argument value can be omitted.
46
+ #
47
+ # @return [Boolean]
48
+ #
49
+ def optional?
50
+ !@required
51
+ end
52
+
53
+ #
54
+ # Validates whether a given value is compatible with the arg.
55
+ #
56
+ # @param [Object] value
57
+ #
58
+ # @return [true, (false, String)]
59
+ # Returns true if the value is valid, or `false` and a validation error
60
+ # message if the value is not compatible.
61
+ #
62
+ def validate(value)
63
+ if value.nil?
64
+ if required?
65
+ return [false, "does not accept a nil value"]
66
+ else
67
+ return true
68
+ end
69
+ else
70
+ return @type.validate(value)
71
+ end
72
+ end
73
+
74
+ end
75
+ end