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
@@ -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