slop 3.6.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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