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.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +27 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +25 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +20 -0
- data/README.md +369 -0
- data/Rakefile +12 -0
- data/commnad_mapper.gemspec +61 -0
- data/gemspec.yml +23 -0
- data/lib/command_mapper/arg.rb +75 -0
- data/lib/command_mapper/argument.rb +142 -0
- data/lib/command_mapper/command.rb +606 -0
- data/lib/command_mapper/exceptions.rb +19 -0
- data/lib/command_mapper/option.rb +282 -0
- data/lib/command_mapper/option_value.rb +21 -0
- data/lib/command_mapper/sudo.rb +73 -0
- data/lib/command_mapper/types/enum.rb +35 -0
- data/lib/command_mapper/types/hex.rb +82 -0
- data/lib/command_mapper/types/input_dir.rb +35 -0
- data/lib/command_mapper/types/input_file.rb +35 -0
- data/lib/command_mapper/types/input_path.rb +29 -0
- data/lib/command_mapper/types/key_value.rb +131 -0
- data/lib/command_mapper/types/key_value_list.rb +45 -0
- data/lib/command_mapper/types/list.rb +90 -0
- data/lib/command_mapper/types/map.rb +64 -0
- data/lib/command_mapper/types/num.rb +50 -0
- data/lib/command_mapper/types/str.rb +85 -0
- data/lib/command_mapper/types/type.rb +102 -0
- data/lib/command_mapper/types.rb +6 -0
- data/lib/command_mapper/version.rb +4 -0
- data/lib/command_mapper.rb +2 -0
- data/spec/arg_spec.rb +137 -0
- data/spec/argument_spec.rb +513 -0
- data/spec/commnad_spec.rb +1175 -0
- data/spec/exceptions_spec.rb +14 -0
- data/spec/option_spec.rb +882 -0
- data/spec/option_value_spec.rb +17 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/sudo_spec.rb +24 -0
- data/spec/types/enum_spec.rb +31 -0
- data/spec/types/hex_spec.rb +158 -0
- data/spec/types/input_dir_spec.rb +30 -0
- data/spec/types/input_file_spec.rb +34 -0
- data/spec/types/input_path_spec.rb +32 -0
- data/spec/types/key_value_list_spec.rb +100 -0
- data/spec/types/key_value_spec.rb +272 -0
- data/spec/types/list_spec.rb +143 -0
- data/spec/types/map_spec.rb +62 -0
- data/spec/types/num_spec.rb +90 -0
- data/spec/types/str_spec.rb +232 -0
- data/spec/types/type_spec.rb +59 -0
- 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
|