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.
@@ -75,12 +75,12 @@ module TTY
75
75
  output = []
76
76
  space_indent = " " * indent
77
77
  if messages.count == 1
78
- message = messages.first
78
+ msg = messages.first
79
79
  label = "Error: "
80
80
  output << "#{space_indent}#{label}" \
81
- "#{wrap(message, indent: indent + label.length, width: width)}"
81
+ "#{wrap(msg, indent: indent + label.length, width: width)}"
82
82
  else
83
- output << space_indent + "Errors:"
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
- raise_invalid_argument(:bool, val)
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
- raise_invalid_argument(:date, val)
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
- raise_invalid_argument(:float, val)
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
- raise_invalid_argument(:integer, val)
48
+ Const::Undefined
53
49
  end
54
50
  end
55
51
 
56
52
  convert :pathname, :path do |val|
57
- require "pathname"
58
- ::Pathname.new(val.to_s)
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.to_s)
65
+ Regexp.new(val)
64
66
  rescue TypeError, RegexpError
65
- raise_invalid_argument(:regexp, val)
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
- raise_invalid_argument(:symbol, val)
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
- raise_invalid_argument(:uri, val)
86
+ Const::Undefined
83
87
  end
84
88
  end
85
89
 
86
90
  convert :list, :array do |val|
87
- (val.respond_to?(:to_a) ? val : val.split(/(?<!\\),/))
88
- .map { |v| v.strip.gsub(/\\,/, ",") }
89
- .reject(&:empty?)
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
- values = val.respond_to?(:to_a) ? val : val.split(/[& ]/)
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
- key, value = pair.split(/[=:]/, 2)
96
- if (current = pairs[key.to_sym])
97
- pairs[key.to_sym] = Array(current) << value
98
- else
99
- pairs[key.to_sym] = value
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).map do |obj|
111
- conversions[type].(obj)
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
- h[k] = conversions[type].(v)
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
@@ -32,7 +32,7 @@ module TTY
32
32
  names.each do |name|
33
33
  if contain?(name)
34
34
  raise ConversionAlreadyDefined,
35
- "conversion #{name.inspect} is already defined"
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
- "unsupported conversion type #{conv_name.inspect}"
59
+ "unsupported conversion type #{conv_name.inspect}"
60
60
  end
61
61
  end # Converter
62
62
  end # Option
@@ -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
- # Duplicate an object making a deep copy
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
- case object
17
- when String then object.dup
18
- when *NONDUPLICATABLE then object
19
- when Hash then deep_dup_hash(object)
20
- when Array then deep_dup_array(object)
21
- else object.dup
22
- end
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
- # A deep copy of hash
36
+ # Deep copy an array
26
37
  #
27
- # @param [Hash] object
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.deep_dup_hash(object)
31
- object.each_with_object({}) do |(key, val), new_hash|
32
- new_hash[deep_dup(key)] = deep_dup(val)
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
- # A deep copy of array
53
+ # Deep copy a hash
37
54
  #
38
- # @param [Array] object
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.deep_dup_array(object)
42
- object.each_with_object([]) do |val, new_array|
43
- new_array << deep_dup(val)
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
@@ -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
- if usage.command.empty?
32
- usage.command(dasherize(demodulize(self.name)))
33
- end
34
- end
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 = { default: false }
72
+ defaults = {default: false}
73
73
  option(name, **defaults.merge(settings), &block)
74
74
  end
75
75
 
@@ -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.join(", "))
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