slop 3.6.0 → 4.0.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.
@@ -0,0 +1,20 @@
1
+ module Slop
2
+ # Base error class.
3
+ class Error < StandardError
4
+ end
5
+
6
+ # Raised when calling `call` on Slop::Option (this
7
+ # method must be overriden in subclasses)
8
+ class NotImplementedError < Error
9
+ end
10
+
11
+ # Raised when an option that expects an argument is
12
+ # executed without one. Suppress with the `suppress_errors`
13
+ # config option.
14
+ class MissingArgument < Error
15
+ end
16
+
17
+ # Raised when an unknown option is parsed. Suppress
18
+ # with the `suppress_errors` config option.
19
+ class UnknownOption < Error; end
20
+ end
@@ -1,214 +1,117 @@
1
- class Slop
1
+ module Slop
2
2
  class Option
3
-
4
- # The default Hash of configuration options this class uses.
5
- DEFAULT_OPTIONS = {
6
- :argument => false,
7
- :optional_argument => false,
8
- :tail => false,
9
- :default => nil,
10
- :callback => nil,
11
- :delimiter => ',',
12
- :limit => 0,
13
- :match => nil,
14
- :optional => true,
15
- :required => false,
16
- :as => String,
17
- :autocreated => false
3
+ DEFAULT_CONFIG = {
4
+ help: true
18
5
  }
19
6
 
20
- attr_reader :short, :long, :description, :config, :types
21
- attr_accessor :count, :argument_in_value
22
-
23
- # Incapsulate internal option information, mainly used to store
24
- # option specific configuration data, most of the meat of this
25
- # class is found in the #value method.
26
- #
27
- # slop - The instance of Slop tied to this Option.
28
- # short - The String or Symbol short flag.
29
- # long - The String or Symbol long flag.
30
- # description - The String description text.
31
- # config - A Hash of configuration options.
32
- # block - An optional block used as a callback.
33
- def initialize(slop, short, long, description, config = {}, &block)
34
- @slop = slop
35
- @short = short
36
- @long = long
37
- @description = description
38
- @config = DEFAULT_OPTIONS.merge(config)
39
- @count = 0
40
- @callback = block_given? ? block : config[:callback]
41
- @value = nil
7
+ # An Array of flags this option matches.
8
+ attr_reader :flags
42
9
 
43
- @types = {
44
- :string => proc { |v| v.to_s },
45
- :symbol => proc { |v| v.to_sym },
46
- :integer => proc { |v| value_to_integer(v) },
47
- :float => proc { |v| value_to_float(v) },
48
- :range => proc { |v| value_to_range(v) },
49
- :regexp => proc { |v| Regexp.new(v) },
50
- :count => proc { |v| @count }
51
- }
52
-
53
- if long && long.size > @slop.config[:longest_flag]
54
- @slop.config[:longest_flag] = long.size
55
- end
10
+ # A custom description used for the help text.
11
+ attr_reader :desc
56
12
 
57
- @config.each_key do |key|
58
- predicate = :"#{key}?"
59
- unless self.class.method_defined? predicate
60
- self.class.__send__(:define_method, predicate) { !!@config[key] }
61
- end
62
- end
63
- end
13
+ # A Hash of configuration options.
14
+ attr_reader :config
64
15
 
65
- # Returns true if this option expects an argument.
66
- def expects_argument?
67
- config[:argument] && config[:argument] != :optional
68
- end
16
+ # An Integer count for the total times this option
17
+ # has been executed.
18
+ attr_reader :count
69
19
 
70
- # Returns true if this option accepts an optional argument.
71
- def accepts_optional_argument?
72
- config[:optional_argument] || config[:argument] == :optional
73
- end
20
+ # A custom proc that yields the option value when
21
+ # it's executed.
22
+ attr_reader :block
74
23
 
75
- # Returns the String flag of this option. Preferring the long flag.
76
- def key
77
- long || short
78
- end
24
+ # The end value for this option.
25
+ attr_writer :value
79
26
 
80
- # Call this options callback if one exists, and it responds to call().
81
- #
82
- # Returns nothing.
83
- def call(*objects)
84
- @callback.call(*objects) if @callback.respond_to?(:call)
27
+ def initialize(flags, desc, **config, &block)
28
+ @flags = flags
29
+ @desc = desc
30
+ @config = DEFAULT_CONFIG.merge(config)
31
+ @block = block
32
+ reset
85
33
  end
86
34
 
87
- # Set the new argument value for this option.
88
- #
89
- # We use this setter method to handle concatenating lists. That is,
90
- # when an array type is specified and used more than once, values from
91
- # both options will be grouped together and flattened into a single array.
92
- def value=(new_value)
93
- if config[:as].to_s.downcase == 'array'
94
- @value ||= []
95
-
96
- if new_value.respond_to?(:split)
97
- @value.concat new_value.split(config[:delimiter], config[:limit])
98
- end
99
- else
100
- @value = new_value
101
- end
35
+ # Reset the option count and value. Used when calling .reset
36
+ # on the Parser.
37
+ def reset
38
+ @value = nil
39
+ @count = 0
102
40
  end
103
41
 
104
- # Fetch the argument value for this option.
105
- #
106
- # Returns the Object once any type conversions have taken place.
107
- def value
108
- value = @value.nil? ? config[:default] : @value
42
+ # Since `call()` can be used/overriden in subclasses, this
43
+ # method is used to do general tasks like increment count. This
44
+ # ensures you don't *have* to call `super` when overriding `call()`.
45
+ # It's used in the Parser.
46
+ def ensure_call(value)
47
+ @count += 1
109
48
 
110
- if [true, false, nil].include?(value) && config[:as].to_s != 'count'
111
- return value
49
+ if value.nil? && expects_argument? && !suppress_errors?
50
+ raise Slop::MissingArgument, "missing argument for #{flag}"
112
51
  end
113
52
 
114
- type = config[:as]
115
- if type.respond_to?(:call)
116
- type.call(value)
117
- else
118
- if callable = types[type.to_s.downcase.to_sym]
119
- callable.call(value)
120
- else
121
- value
122
- end
123
- end
53
+ @value = call(value)
54
+ block.call(@value) if block.respond_to?(:call)
124
55
  end
125
56
 
126
- # Returns the help String for this option.
127
- def to_s
128
- return config[:help] if config[:help].respond_to?(:to_str)
57
+ # This method is called immediately when an option is found.
58
+ # Override it in sub-classes.
59
+ def call(_value)
60
+ raise NotImplementedError,
61
+ "you must override the `call' method for option #{self.class}"
62
+ end
129
63
 
130
- out = " #{short ? "-#{short}, " : ' ' * 4}"
64
+ # By default this method does nothing. It's called when all options
65
+ # have been parsed and allows you to mutate the `@value` attribute
66
+ # according to other options.
67
+ def finish(_result)
68
+ end
131
69
 
132
- if long
133
- out << "--#{long}"
134
- size = long.size
135
- diff = @slop.config[:longest_flag] - size
136
- out << (' ' * (diff + 6))
137
- else
138
- out << (' ' * (@slop.config[:longest_flag] + 8))
139
- end
70
+ # Override this if this option type does not expect an argument
71
+ # (i.e a boolean option type).
72
+ def expects_argument?
73
+ true
74
+ end
140
75
 
141
- if config[:default]
142
- default = config[:default]
143
- "#{out}#{description} (default: #{default})"
144
- else
145
- "#{out}#{description}"
146
- end
76
+ # Override this if you want to ignore the return value for an option
77
+ # (i.e so Result#to_hash does not include it).
78
+ def null?
79
+ false
147
80
  end
148
- alias help to_s
149
81
 
150
- # Returns the String inspection text.
151
- def inspect
152
- "#<Slop::Option [-#{short} | --#{long}" +
153
- "#{'=' if expects_argument?}#{'=?' if accepts_optional_argument?}]" +
154
- " (#{description}) #{config.inspect}"
82
+ # Returns the value for this option. Falls back to the default (or nil).
83
+ def value
84
+ @value || default_value
155
85
  end
156
86
 
157
- private
158
-
159
- # Convert an object to an Integer if possible.
160
- #
161
- # value - The Object we want to convert to an integer.
162
- #
163
- # Returns the Integer value if possible to convert, else a zero.
164
- def value_to_integer(value)
165
- if @slop.strict?
166
- begin
167
- Integer(value.to_s, 10)
168
- rescue ArgumentError
169
- raise InvalidArgumentError, "#{value} could not be coerced into Integer"
170
- end
171
- else
172
- value.to_s.to_i
173
- end
87
+ # Returns the default value for this option (default is nil).
88
+ def default_value
89
+ config[:default]
174
90
  end
175
91
 
176
- # Convert an object to a Float if possible.
177
- #
178
- # value - The Object we want to convert to a float.
179
- #
180
- # Returns the Float value if possible to convert, else a zero.
181
- def value_to_float(value)
182
- if @slop.strict?
183
- begin
184
- Float(value.to_s)
185
- rescue ArgumentError
186
- raise InvalidArgumentError, "#{value} could not be coerced into Float"
187
- end
188
- else
189
- value.to_s.to_f
190
- end
92
+ # Returns true if we should ignore errors that cause exceptions to be raised.
93
+ def suppress_errors?
94
+ config[:suppress_errors]
191
95
  end
192
96
 
193
- # Convert an object to a Range if possible.
194
- #
195
- # value - The Object we want to convert to a range.
196
- #
197
- # Returns the Range value if one could be found, else the original object.
198
- def value_to_range(value)
199
- case value.to_s
200
- when /\A(\-?\d+)\z/
201
- Range.new($1.to_i, $1.to_i)
202
- when /\A(-?\d+?)(\.\.\.?|-|,)(-?\d+)\z/
203
- Range.new($1.to_i, $3.to_i, $2 == '...')
204
- else
205
- if @slop.strict?
206
- raise InvalidArgumentError, "#{value} could not be coerced into Range"
207
- else
208
- value
209
- end
210
- end
97
+ # Returns all flags joined by a comma. Used by the help string.
98
+ def flag
99
+ flags.join(", ")
100
+ end
101
+
102
+ # Returns the last key as a symbol. Used in Options.to_hash.
103
+ def key
104
+ (config[:key] || flags.last.sub(/\A--?/, '')).to_sym
105
+ end
106
+
107
+ # Returns true if this option should be displayed in help text.
108
+ def help?
109
+ config[:help]
211
110
  end
212
111
 
112
+ # Returns the help text for this option (flags and description).
113
+ def to_s(offset: 0)
114
+ "%-#{offset}s %s" % [flag, desc]
115
+ end
213
116
  end
214
117
  end
@@ -0,0 +1,141 @@
1
+ module Slop
2
+ class Options
3
+ include Enumerable
4
+
5
+ DEFAULT_CONFIG = {
6
+ suppress_errors: false,
7
+ type: "null",
8
+ banner: true,
9
+ }
10
+
11
+ # The Array of Option instances we've created.
12
+ attr_reader :options
13
+
14
+ # An Array of separators used for the help text.
15
+ attr_reader :separators
16
+
17
+ # Our Parser instance.
18
+ attr_reader :parser
19
+
20
+ # A Hash of configuration options.
21
+ attr_reader :config
22
+
23
+ # The String banner prefixed to the help string.
24
+ attr_accessor :banner
25
+
26
+ def initialize(**config)
27
+ @options = []
28
+ @separators = []
29
+ @banner = "usage: #{$0} [options]"
30
+ @config = DEFAULT_CONFIG.merge(config)
31
+ @parser = Parser.new(self, @config)
32
+
33
+ yield self if block_given?
34
+ end
35
+
36
+ # Add a new option. This method is an alias for adding a NullOption
37
+ # (i.e an option with an ignored return value).
38
+ #
39
+ # Example:
40
+ #
41
+ # opts = Slop.parse do |o|
42
+ # o.on '--version' do
43
+ # puts Slop::VERSION
44
+ # end
45
+ # end
46
+ #
47
+ # opts.to_hash #=> {}
48
+ #
49
+ # Returns the newly created Option subclass.
50
+ def on(*flags, **config, &block)
51
+ desc = flags.pop unless flags.last.start_with?('-')
52
+ config = self.config.merge(config)
53
+ klass = Slop.string_to_option_class(config[:type].to_s)
54
+ option = klass.new(flags, desc, config, &block)
55
+
56
+ add_option option
57
+ end
58
+
59
+ # Add a separator between options. Used when displaying
60
+ # the help text.
61
+ def separator(string)
62
+ if separators[options.size]
63
+ separators.last << "\n#{string}"
64
+ else
65
+ separators[options.size] = string
66
+ end
67
+ end
68
+
69
+ # Sugar to avoid `options.parser.parse(x)`.
70
+ def parse(strings)
71
+ parser.parse(strings)
72
+ end
73
+
74
+ # Implements the Enumerable interface.
75
+ def each(&block)
76
+ options.each(&block)
77
+ end
78
+
79
+ # Handle custom option types. Will fall back to raising an
80
+ # exception if an option is not defined.
81
+ def method_missing(name, *args, **config, &block)
82
+ if respond_to_missing?(name)
83
+ config[:type] = name
84
+ on(*args, config, &block)
85
+ else
86
+ super
87
+ end
88
+ end
89
+
90
+ def respond_to_missing?(name, include_private = false)
91
+ Slop.option_defined?(name) || super
92
+ end
93
+
94
+ # Return a copy of our options Array.
95
+ def to_a
96
+ options.dup
97
+ end
98
+
99
+ # Returns the help text for this options. Used by Result#to_s.
100
+ def to_s(prefix: " " * 4)
101
+ str = config[:banner] ? "#{banner}\n" : ""
102
+ len = longest_flag_length
103
+
104
+ options.select(&:help?).each_with_index do |opt, i|
105
+ # use the index to fetch an associated separator
106
+ if sep = separators[i]
107
+ str << "#{sep}\n"
108
+ end
109
+
110
+ str << "#{prefix}#{opt.to_s(offset: len)}\n"
111
+ end
112
+
113
+ str
114
+ end
115
+
116
+ private
117
+
118
+ def longest_flag_length
119
+ (o = longest_option) && o.flag.length || 0
120
+ end
121
+
122
+ def longest_option
123
+ options.max { |a, b| a.flag.length <=> b.flag.length }
124
+ end
125
+
126
+ def add_option(option)
127
+ options.each do |o|
128
+ flags = o.flags & option.flags
129
+
130
+ # Raise an error if we found an existing option with the same
131
+ # flags. I can't immediately see a use case for this..
132
+ if flags.any?
133
+ raise ArgumentError, "duplicate flags: #{flags}"
134
+ end
135
+ end
136
+
137
+ options << option
138
+ option
139
+ end
140
+ end
141
+ end