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,131 @@
|
|
1
|
+
require 'command_mapper/types/type'
|
2
|
+
require 'command_mapper/types/str'
|
3
|
+
|
4
|
+
module CommandMapper
|
5
|
+
module Types
|
6
|
+
#
|
7
|
+
# Represents a key-value type.
|
8
|
+
#
|
9
|
+
class KeyValue < Type
|
10
|
+
|
11
|
+
# The separator String between the key and value.
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :separator
|
15
|
+
|
16
|
+
# The key's type.
|
17
|
+
#
|
18
|
+
# @return [Type]
|
19
|
+
attr_reader :key
|
20
|
+
|
21
|
+
# The value's type.
|
22
|
+
#
|
23
|
+
# @return [Type]
|
24
|
+
attr_reader :value
|
25
|
+
|
26
|
+
#
|
27
|
+
# Initializes the key-value value type.
|
28
|
+
#
|
29
|
+
# @param [String] separator
|
30
|
+
# The key-value separator.
|
31
|
+
#
|
32
|
+
# @param [Type, Hash] key
|
33
|
+
# The key's value type.
|
34
|
+
#
|
35
|
+
# @param [Type, Hash] value
|
36
|
+
# The value's value type.
|
37
|
+
#
|
38
|
+
# @param [Hash{Symbol => Object}]
|
39
|
+
# Additional keyword arguments for {Type#initialize}.
|
40
|
+
#
|
41
|
+
def initialize(separator: '=', key: Str.new, value: Str.new)
|
42
|
+
@separator = separator
|
43
|
+
|
44
|
+
if key.nil?
|
45
|
+
raise(ArgumentError,"key: keyword cannot be nil")
|
46
|
+
end
|
47
|
+
|
48
|
+
if value.nil?
|
49
|
+
raise(ArgumentError,"value: keyword cannot be nil")
|
50
|
+
end
|
51
|
+
|
52
|
+
@key = Types::Type(key)
|
53
|
+
@value = Types::Type(value)
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Valides the given value.
|
58
|
+
#
|
59
|
+
# @param [Object] value
|
60
|
+
#
|
61
|
+
# @return [true, (false, String)]
|
62
|
+
#
|
63
|
+
def validate(value)
|
64
|
+
case value
|
65
|
+
when Hash
|
66
|
+
if value.length < 1
|
67
|
+
return [false, "cannot be empty"]
|
68
|
+
end
|
69
|
+
|
70
|
+
if value.length > 1
|
71
|
+
return [false, "cannot contain multiple key:value pairs (#{value.inspect})"]
|
72
|
+
end
|
73
|
+
|
74
|
+
key, value = value.first
|
75
|
+
when Array
|
76
|
+
if value.length < 2
|
77
|
+
return [false, "must contain two elements (#{value.inspect})"]
|
78
|
+
end
|
79
|
+
|
80
|
+
if value.length > 2
|
81
|
+
return [false, "cannot contain more than two elements (#{value.inspect})"]
|
82
|
+
end
|
83
|
+
|
84
|
+
key, value = value
|
85
|
+
else
|
86
|
+
return [false, "must be a Hash or an Array (#{value.inspect})"]
|
87
|
+
end
|
88
|
+
|
89
|
+
valid, message = @key.validate(key)
|
90
|
+
|
91
|
+
unless valid
|
92
|
+
return [false, "key #{message}"]
|
93
|
+
end
|
94
|
+
|
95
|
+
valid, message = @value.validate(value)
|
96
|
+
|
97
|
+
unless valid
|
98
|
+
return [false, "value #{message}"]
|
99
|
+
end
|
100
|
+
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Formats a value into a key-value pair.
|
106
|
+
#
|
107
|
+
# @param [Hash, Array, #to_s] value
|
108
|
+
# The given value.
|
109
|
+
#
|
110
|
+
# @return [String]
|
111
|
+
# The formatted key-value pair.
|
112
|
+
#
|
113
|
+
def format(value)
|
114
|
+
case value
|
115
|
+
when Hash, Array
|
116
|
+
case value
|
117
|
+
when Hash
|
118
|
+
key, value = value.first
|
119
|
+
when Array
|
120
|
+
key, value = value
|
121
|
+
end
|
122
|
+
|
123
|
+
"#{@key.format(key)}#{@separator}#{@value.format(value)}"
|
124
|
+
else
|
125
|
+
super(value)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'command_mapper/types/list'
|
2
|
+
require 'command_mapper/types/key_value'
|
3
|
+
|
4
|
+
module CommandMapper
|
5
|
+
module Types
|
6
|
+
#
|
7
|
+
# Represents a list of `key=value` pairs.
|
8
|
+
#
|
9
|
+
class KeyValueList < List
|
10
|
+
|
11
|
+
#
|
12
|
+
# Initializes the key-value list.
|
13
|
+
#
|
14
|
+
# @param [String] separator
|
15
|
+
# The list separator character (ex: `,`).
|
16
|
+
#
|
17
|
+
# @param [String] key_value_separator
|
18
|
+
# The key-value separator (ex: `=`).
|
19
|
+
#
|
20
|
+
# @param [Hash{Symbol => Object}] kwargs
|
21
|
+
# Additional keyword arguments for {KeyValue#initialize}.
|
22
|
+
#
|
23
|
+
def initialize(separator: ',', key_value_separator: '=', **kwargs)
|
24
|
+
super(
|
25
|
+
type: KeyValue.new(separator: key_value_separator, **kwargs),
|
26
|
+
separator: separator
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Formats the value.
|
32
|
+
#
|
33
|
+
# @param [Hash, Array((key, value))] value
|
34
|
+
# The list of key-value pairs.
|
35
|
+
#
|
36
|
+
# @return [String]
|
37
|
+
# The formatted key-value list.
|
38
|
+
#
|
39
|
+
def format(value)
|
40
|
+
super(Array(value).map(&@type.method(:format)))
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'command_mapper/types/type'
|
2
|
+
require 'command_mapper/types/str'
|
3
|
+
|
4
|
+
module CommandMapper
|
5
|
+
module Types
|
6
|
+
#
|
7
|
+
# Represents a list type.
|
8
|
+
#
|
9
|
+
class List < Type
|
10
|
+
|
11
|
+
# The seperator character.
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :separator
|
15
|
+
|
16
|
+
# The list element type.
|
17
|
+
#
|
18
|
+
# @return [Type]
|
19
|
+
attr_reader :type
|
20
|
+
|
21
|
+
#
|
22
|
+
# Initializes the list type.
|
23
|
+
#
|
24
|
+
# @param [String] separator
|
25
|
+
# The list separator character.
|
26
|
+
#
|
27
|
+
# @param [Type, Hash] value
|
28
|
+
# The list's value type.
|
29
|
+
#
|
30
|
+
def initialize(separator: ',', type: Str.new, allow_empty: false)
|
31
|
+
if type.nil?
|
32
|
+
raise(ArgumentError,"type: keyword cannot be nil")
|
33
|
+
end
|
34
|
+
|
35
|
+
@separator = separator
|
36
|
+
@type = Types::Type(type)
|
37
|
+
|
38
|
+
@allow_empty = allow_empty
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Specifies whether the option's value may accept empty values.
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
#
|
46
|
+
def allow_empty?
|
47
|
+
@allow_empty
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Validates the value.
|
52
|
+
#
|
53
|
+
# @param [Object] value
|
54
|
+
#
|
55
|
+
# @return [true, (false, String)]
|
56
|
+
#
|
57
|
+
def validate(value)
|
58
|
+
values = Array(value)
|
59
|
+
|
60
|
+
if values.empty?
|
61
|
+
unless allow_empty?
|
62
|
+
return [false, "cannot be empty"]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
values.each do |element|
|
67
|
+
valid, message = @type.validate(element)
|
68
|
+
|
69
|
+
unless valid
|
70
|
+
return [false, "element #{message}"]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Formats the value into a list.
|
79
|
+
#
|
80
|
+
# @param [Object] value
|
81
|
+
#
|
82
|
+
# @return [String]
|
83
|
+
#
|
84
|
+
def format(value)
|
85
|
+
Array(value).map(&@type.method(:format)).join(@separator)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'command_mapper/types/type'
|
2
|
+
|
3
|
+
module CommandMapper
|
4
|
+
module Types
|
5
|
+
class Map < Type
|
6
|
+
|
7
|
+
# @return [Hash{Object => String}]
|
8
|
+
attr_reader :map
|
9
|
+
|
10
|
+
#
|
11
|
+
# Initializes the map value type.
|
12
|
+
#
|
13
|
+
# @param [Hash{Object => String}] map
|
14
|
+
# The map of values to Strings.
|
15
|
+
#
|
16
|
+
def initialize(map)
|
17
|
+
@map = map
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Creates a new map.
|
22
|
+
#
|
23
|
+
# @param [Hash{Object => String}] map
|
24
|
+
# The map of values to Strings.
|
25
|
+
#
|
26
|
+
# @return [Map]
|
27
|
+
#
|
28
|
+
def self.[](map)
|
29
|
+
new(map)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Maps boolean values to "yes" and "no"
|
33
|
+
YesNo = new(true => 'yes', false => 'no')
|
34
|
+
|
35
|
+
# Maps boolean values to "enabled" and "disabled"
|
36
|
+
EnabledDisabled = new(true => 'enabled', false => 'disabled')
|
37
|
+
|
38
|
+
#
|
39
|
+
# Validates a value.
|
40
|
+
#
|
41
|
+
# @param [Object] value
|
42
|
+
#
|
43
|
+
# @return [true, (false, String)]
|
44
|
+
#
|
45
|
+
def validate(value)
|
46
|
+
unless @map.has_key?(value)
|
47
|
+
return [false, "unknown value (#{value.inspect})"]
|
48
|
+
end
|
49
|
+
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# @param [Object] value
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
#
|
58
|
+
def format(value)
|
59
|
+
super(@map.fetch(value))
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'command_mapper/types/type'
|
2
|
+
|
3
|
+
module CommandMapper
|
4
|
+
module Types
|
5
|
+
#
|
6
|
+
# Represents a numeric value.
|
7
|
+
#
|
8
|
+
class Num < Type
|
9
|
+
|
10
|
+
#
|
11
|
+
# Validates a value.
|
12
|
+
#
|
13
|
+
# @param [String, Integer] value
|
14
|
+
#
|
15
|
+
# @return [true, (false, String)]
|
16
|
+
#
|
17
|
+
def validate(value)
|
18
|
+
case value
|
19
|
+
when Integer
|
20
|
+
return true
|
21
|
+
when String
|
22
|
+
unless value =~ /\A\d+\z/
|
23
|
+
return [false, "contains non-numeric characters (#{value.inspect})"]
|
24
|
+
end
|
25
|
+
else
|
26
|
+
unless value.respond_to?(:to_i)
|
27
|
+
return [false, "cannot be converted into an Integer (#{value.inspect})"]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Formats a numeric value.
|
36
|
+
#
|
37
|
+
# @param [String, Integer, #to_i] value
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
#
|
41
|
+
def format(value)
|
42
|
+
case value
|
43
|
+
when Integer, String then value.to_s
|
44
|
+
else value.to_i.to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'command_mapper/types/type'
|
2
|
+
|
3
|
+
module CommandMapper
|
4
|
+
module Types
|
5
|
+
class Str < Type
|
6
|
+
#
|
7
|
+
# Initializes the value.
|
8
|
+
#
|
9
|
+
# @param [Boolean] allow_empty
|
10
|
+
# Specifies whether the argument may accept empty values.
|
11
|
+
#
|
12
|
+
# @param [Boolean] allow_blank
|
13
|
+
# Specifies whether the argument may accept blank values.
|
14
|
+
#
|
15
|
+
def initialize(allow_empty: false, allow_blank: false)
|
16
|
+
@allow_empty = allow_empty
|
17
|
+
@allow_blank = allow_blank
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Specifies whether the option's value may accept empty values.
|
22
|
+
#
|
23
|
+
# @return [Boolean]
|
24
|
+
#
|
25
|
+
def allow_empty?
|
26
|
+
@allow_empty
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Specifies whether the option's value may accept blank values.
|
31
|
+
#
|
32
|
+
# @return [Boolean]
|
33
|
+
#
|
34
|
+
def allow_blank?
|
35
|
+
@allow_blank
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Validates the given value.
|
40
|
+
#
|
41
|
+
# @param [Object] value
|
42
|
+
# The given value to validate.
|
43
|
+
#
|
44
|
+
# @return [true, (false, String)]
|
45
|
+
# Returns true if the valid is considered valid, or false and a
|
46
|
+
# validation message if the value is not valid.
|
47
|
+
# * If `nil` is given and a value is required, then `false` will be
|
48
|
+
# returned.
|
49
|
+
# * If an empty value is given and empty values are not allowed, then
|
50
|
+
# `false` will be returned.
|
51
|
+
# * If an empty value is given and blank values are not allowed, then
|
52
|
+
# `false` will be returned.
|
53
|
+
#
|
54
|
+
def validate(value)
|
55
|
+
case value
|
56
|
+
when nil
|
57
|
+
unless allow_empty?
|
58
|
+
return [false, "cannot be nil"]
|
59
|
+
end
|
60
|
+
when Enumerable
|
61
|
+
return [false, "cannot convert a #{value.class} into a String (#{value.inspect})"]
|
62
|
+
else
|
63
|
+
unless value.respond_to?(:to_s)
|
64
|
+
return [false, "does not define a #to_s method (#{value.inspect})"]
|
65
|
+
end
|
66
|
+
|
67
|
+
string = value.to_s
|
68
|
+
|
69
|
+
if string.empty?
|
70
|
+
unless allow_empty?
|
71
|
+
return [false, "does not allow an empty value"]
|
72
|
+
end
|
73
|
+
elsif string =~ /\A\s+\z/
|
74
|
+
unless allow_blank?
|
75
|
+
return [false, "does not allow a blank value (#{value.inspect})"]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module CommandMapper
|
2
|
+
module Types
|
3
|
+
#
|
4
|
+
# The base type for all command-line argument types.
|
5
|
+
#
|
6
|
+
# ## Custom Types
|
7
|
+
#
|
8
|
+
# Custom types can be defined by extending the {Type} class.
|
9
|
+
#
|
10
|
+
# class PortRange < CommandMapper::Types::Type
|
11
|
+
#
|
12
|
+
# def validate(value)
|
13
|
+
# case value
|
14
|
+
# when Integer
|
15
|
+
# true
|
16
|
+
# when Range
|
17
|
+
# if value.begin.kind_of?(Integer)
|
18
|
+
# true
|
19
|
+
# else
|
20
|
+
# [false, "port range can only contain Integers"]
|
21
|
+
# end
|
22
|
+
# else
|
23
|
+
# [false, "port range must be an Integer or a Range of Integers"]
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# def format(value)
|
28
|
+
# case value
|
29
|
+
# when Integer
|
30
|
+
# "#{value}"
|
31
|
+
# when Range
|
32
|
+
# "#{value.begin}-#{value.end}"
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
#
|
39
|
+
# The custom type can define the following methods:
|
40
|
+
#
|
41
|
+
# * `#initialize` - accepts additional configuration options.
|
42
|
+
# * `#validate` - accepts a value object and returns `true` (indicating the
|
43
|
+
# value is valid) or `[false, message]` (indicating the value is invalid).
|
44
|
+
# * `#format` - accepts a validated value and returns a formatted String.
|
45
|
+
#
|
46
|
+
# Once defined, custom {Type} classes can be used with `option` or
|
47
|
+
# `argument` and passed in via the `type:` keyword argument.
|
48
|
+
#
|
49
|
+
# option "--ports", value: {required: true, type: PortRange.new}
|
50
|
+
#
|
51
|
+
# argument :ports, required: true, type: PortRange.new
|
52
|
+
#
|
53
|
+
class Type
|
54
|
+
|
55
|
+
#
|
56
|
+
# The default `validate` method for all types.
|
57
|
+
#
|
58
|
+
# @param [Object]
|
59
|
+
#
|
60
|
+
# @return [true, (false, String)]
|
61
|
+
#
|
62
|
+
def validate(value)
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# The default `format` method for all types.
|
68
|
+
#
|
69
|
+
# @param [#to_s] value
|
70
|
+
#
|
71
|
+
# @return [String]
|
72
|
+
# The String version of the value.
|
73
|
+
#
|
74
|
+
def format(value)
|
75
|
+
value.to_s
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
require 'command_mapper/types/str'
|
81
|
+
|
82
|
+
#
|
83
|
+
# Converts a value into a {Type} object.
|
84
|
+
#
|
85
|
+
# @param [Type, Hash, nil] value
|
86
|
+
#
|
87
|
+
# @return [Type]
|
88
|
+
#
|
89
|
+
# @raise [ArgumentError]
|
90
|
+
#
|
91
|
+
def self.Type(value)
|
92
|
+
case value
|
93
|
+
when Type then value
|
94
|
+
when Hash then Str.new(**value)
|
95
|
+
when nil then nil
|
96
|
+
else
|
97
|
+
raise(ArgumentError,"value must be a #{Type}, Hash, or nil: #{value.inspect}")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|