command_mapper 0.1.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,282 @@
1
+ require 'command_mapper/exceptions'
2
+ require 'command_mapper/option_value'
3
+
4
+ module CommandMapper
5
+ #
6
+ # Represents an option for a command.
7
+ #
8
+ class Option
9
+
10
+ # @return [String]
11
+ attr_reader :flag
12
+
13
+ # @return [Symbol]
14
+ attr_reader :name
15
+
16
+ # @return [OptionValue, nil]
17
+ attr_reader :value
18
+
19
+ #
20
+ # Initializes the option.
21
+ #
22
+ # @param [String] flag
23
+ # The option's flag (ex: `-o` or `--output`).
24
+ #
25
+ # @param [Symbol, nil] name
26
+ # The option's name.
27
+ #
28
+ # @param [Boolean] equals
29
+ # Specifies whether the option's flag and value should be separated with a
30
+ # `=` character.
31
+ #
32
+ # @param [Hash, nil] value
33
+ # The option's value.
34
+ #
35
+ # @option value [Boolean] :required
36
+ # Specifies whether the option requires a value or not.
37
+ #
38
+ # @option value [Types:Type, Hash, nil] :type
39
+ # The explicit type for the option's value.
40
+ #
41
+ # @param [Boolean] repeats
42
+ # Specifies whether the option can be given multiple times.
43
+ #
44
+ def initialize(flag, name: nil, equals: nil, value: nil, repeats: false)
45
+ @flag = flag
46
+ @name = name || self.class.infer_name_from_flag(flag)
47
+ @equals = equals
48
+ @value = case value
49
+ when Hash then OptionValue.new(**value)
50
+ when true then OptionValue.new
51
+ end
52
+ @repeats = repeats
53
+ end
54
+
55
+ #
56
+ # Infers a method name from the given option flag.
57
+ #
58
+ # @param [String] flag
59
+ # The given long or short option flag.
60
+ #
61
+ # @return [Symbol]
62
+ # The inferred method method name.
63
+ #
64
+ # @raise [ArgumentError]
65
+ # Could not infer the name from the given option flag or was not given a
66
+ # valid option flag.
67
+ #
68
+ def self.infer_name_from_flag(flag)
69
+ if flag.start_with?('--')
70
+ name = flag[2..-1]
71
+ elsif flag.start_with?('-')
72
+ name = flag[1..-1]
73
+
74
+ if name.length < 2
75
+ raise(ArgumentError,"cannot infer a name from short option flag: #{flag.inspect}")
76
+ end
77
+ else
78
+ raise(ArgumentError,"not an option flag: #{flag}")
79
+ end
80
+
81
+ name.downcase.gsub(/[_-]+/,'_').to_sym
82
+ end
83
+
84
+ #
85
+ # Indicates whether the option accepts a value.
86
+ #
87
+ # @return [Boolean]
88
+ #
89
+ def accepts_value?
90
+ !@value.nil?
91
+ end
92
+
93
+ #
94
+ # Indicates whether the option flag and value should be separated with a
95
+ # `=` character.
96
+ #
97
+ # @return [Boolean]
98
+ #
99
+ def equals?
100
+ @equals
101
+ end
102
+
103
+ #
104
+ # Determines whether the option can be given multiple times.
105
+ #
106
+ # @return [Boolean]
107
+ #
108
+ def repeats?
109
+ @repeats
110
+ end
111
+
112
+ #
113
+ # Validates whether the given value is compatible with the option.
114
+ #
115
+ # @param [Array<Object>, Object] value
116
+ #
117
+ # @return [true, (false, String)]
118
+ # Returns true if the value is valid, or `false` and a validation error
119
+ # message if the value is not compatible.
120
+ #
121
+ def validate(value)
122
+ if accepts_value?
123
+ if repeats?
124
+ validate_repeating(value)
125
+ else
126
+ @value.validate(value)
127
+ end
128
+ else
129
+ validate_does_not_accept_value(value)
130
+ end
131
+ end
132
+
133
+ #
134
+ # Converts the given value into the command-line arguments for the option's
135
+ # flag and value.
136
+ #
137
+ # @param [Array] argv
138
+ # The argv array.
139
+ #
140
+ # @param [Object] value
141
+ # The value given to the option.
142
+ #
143
+ # @return [Array<String>]
144
+ #
145
+ # @raise [ArgumentError]
146
+ # The given value was incompatible with the option.
147
+ #
148
+ def argv(argv=[],value)
149
+ valid, message = validate(value)
150
+
151
+ unless valid
152
+ raise(ValidationError,"option #{@name} was given an invalid value (#{value.inspect}): #{message}")
153
+ end
154
+
155
+ if accepts_value?
156
+ if repeats?
157
+ values = Array(value)
158
+
159
+ values.each do |element|
160
+ emit_option_flag_and_value(argv,element)
161
+ end
162
+ else
163
+ emit_option_flag_and_value(argv,value)
164
+ end
165
+ else
166
+ emit_option_flag_only(argv,value)
167
+ end
168
+
169
+ return argv
170
+ end
171
+
172
+ private
173
+
174
+ #
175
+ # Validates a value when the option can be repeated.
176
+ #
177
+ # @param [Array<Object>, Object] value
178
+ #
179
+ # @return [true, (false, String)]
180
+ # Returns true if the value is valid, or `false` and a validation error
181
+ # message if the value is not compatible.
182
+ #
183
+ def validate_repeating(value)
184
+ values = case value
185
+ when Array then value
186
+ else [value]
187
+ end
188
+
189
+ if @value.required?
190
+ # option requires atleast one value
191
+ if values.empty?
192
+ return [false, "requires at least one value"]
193
+ end
194
+ end
195
+
196
+ values.each do |element|
197
+ valid, message = @value.validate(element)
198
+
199
+ unless valid
200
+ return [false, message]
201
+ end
202
+ end
203
+
204
+ return true
205
+ end
206
+
207
+ #
208
+ # Validates a value when the option does not accept a value.
209
+ #
210
+ # @param [Array<Object>, Object] value
211
+ #
212
+ # @return [true, (false, String)]
213
+ # Returns true if the value is valid, or `false` and a validation error
214
+ # message if the value is not compatible.
215
+ #
216
+ def validate_does_not_accept_value(value)
217
+ case value
218
+ when true, false, nil
219
+ return true
220
+ when Integer
221
+ if repeats?
222
+ return true
223
+ else
224
+ return [false, "only repeating options may accept Integers"]
225
+ end
226
+ else
227
+ return [false, "only accepts true, false, or nil"]
228
+ end
229
+ end
230
+
231
+ #
232
+ # Emits the option's flag.
233
+ #
234
+ # @param [Array<String>] argv
235
+ # The argv array to append to.
236
+ #
237
+ # @param [true, false, nil] value
238
+ # Indicates whether to emit the option's flag or not.
239
+ #
240
+ def emit_option_flag_only(argv,value)
241
+ case value
242
+ when true
243
+ argv << @flag
244
+ when Integer
245
+ if repeats?
246
+ value.times { argv << @flag }
247
+ end
248
+ end
249
+ end
250
+
251
+ #
252
+ # Emits the option's flag and value.
253
+ #
254
+ # @param [Array<String>] argv
255
+ # The argv array to append to.
256
+ #
257
+ # @param [Object] value
258
+ # The value for the option.
259
+ #
260
+ # @raise [VAlidationError]
261
+ # The formatted value starts with a `-` character.
262
+ #
263
+ def emit_option_flag_and_value(argv,value)
264
+ if !@value.required? && value == true
265
+ argv << @flag
266
+ else
267
+ string = @value.format(value)
268
+
269
+ if string.start_with?('-')
270
+ raise(ValidationError,"option #{@name} formatted value (#{string.inspect}) cannot start with a '-'")
271
+ end
272
+
273
+ if equals?
274
+ argv << "#{@flag}=#{string}"
275
+ else
276
+ argv << @flag << string
277
+ end
278
+ end
279
+ end
280
+ end
281
+
282
+ end
@@ -0,0 +1,21 @@
1
+ require 'command_mapper/arg'
2
+
3
+ module CommandMapper
4
+ #
5
+ # Represents the value for an option.
6
+ #
7
+ class OptionValue < Arg
8
+
9
+ #
10
+ # Formats a value using the options {#type}.
11
+ #
12
+ # @param [Object] value
13
+ #
14
+ # @return [String]
15
+ #
16
+ def format(value)
17
+ @type.format(value)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ require 'command_mapper/command'
2
+ require 'command_mapper/types/key_value_list'
3
+
4
+ module CommandMapper
5
+ #
6
+ # Represents the `sudo` command.
7
+ #
8
+ # ## Sudo options:
9
+ #
10
+ # * `-A` - `sudo.ask_password`
11
+ # * `-b` - `sudo.background`
12
+ # * `-C` - `sudo.close_from`
13
+ # * `-E` - `sudo.preserve_env`
14
+ # * `-e` - `sudo.edit`
15
+ # * `-g` - `sudo.group`
16
+ # * `-H` - `sudo.home`
17
+ # * `-h` - `sudo.help`
18
+ # * `-i` - `sudo.simulate_initial_login`
19
+ # * `-k` - `sudo.kill`
20
+ # * `-K` - `sudo.sure_kill`
21
+ # * `-L` - `sudo.list_defaults`
22
+ # * `-l` - `sudo.list`
23
+ # * `-n` - `sudo.non_interactive`
24
+ # * `-P` - `sudo.preserve_group`
25
+ # * `-p` - `sudo.prompt`
26
+ # * `-r` - `sudo.role`
27
+ # * `-S` - `sudo.stdin`
28
+ # * `-s` - `sudo.shell`
29
+ # * `-t` - `sudo.type`
30
+ # * `-U` - `sudo.other_user`
31
+ # * `-u` - `sudo.user`
32
+ # * `-V` - `sudo.version`
33
+ # * `-v` - `sudo.validate`
34
+ #
35
+ # * `[command]` - `sudo.command`
36
+ #
37
+ class Sudo < Command
38
+
39
+ command "sudo" do
40
+ option "--askpass"
41
+ option "--background"
42
+ option "--bell"
43
+ option "--close-from", equals: true, value: true
44
+ option "--chdir", equals: true, value: true
45
+ option "--preserve-env", equals: true, value: true
46
+ option "--edit"
47
+ option "--group", equals: true, value: true
48
+ option "--set-home"
49
+ option "--help"
50
+ option "--host", equals: true, value: true
51
+ option "--login"
52
+ option "--remove-timestamp"
53
+ option "--reset-timestamp"
54
+ option "--list"
55
+ option "--non-interactive"
56
+ option "--preserve-groups"
57
+ option "--prompt", equals: true, value: true
58
+ option "--chroot", equals: true, value: true
59
+ option "--role", equals: true, value: true
60
+ option "--stdin"
61
+ option "--shell"
62
+ option "--type", equals: true, value: true
63
+ option "--command-timeout", equals: true, value: true
64
+ option "--other-user", equals: true, value: true
65
+ option "--user", equals: true, value: true
66
+ option "--version"
67
+ option "--validate"
68
+
69
+ argument :command, required: false, repeats: true
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,35 @@
1
+ require 'command_mapper/types/map'
2
+
3
+ module CommandMapper
4
+ module Types
5
+ class Enum < Map
6
+
7
+ # @return [Array<Object>]
8
+ attr_reader :values
9
+
10
+ #
11
+ # Initializes the enum type.
12
+ #
13
+ # @param [Array<Object>] values
14
+ # The values of the enum type.
15
+ #
16
+ def initialize(values)
17
+ @values = values
18
+
19
+ super(Hash[values.map { |value| [value, value.to_s] }])
20
+ end
21
+
22
+ #
23
+ # Creates a new enum.
24
+ #
25
+ # @param [Array<Object>] values
26
+ #
27
+ # @return [Enum]
28
+ #
29
+ def self.[](*values)
30
+ new(values)
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,82 @@
1
+ require 'command_mapper/types/num'
2
+
3
+ module CommandMapper
4
+ module Types
5
+ #
6
+ # Represents a hexadecimal value.
7
+ #
8
+ class Hex < Num
9
+
10
+ #
11
+ # Initializes the hex value.
12
+ #
13
+ # @param [Boolean] leading_zero
14
+ # Specifies whether the hex value will start with `0x` or not.
15
+ #
16
+ # @param [Hash{Symbol => Object}] kwargs
17
+ # Additional keyword arguments for {Type#initialize}.
18
+ #
19
+ def initialize(leading_zero: false)
20
+ @leading_zero = leading_zero
21
+ end
22
+
23
+ #
24
+ # Indicates whether the hex value will start with `0x` or not.
25
+ #
26
+ # @return [Boolean]
27
+ #
28
+ def leading_zero?
29
+ @leading_zero
30
+ end
31
+
32
+ #
33
+ # Validates a value.
34
+ #
35
+ # @param [String, Integer, Object] value
36
+ #
37
+ # @return [true, (false, String)]
38
+ #
39
+ def validate(value)
40
+ case value
41
+ when String
42
+ unless value =~ /\A(?:0x)?[A-Fa-f0-9]+\z/
43
+ return [false, "not in hex format (#{value.inspect})"]
44
+ end
45
+
46
+ return true
47
+ else
48
+ super(value)
49
+ end
50
+ end
51
+
52
+ #
53
+ # Formats the value.
54
+ #
55
+ # @param [#to_i] value
56
+ #
57
+ # @return [String]
58
+ #
59
+ def format(value)
60
+ case value
61
+ when String
62
+ if leading_zero? && !value.start_with?('0x')
63
+ value = "0x#{value}"
64
+ elsif (!leading_zero? && value.start_with?('0x'))
65
+ value = value[2..]
66
+ end
67
+
68
+ value
69
+ else
70
+ value = value.to_i
71
+
72
+ if leading_zero?
73
+ "0x%x" % value
74
+ else
75
+ "%x" % value
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,35 @@
1
+ require 'command_mapper/types/input_path'
2
+
3
+ module CommandMapper
4
+ module Types
5
+ #
6
+ # Represents a path to an existing directory.
7
+ #
8
+ class InputDir < InputPath
9
+
10
+ #
11
+ # Validates whether the directory exists.
12
+ #
13
+ # @param [Object] value
14
+ #
15
+ # @return [true, (false, String)]
16
+ #
17
+ def validate(value)
18
+ valid, message = super(value)
19
+
20
+ unless valid
21
+ return valid, message
22
+ end
23
+
24
+ unless value.empty?
25
+ unless File.directory?(value)
26
+ return [false, "directory does not exist (#{value.inspect})"]
27
+ end
28
+ end
29
+
30
+ return true
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ require 'command_mapper/types/input_path'
2
+
3
+ module CommandMapper
4
+ module Types
5
+ #
6
+ # Represents a path to an existing file.
7
+ #
8
+ class InputFile < InputPath
9
+
10
+ #
11
+ # Validates the file exists.
12
+ #
13
+ # @param [Object] value
14
+ #
15
+ # @return [true, (false, String)]
16
+ #
17
+ def validate(value)
18
+ valid, message = super(value)
19
+
20
+ unless valid
21
+ return valid, message
22
+ end
23
+
24
+ unless value.empty?
25
+ unless File.file?(value)
26
+ return [false, "file does not exist (#{value.inspect})"]
27
+ end
28
+ end
29
+
30
+ return true
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ require 'command_mapper/types/type'
2
+
3
+ module CommandMapper
4
+ module Types
5
+ #
6
+ # Represents a path to an existing file or a directory.
7
+ #
8
+ class InputPath < Type
9
+
10
+ #
11
+ # Validates whether the path exists or not.
12
+ #
13
+ # @param [Object] value
14
+ #
15
+ # @return [true, (false, String)]
16
+ #
17
+ def validate(value)
18
+ unless value.empty?
19
+ unless File.exists?(value)
20
+ return [false, "path does not exist (#{value.inspect})"]
21
+ end
22
+ end
23
+
24
+ return true
25
+ end
26
+
27
+ end
28
+ end
29
+ end