tty-option 0.1.0 → 0.3.0
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 +4 -4
- data/CHANGELOG.md +41 -1
- data/LICENSE.txt +1 -1
- data/README.md +1204 -492
- data/lib/tty/option/aggregate_errors.rb +3 -3
- data/lib/tty/option/const.rb +17 -0
- data/lib/tty/option/conversions.rb +52 -34
- data/lib/tty/option/converter.rb +2 -2
- data/lib/tty/option/deep_dup.rb +43 -20
- data/lib/tty/option/dsl.rb +5 -5
- data/lib/tty/option/errors.rb +33 -3
- data/lib/tty/option/formatter.rb +369 -74
- data/lib/tty/option/param_conversion.rb +11 -9
- data/lib/tty/option/param_permitted.rb +8 -4
- data/lib/tty/option/param_validation.rb +125 -21
- data/lib/tty/option/parameter.rb +23 -15
- data/lib/tty/option/parameters.rb +15 -1
- data/lib/tty/option/parser/arguments.rb +1 -1
- data/lib/tty/option/parser/arity_check.rb +1 -1
- data/lib/tty/option/parser/environments.rb +2 -2
- data/lib/tty/option/parser/keywords.rb +1 -1
- data/lib/tty/option/parser/options.rb +6 -1
- data/lib/tty/option/parser/param_types.rb +16 -4
- data/lib/tty/option/parser/required_check.rb +1 -1
- data/lib/tty/option/parser.rb +1 -1
- data/lib/tty/option/pipeline.rb +1 -1
- data/lib/tty/option/result.rb +25 -3
- data/lib/tty/option/usage.rb +58 -1
- data/lib/tty/option/usage_wrapper.rb +3 -2
- data/lib/tty/option/version.rb +1 -1
- metadata +8 -6
@@ -75,12 +75,12 @@ module TTY
|
|
75
75
|
output = []
|
76
76
|
space_indent = " " * indent
|
77
77
|
if messages.count == 1
|
78
|
-
|
78
|
+
msg = messages.first
|
79
79
|
label = "Error: "
|
80
80
|
output << "#{space_indent}#{label}" \
|
81
|
-
"#{wrap(
|
81
|
+
"#{wrap(msg, indent: indent + label.length, width: width)}"
|
82
82
|
else
|
83
|
-
output << space_indent
|
83
|
+
output << "#{space_indent}Errors:"
|
84
84
|
messages.each_with_index do |message, num|
|
85
85
|
entry = " #{num + 1}) "
|
86
86
|
output << "#{space_indent}#{entry}" \
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
module Option
|
5
|
+
module Const
|
6
|
+
Undefined = Object.new.tap do |obj|
|
7
|
+
def obj.to_s
|
8
|
+
"undefined"
|
9
|
+
end
|
10
|
+
|
11
|
+
def obj.inspect
|
12
|
+
"undefined".inspect
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end # Const
|
16
|
+
end # Option
|
17
|
+
end # TTY
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "const"
|
3
4
|
require_relative "converter"
|
4
5
|
|
5
6
|
module TTY
|
@@ -10,13 +11,6 @@ module TTY
|
|
10
11
|
TRUE_VALUES = /^(true|y(es)?|t|1)$/i.freeze
|
11
12
|
FALSE_VALUES = /^(false|n(o)?|f|0)$/i.freeze
|
12
13
|
|
13
|
-
# @api public
|
14
|
-
def self.raise_invalid_argument(conv_name, val)
|
15
|
-
raise ConversionError,
|
16
|
-
format("invalid value of %<value>s for %<conv>s conversion",
|
17
|
-
value: val.inspect, conv: conv_name.inspect)
|
18
|
-
end
|
19
|
-
|
20
14
|
convert :bool, :boolean do |val|
|
21
15
|
case val.to_s
|
22
16
|
when TRUE_VALUES
|
@@ -24,16 +18,18 @@ module TTY
|
|
24
18
|
when FALSE_VALUES
|
25
19
|
false
|
26
20
|
else
|
27
|
-
|
21
|
+
Const::Undefined
|
28
22
|
end
|
29
23
|
end
|
30
24
|
|
31
25
|
convert :date do |val|
|
26
|
+
require "date" unless defined?(::Date)
|
27
|
+
next val if val.is_a?(::Date)
|
28
|
+
|
32
29
|
begin
|
33
|
-
require "date" unless defined?(::Date)
|
34
30
|
::Date.parse(val)
|
35
31
|
rescue ArgumentError, TypeError
|
36
|
-
|
32
|
+
Const::Undefined
|
37
33
|
end
|
38
34
|
end
|
39
35
|
|
@@ -41,7 +37,7 @@ module TTY
|
|
41
37
|
begin
|
42
38
|
Float(val)
|
43
39
|
rescue ArgumentError, TypeError
|
44
|
-
|
40
|
+
Const::Undefined
|
45
41
|
end
|
46
42
|
end
|
47
43
|
|
@@ -49,56 +45,68 @@ module TTY
|
|
49
45
|
begin
|
50
46
|
Float(val).to_i
|
51
47
|
rescue ArgumentError, TypeError
|
52
|
-
|
48
|
+
Const::Undefined
|
53
49
|
end
|
54
50
|
end
|
55
51
|
|
56
52
|
convert :pathname, :path do |val|
|
57
|
-
require "pathname"
|
58
|
-
::Pathname
|
53
|
+
require "pathname" unless defined?(::Pathname)
|
54
|
+
next val if val.is_a?(::Pathname)
|
55
|
+
|
56
|
+
begin
|
57
|
+
::Pathname.new(val)
|
58
|
+
rescue TypeError
|
59
|
+
Const::Undefined
|
60
|
+
end
|
59
61
|
end
|
60
62
|
|
61
63
|
convert :regexp do |val|
|
62
64
|
begin
|
63
|
-
Regexp.new(val
|
65
|
+
Regexp.new(val)
|
64
66
|
rescue TypeError, RegexpError
|
65
|
-
|
67
|
+
Const::Undefined
|
66
68
|
end
|
67
69
|
end
|
68
70
|
|
69
71
|
convert :sym, :symbol do |val|
|
70
72
|
begin
|
71
73
|
String(val).to_sym
|
72
|
-
rescue ArgumentError
|
73
|
-
|
74
|
+
rescue ArgumentError, TypeError
|
75
|
+
Const::Undefined
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
77
79
|
convert :uri do |val|
|
80
|
+
require "uri" unless defined?(::URI)
|
81
|
+
next val if val.is_a?(::URI)
|
82
|
+
|
78
83
|
begin
|
79
|
-
require "uri"
|
80
84
|
::URI.parse(val)
|
81
85
|
rescue ::URI::InvalidURIError
|
82
|
-
|
86
|
+
Const::Undefined
|
83
87
|
end
|
84
88
|
end
|
85
89
|
|
86
90
|
convert :list, :array do |val|
|
87
|
-
|
88
|
-
|
89
|
-
|
91
|
+
next Const::Undefined if val.nil?
|
92
|
+
next Array(val) unless val.respond_to?(:split)
|
93
|
+
|
94
|
+
val.split(/(?<!\\),/)
|
95
|
+
.map { |v| v.strip.gsub(/\\,/, ",") }
|
96
|
+
.reject(&:empty?)
|
90
97
|
end
|
91
98
|
|
92
99
|
convert :map, :hash do |val|
|
93
|
-
|
100
|
+
next Const::Undefined if val.nil?
|
101
|
+
next val if val.is_a?(Hash)
|
102
|
+
|
103
|
+
values = val.respond_to?(:split) ? val.split(/[& ]/) : Array(val)
|
94
104
|
values.each_with_object({}) do |pair, pairs|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
101
|
-
pairs
|
105
|
+
is_string = pair.respond_to?(:split)
|
106
|
+
key, value = is_string ? pair.split(/[=:]/, 2) : pair
|
107
|
+
new_key = is_string ? key.to_sym : key
|
108
|
+
current = pairs[new_key]
|
109
|
+
pairs[new_key] = current ? Array(current) << value : value
|
102
110
|
end
|
103
111
|
end
|
104
112
|
|
@@ -107,16 +115,26 @@ module TTY
|
|
107
115
|
|
108
116
|
[:"#{type}_list", :"#{type}_array", :"#{type}s"].each do |new_type|
|
109
117
|
convert new_type do |val|
|
110
|
-
conversions[:list].(val)
|
111
|
-
|
118
|
+
list_conversion = conversions[:list].(val)
|
119
|
+
next list_conversion if list_conversion == Const::Undefined
|
120
|
+
|
121
|
+
list_conversion.map do |obj|
|
122
|
+
converted = conversions[type].(obj)
|
123
|
+
break converted if converted == Const::Undefined
|
124
|
+
converted
|
112
125
|
end
|
113
126
|
end
|
114
127
|
end
|
115
128
|
|
116
129
|
[:"#{type}_map", :"#{type}_hash"].each do |new_type|
|
117
130
|
convert new_type do |val|
|
131
|
+
map_conversion = conversions[:map].(val)
|
132
|
+
next map_conversion if map_conversion == Const::Undefined
|
133
|
+
|
118
134
|
conversions[:map].(val).each_with_object({}) do |(k, v), h|
|
119
|
-
|
135
|
+
converted = conversions[type].(v)
|
136
|
+
break converted if converted == Const::Undefined
|
137
|
+
h[k] = converted
|
120
138
|
end
|
121
139
|
end
|
122
140
|
end
|
data/lib/tty/option/converter.rb
CHANGED
@@ -32,7 +32,7 @@ module TTY
|
|
32
32
|
names.each do |name|
|
33
33
|
if contain?(name)
|
34
34
|
raise ConversionAlreadyDefined,
|
35
|
-
|
35
|
+
"conversion #{name.inspect} is already defined"
|
36
36
|
end
|
37
37
|
conversions[name] = block
|
38
38
|
end
|
@@ -56,7 +56,7 @@ module TTY
|
|
56
56
|
# @api public
|
57
57
|
def raise_unsupported_error(conv_name)
|
58
58
|
raise UnsupportedConversion,
|
59
|
-
|
59
|
+
"unsupported conversion type #{conv_name.inspect}"
|
60
60
|
end
|
61
61
|
end # Converter
|
62
62
|
end # Option
|
data/lib/tty/option/deep_dup.rb
CHANGED
@@ -2,47 +2,70 @@
|
|
2
2
|
|
3
3
|
module TTY
|
4
4
|
module Option
|
5
|
+
# Responsible for deep copying an object
|
6
|
+
#
|
7
|
+
# @api private
|
5
8
|
module DeepDup
|
6
9
|
NONDUPLICATABLE = [
|
7
|
-
Symbol, TrueClass, FalseClass, NilClass, Numeric, Method
|
10
|
+
Symbol, TrueClass, FalseClass, NilClass, Numeric, Method, UnboundMethod
|
8
11
|
].freeze
|
9
12
|
|
10
|
-
#
|
13
|
+
# Deep copy an object
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# DeepDeup.deep_dup({foo: {bar: [1, 2]}})
|
11
17
|
#
|
12
18
|
# @param [Object] object
|
19
|
+
# the object to deep copy
|
20
|
+
# @param [Hash] cache
|
21
|
+
# the cache of copied objects
|
22
|
+
#
|
23
|
+
# @return [Object]
|
13
24
|
#
|
14
25
|
# @api public
|
15
|
-
def self.deep_dup(object)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
def self.deep_dup(object, cache = {})
|
27
|
+
cache[object.object_id] ||=
|
28
|
+
case object
|
29
|
+
when *NONDUPLICATABLE then object
|
30
|
+
when Array then deep_dup_array(object, cache)
|
31
|
+
when Hash then deep_dup_hash(object, cache)
|
32
|
+
else object.dup
|
33
|
+
end
|
23
34
|
end
|
24
35
|
|
25
|
-
#
|
36
|
+
# Deep copy an array
|
26
37
|
#
|
27
|
-
# @param [
|
38
|
+
# @param [Array] object
|
39
|
+
# the array object to deep copy
|
40
|
+
# @param [Hash] cache
|
41
|
+
# the cache of copied objects
|
42
|
+
#
|
43
|
+
# @return [Array]
|
28
44
|
#
|
29
45
|
# @api private
|
30
|
-
def self.
|
31
|
-
object.each_with_object(
|
32
|
-
|
46
|
+
def self.deep_dup_array(object, cache)
|
47
|
+
object.each_with_object(cache[object.object_id] = []) do |val, array|
|
48
|
+
array << deep_dup(val, cache)
|
33
49
|
end
|
34
50
|
end
|
51
|
+
private_class_method :deep_dup_array
|
35
52
|
|
36
|
-
#
|
53
|
+
# Deep copy a hash
|
37
54
|
#
|
38
|
-
# @param [
|
55
|
+
# @param [Hash] object
|
56
|
+
# the hash object to deep copy
|
57
|
+
# @param [Hash] cache
|
58
|
+
# the cache of copied objects
|
59
|
+
#
|
60
|
+
# @return [Hash]
|
39
61
|
#
|
40
62
|
# @api private
|
41
|
-
def self.
|
42
|
-
object.each_with_object([]) do |
|
43
|
-
|
63
|
+
def self.deep_dup_hash(object, cache)
|
64
|
+
object.each_with_object(cache[object.object_id] = {}) do |(k, v), hash|
|
65
|
+
hash[deep_dup(k, cache)] = deep_dup(v, cache)
|
44
66
|
end
|
45
67
|
end
|
68
|
+
private_class_method :deep_dup_hash
|
46
69
|
end # DeepDup
|
47
70
|
end # Option
|
48
71
|
end # TTY
|
data/lib/tty/option/dsl.rb
CHANGED
@@ -28,10 +28,10 @@ module TTY
|
|
28
28
|
# @api public
|
29
29
|
def usage(**properties, &block)
|
30
30
|
@usage ||= Usage.create(**properties, &block).tap do |usage|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
unless usage.command? || usage.no_command?
|
32
|
+
usage.command(dasherize(demodulize(self.name)))
|
33
|
+
end
|
34
|
+
end
|
35
35
|
end
|
36
36
|
|
37
37
|
# Specify an argument
|
@@ -69,7 +69,7 @@ module TTY
|
|
69
69
|
#
|
70
70
|
# @api public
|
71
71
|
def flag(name, **settings, &block)
|
72
|
-
defaults = {
|
72
|
+
defaults = {default: false}
|
73
73
|
option(name, **defaults.merge(settings), &block)
|
74
74
|
end
|
75
75
|
|
data/lib/tty/option/errors.rb
CHANGED
@@ -22,6 +22,22 @@ module TTY
|
|
22
22
|
# Raised during command line input parsing
|
23
23
|
class ParseError < Error
|
24
24
|
attr_accessor :param
|
25
|
+
|
26
|
+
# Format value
|
27
|
+
#
|
28
|
+
# @param [Object] value
|
29
|
+
# the value to format
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# format_value([:a, 1])
|
33
|
+
# # => a:1
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def format_value(value)
|
39
|
+
value.respond_to?(:to_ary) ? value.join(":") : value.to_s
|
40
|
+
end
|
25
41
|
end
|
26
42
|
|
27
43
|
# Raised when found unrecognized parameter
|
@@ -39,7 +55,7 @@ module TTY
|
|
39
55
|
@param = param_or_message
|
40
56
|
|
41
57
|
message = format(MESSAGE,
|
42
|
-
value: value,
|
58
|
+
value: format_value(value),
|
43
59
|
name: param.name,
|
44
60
|
type: param.to_sym)
|
45
61
|
else
|
@@ -129,16 +145,30 @@ module TTY
|
|
129
145
|
if param_or_message.is_a?(Parameter)
|
130
146
|
@param = param_or_message
|
131
147
|
message = format(MESSAGE,
|
132
|
-
value: value,
|
148
|
+
value: format_value(value),
|
133
149
|
name: param.name,
|
134
150
|
type: param.to_sym,
|
135
|
-
choices: param.permit
|
151
|
+
choices: format_choices(param.permit))
|
136
152
|
else
|
137
153
|
message = param_or_message
|
138
154
|
end
|
139
155
|
|
140
156
|
super(message)
|
141
157
|
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Format permitted choices
|
162
|
+
#
|
163
|
+
# @param [Array<Object>] choices
|
164
|
+
# the choices to format
|
165
|
+
#
|
166
|
+
# @return [String]
|
167
|
+
#
|
168
|
+
# @api private
|
169
|
+
def format_choices(choices)
|
170
|
+
choices.map { |val| format_value(val) }.join(", ")
|
171
|
+
end
|
142
172
|
end
|
143
173
|
end # Option
|
144
174
|
end # TTY
|