mamertes 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +15 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +6 -0
  4. data/.travis-gemfile +15 -0
  5. data/.travis.yml +10 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +21 -0
  8. data/README.md +126 -0
  9. data/Rakefile +29 -0
  10. data/doc/Mamertes.html +155 -0
  11. data/doc/Mamertes/Application.html +3057 -0
  12. data/doc/Mamertes/Command.html +7031 -0
  13. data/doc/Mamertes/CommandMethods.html +125 -0
  14. data/doc/Mamertes/CommandMethods/Children.html +1286 -0
  15. data/doc/Mamertes/CommandMethods/Help.html +209 -0
  16. data/doc/Mamertes/Error.html +631 -0
  17. data/doc/Mamertes/Localizer.html +376 -0
  18. data/doc/Mamertes/Option.html +6671 -0
  19. data/doc/Mamertes/Parser.html +276 -0
  20. data/doc/Mamertes/ParserMethods.html +125 -0
  21. data/doc/Mamertes/ParserMethods/General.html +134 -0
  22. data/doc/Mamertes/ParserMethods/General/ClassMethods.html +574 -0
  23. data/doc/Mamertes/Version.html +189 -0
  24. data/doc/_index.html +276 -0
  25. data/doc/class_list.html +54 -0
  26. data/doc/css/common.css +1 -0
  27. data/doc/css/full_list.css +57 -0
  28. data/doc/css/style.css +338 -0
  29. data/doc/file.README.html +198 -0
  30. data/doc/file_list.html +56 -0
  31. data/doc/frames.html +28 -0
  32. data/doc/index.html +198 -0
  33. data/doc/js/app.js +214 -0
  34. data/doc/js/full_list.js +178 -0
  35. data/doc/js/jquery.js +4 -0
  36. data/doc/method_list.html +509 -0
  37. data/doc/top-level-namespace.html +112 -0
  38. data/lib/mamertes.rb +18 -0
  39. data/lib/mamertes/application.rb +206 -0
  40. data/lib/mamertes/command.rb +529 -0
  41. data/lib/mamertes/option.rb +236 -0
  42. data/lib/mamertes/parser.rb +317 -0
  43. data/lib/mamertes/version.rb +24 -0
  44. data/locales/en.yml +40 -0
  45. data/locales/it.yml +40 -0
  46. data/mamertes.gemspec +30 -0
  47. data/spec/coverage_helper.rb +20 -0
  48. data/spec/mamertes/application_spec.rb +181 -0
  49. data/spec/mamertes/command_spec.rb +526 -0
  50. data/spec/mamertes/option_spec.rb +274 -0
  51. data/spec/mamertes/parser_spec.rb +126 -0
  52. data/spec/spec_helper.rb +15 -0
  53. metadata +115 -0
@@ -0,0 +1,236 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the mamertes gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ module Mamertes
8
+ # List of valid option types.
9
+ #
10
+ # Values are the default values for that type.
11
+ #
12
+ # For any unknown type, the default value is `false`, it means that any unknown type is managed as a Boolean value with no argument.
13
+ OPTION_TYPES = {String => "", Integer => 0, Float => 0.0, Array => []}
14
+ OPTION_TYPES.default = false
15
+
16
+ # This class represents an option for a command.
17
+ #
18
+ # @attribute name
19
+ # @return [String] The name of this option.
20
+ # @attribute short
21
+ # @return [String] The short form (i.e.: `-h`) for this option.
22
+ # @attribute long
23
+ # @return [String] The long form (i.e.: `--help`) for this option.
24
+ # @attribute type
25
+ # @return [Class] The type of this option.
26
+ # @attribute required
27
+ # @return [Boolean] If this option is required.
28
+ # @attribute default
29
+ # @return [Object] The default value of this option.
30
+ # @attribute meta
31
+ # @return [String] The META argument for this option, used only when showing the help.
32
+ # @attribute help
33
+ # @return [String] An help message for this option.
34
+ # @attribute value
35
+ # @return [Object] The current value of this option.
36
+ # @attribute action
37
+ # @return [Proc] The action associated to this option.
38
+ # @attribute validator
39
+ # @return [Array|Regexp] or A constraint for valid values. Can be an Array of valid values or a Regexp.
40
+ # @attribute parent
41
+ # @return [Command] The parent of this option.
42
+ class Option
43
+ attr_accessor :name
44
+ attr_accessor :short
45
+ attr_accessor :long
46
+ attr_accessor :type
47
+ attr_accessor :required
48
+ attr_accessor :default
49
+ attr_accessor :meta
50
+ attr_accessor :help
51
+ attr_accessor :value
52
+ attr_accessor :action
53
+ attr_accessor :validator
54
+ attr_accessor :parent
55
+
56
+ # Creates a new option.
57
+ #
58
+ # @param name [String] The name of this option. Must be unique.
59
+ # @param forms [Array] An array of short and long forms for this option. Missing forms will be inferred by the name.
60
+ # @param options [Hash] The settings for this option.
61
+ # @param action [Proc] The action of this option.
62
+ def initialize(name, forms = [], options = {}, &action)
63
+ @name = name.ensure_string
64
+ @provided = false
65
+ setup_forms(forms)
66
+ setup_options(options)
67
+ setup_action(action)
68
+ end
69
+
70
+ # Sets the short form of this option.
71
+ #
72
+ # @param value [String] The short form of this option.
73
+ def short=(value)
74
+ value = @name[0, 1] if !value.present?
75
+
76
+ # Clean value
77
+ final_value = value.to_s.match(/^-{0,2}([a-z0-9])(.*)$/i)[1]
78
+
79
+ @short = final_value if final_value.present?
80
+ end
81
+
82
+ # Sets the long form of this option.
83
+ #
84
+ # @param value [String] The short form of this option.
85
+ def long=(value)
86
+ value = @name if !value.present?
87
+
88
+ # Clean value
89
+ final_value = value.to_s.match(/^-{0,2}(.+)$/)[1]
90
+
91
+ @long = final_value if final_value.present?
92
+ end
93
+
94
+ # Sets the long form of this option. Can be a Object, an Array or a Regexp.
95
+ #
96
+ # @param value [String] The validator of this option.
97
+ def validator=(value)
98
+ value = nil if value.blank? || (value.is_a?(Regexp) && value.source.blank?)
99
+ value = value.ensure_array(nil, true, true, true, :ensure_string) if !value.nil? && !value.is_a?(Regexp)
100
+ @validator = value
101
+ end
102
+
103
+ # Returns the short form with a dash prepended.
104
+ #
105
+ # @return [String] The short form with a dash prepended.
106
+ def complete_short
107
+ "-#{@short}"
108
+ end
109
+
110
+ # Returns the long form with two dashes prepended.
111
+ #
112
+ # @return [String] The short form with two dashes prepended.
113
+ def complete_long
114
+ "--#{@long}"
115
+ end
116
+
117
+ # Returns a label for this option, combining short and long forms.
118
+ #
119
+ # @return [String] A label for this option.
120
+ def label
121
+ [complete_short, complete_long].compact.join("/")
122
+ end
123
+
124
+ # Returns the meta argument for this option.
125
+ #
126
+ # @return [String|NilClass] Returns the current meta argument for this option (the default value is the option name uppercased) or `nil`, if this option doesn't require a meta argument.
127
+ def meta
128
+ requires_argument? ? (@meta.present? ? @meta : @name.upcase) : nil
129
+ end
130
+
131
+ # Get the current default value for this option.
132
+ #
133
+ # @return [Object] The default value for this option.
134
+ def default
135
+ @default || ::Mamertes::OPTION_TYPES[@type]
136
+ end
137
+
138
+ # Check if the current option has a default value.
139
+ #
140
+ # @return [Boolean] If the current option has a default value.
141
+ def has_default?
142
+ !@default.nil?
143
+ end
144
+
145
+ # Sets the value of this option and also make sure that it is validated.
146
+ #
147
+ # @param value [Object] The new value of this option.
148
+ # @param raise_error [Boolean] If raise an ArgumentError in case of validation errors.
149
+ # @return [Boolean] `true` if operation succeeded, `false` otherwise.
150
+ def set(value, raise_error = true)
151
+ vs = @validator.present? ? (@validator.is_a?(Array) ? :array : :regexp) : false # Check we have a validator
152
+ rv = vs ? @validator.send(vs == :array ? "include?" : "match", value) : true
153
+
154
+ if rv then
155
+ @value = value
156
+ @provided = true
157
+ elsif raise_error then # Validation failed
158
+ handle_set_failure(vs)
159
+ else
160
+ false
161
+ end
162
+ end
163
+
164
+ # Executes the action associated to this option.
165
+ def execute_action
166
+ if @action.present? then
167
+ @provided = true
168
+ @action.call(parent, self)
169
+ end
170
+ end
171
+
172
+ # Checks if this option requires an argument.
173
+ #
174
+ # @return [Boolean] `true` if this option requires an argument, `false` otherwise.
175
+ def requires_argument?
176
+ [String, Integer, Float, Array].include?(@type) && @action.blank?
177
+ end
178
+
179
+ # If this option was provided.
180
+ #
181
+ # @return [Boolean] `true` if this option was provided, `false` otherwise.
182
+ def provided?
183
+ @provided
184
+ end
185
+
186
+ # Check if this command has a help.
187
+ #
188
+ # @return [Boolean] `true` if this command has a help, `false` otherwise.
189
+ def has_help?
190
+ @help.present?
191
+ end
192
+
193
+ # Get the current value for this option.
194
+ #
195
+ # @return [Object] The current value of this option.
196
+ def value
197
+ provided? ? @value : default
198
+ end
199
+
200
+ private
201
+ # Setups the forms of the this option.
202
+ #
203
+ # @param forms [Array] An array of short and long forms for this option. Missing forms will be inferred by the name.
204
+ def setup_forms(forms)
205
+ self.short = forms.length > 0 ? forms[0] : @name[0, 1]
206
+ self.long = forms.length == 2 ? forms[1] : @name
207
+ end
208
+
209
+ # Setups the settings of the this option.
210
+ #
211
+ # @param options [Hash] The settings for this option.
212
+ def setup_options(options)
213
+ (options.is_a?(::Hash) ? options : {}).each_pair do |option, value|
214
+ send("#{option}=", value) if respond_to?("#{option}=")
215
+ end
216
+ end
217
+
218
+ # Setups the action of the this option.
219
+ #
220
+ # @param action [Proc] The action of this option.
221
+ def setup_action(action)
222
+ @action = action if action.present? && action.respond_to?(:call) && action.try(:arity) == 2
223
+ end
224
+
225
+ # Handle failure in setting an option.
226
+ #
227
+ # @param vs [Symbol] The type of validator.
228
+ def handle_set_failure(vs)
229
+ if vs == :array then
230
+ raise ::Mamertes::Error.new(self, :validation_failed, @parent.i18n.invalid_value(label, ::Mamertes::Parser.smart_join(@validator)))
231
+ else
232
+ raise ::Mamertes::Error.new(self, :validation_failed, @parent.i18n.invalid_for_regexp(label, @validator.inspect))
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,317 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the mamertes gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ module Mamertes
8
+ # Methods for the {Parser Parser} class.
9
+ module ParserMethods
10
+ # General methods.
11
+ module General
12
+ extend ActiveSupport::Concern
13
+
14
+ # Class methods
15
+ module ClassMethods
16
+ # Joins an array using multiple separators.
17
+ #
18
+ # @param array [Array] The array to join.
19
+ # @param separator [String] The separator to use for all but last join.
20
+ # @param last_separator [String] The separator to use for the last join.
21
+ # @param quote [String] If not nil, elements are quoted with that element.
22
+ # @return [String] The joined array.
23
+ def smart_join(array, separator = ", ", last_separator = " and ", quote = "\"")
24
+ separator = separator.ensure_string
25
+ last_separator = last_separator.ensure_string
26
+ array = array.ensure_array {|a| quote.present? ? "#{quote}#{a}#{quote}" : a.ensure_string }
27
+ array.length < 2 ? (array[0] || "") : (array[0, array.length - 1].join(separator) + last_separator + array[-1])
28
+ end
29
+
30
+ # Finds a command which corresponds to an argument.
31
+ #
32
+ # @param arg [String] The string to match.
33
+ # @param command [Command] The command to search subcommand in.
34
+ # @param args [String] The complete list of arguments passed.
35
+ # @param separator [String] The separator for joined syntax commands.
36
+ # @return [Hash|NilClass] An hash with `name` and `args` keys if a valid subcommand is found, `nil` otherwise.
37
+ def find_command(arg, command, args, separator = ":")
38
+ args = args.ensure_array.dup
39
+
40
+ if command.commands.present? then
41
+ arg, args = adjust_command(arg, args, separator)
42
+
43
+ matching = match_subcommands(arg, command)
44
+ if matching.length == 1 # Found a command
45
+ {name: matching[0], args: args}
46
+ elsif matching.length > 1 # Ambiguous match
47
+ raise ::Mamertes::Error.new(command, :ambiguous_command, command.i18n.ambigous_command(arg, ::Mamertes::Parser.smart_join(matching)))
48
+ end
49
+ else
50
+ nil
51
+ end
52
+ end
53
+
54
+ # Parses a command/application.
55
+ #
56
+ # @param command [Command] The command or application to parse.
57
+ # @param args [Array] The arguments to parse.
58
+ # @return [Hash|NilClass] An hash with `name` (of a subcommand to execute) and `args` keys if a valid subcommand is found, `nil` otherwise.
59
+ def parse(command, args)
60
+ ::Mamertes::Parser.new.parse(command, args)
61
+ end
62
+
63
+ private
64
+ # Adjusts a command so that it only specify a single command.
65
+ #
66
+ # @param arg [String] The string to match.
67
+ # @param args [String] The complete list of arguments passed.
68
+ # @param separator [String] The separator for joined syntax commands.
69
+ # @return [Array] Adjust command and arguments.
70
+ def adjust_command(arg, args, separator)
71
+ if arg.index(separator) then
72
+ tokens = arg.split(separator, 2)
73
+ arg = tokens[0]
74
+ args.insert(0, tokens[1])
75
+ end
76
+
77
+ [arg, args]
78
+ end
79
+
80
+ # Matches a string against a command's subcommands.
81
+ #
82
+ # @param arg [String] The string to match.
83
+ # @param command [Command] The command to search subcommand in.
84
+ # @return [Array] The matching subcommands.
85
+ def match_subcommands(arg, command)
86
+ command.commands.keys.select {|c| c =~ /^(#{Regexp.quote(arg)})/ }.compact
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ # The parser for the command line.
93
+ class Parser
94
+ include ::Mamertes::ParserMethods::General
95
+
96
+ # Parses a command/application.
97
+ #
98
+ # @param command [Command] The command or application to parse.
99
+ # @param args [Array] The arguments to parse.
100
+ # @return [Hash|NilClass] An hash with `name` (of a subcommand to execute) and `args` keys if a valid subcommand is found, `nil` otherwise.
101
+ def parse(command, args)
102
+ args = args.ensure_array.dup
103
+ forms, parser = create_parser(command)
104
+ perform_parsing(parser, command, args, forms)
105
+ end
106
+
107
+ private
108
+ # Creates a new option parser.
109
+ #
110
+ # @param command [Command] The command or application to parse.
111
+ # @return [OptionParser] The new parser
112
+ def create_parser(command)
113
+ forms = {}
114
+ parser = OptionParser.new do |opts|
115
+ # Add every option
116
+ command.options.each_pair do |_, option|
117
+ check_unique(command, forms, option)
118
+ setup_option(command, opts, option)
119
+ end
120
+ end
121
+
122
+ [forms, parser]
123
+ end
124
+
125
+ # Perform the parsing
126
+ #
127
+ # @param parser [OptionParser] The option parser.
128
+ # @param command [Command] The command or application to parse.
129
+ # @param args [Array] The arguments to parse.
130
+ # @param forms [Hash] The current forms.
131
+ def perform_parsing(parser, command, args, forms)
132
+ rv = nil
133
+
134
+ begin
135
+ rv = execute_parsing(parser, command, args)
136
+ rescue OptionParser::NeedlessArgument, OptionParser::MissingArgument, OptionParser::InvalidOption => oe
137
+ type = oe.class.to_s.gsub("OptionParser::", "").underscore.to_sym
138
+ opt = oe.args.first
139
+ raise ::Mamertes::Error.new(forms[opt], type, command.i18n.send(type, opt))
140
+ rescue => e
141
+ raise e
142
+ end
143
+
144
+ rv
145
+ end
146
+
147
+ # Executes the parsing.
148
+ #
149
+ # @param parser [OptionParser] The option parser.
150
+ # @param command [Command] The command or application to parse.
151
+ # @param args [Array] The arguments to parse.
152
+ # @return [Command|nil] A command to execute or `nil` if no valid command was found.
153
+ def execute_parsing(parser, command, args)
154
+ rv = nil
155
+
156
+ if command.options.present? then
157
+ rv = parse_options(parser, command, args)
158
+ check_required_options(command)
159
+ elsif args.present? then
160
+ rv = find_command_to_execute(command, args)
161
+ end
162
+
163
+ rv
164
+ end
165
+
166
+ # Setups an option for a command.
167
+ #
168
+ # @param command [Command] The command or application to parse.
169
+ # @param opts [Object] The current set options.
170
+ # @param option [Option] The option to set.
171
+ def setup_option(command, opts, option)
172
+ case option.type.to_s
173
+ when "String" then parse_string(command, opts, option)
174
+ when "Integer" then parse_number(command, opts, option, :is_integer?, :to_integer, command.i18n.invalid_integer(option.label))
175
+ when "Float" then parse_number(command, opts, option, :is_float?, :to_float, command.i18n.invalid_float(option.label))
176
+ when "Array" then parse_array(command, opts, option)
177
+ else option.action.present? ? parse_action(opts, option) : parse_boolean(opts, option)
178
+ end
179
+ end
180
+
181
+ # Checks if a option is unique.
182
+ #
183
+ # @param command [Command] The command or application to parse.
184
+ # @param forms [Hash] The current forms.
185
+ # @param option [Option] The option to set.
186
+ def check_unique(command, forms, option)
187
+ if forms[option.complete_short] || forms[option.complete_long] then
188
+ raise ::Mamertes::Error.new(command, :ambiguous_form, command.i18n.conflicting_options(option.label, forms[option.complete_short].label))
189
+ else
190
+ forms[option.complete_short] = option.dup
191
+ forms[option.complete_long] = option.dup
192
+ end
193
+ end
194
+
195
+ # Parses an action option. A block must be provided to deal with the value.
196
+ #
197
+ # @param command [Command] The command or application to parse.
198
+ # @param opts [Object] The current set options.
199
+ # @param option [Option] The option to set.
200
+ def parse_option(command, opts, option)
201
+ opts.on("#{option.complete_short} #{option.meta || command.i18n.help_arg}", "#{option.complete_long} #{option.meta || command.i18n.help_arg}") do |value|
202
+ yield(value)
203
+ end
204
+ end
205
+
206
+ # Parses an action option.
207
+ #
208
+ # @param opts [Object] The current set options.
209
+ # @param option [Option] The option to set.
210
+ def parse_action(opts, option)
211
+ opts.on("-#{option.short}", "--#{option.long}") do |_|
212
+ option.execute_action
213
+ end
214
+ end
215
+
216
+ # Parses a string option.
217
+ #
218
+ # @param command [Command] The command or application to parse.
219
+ # @param opts [Object] The current set options.
220
+ # @param option [Option] The option to set.
221
+ def parse_string(command, opts, option)
222
+ parse_option(command, opts, option) { |value| option.set(value) }
223
+ end
224
+
225
+ # Parses a number option.
226
+ #
227
+ # @param command [Command] The command or application to parse.
228
+ # @param opts [Object] The current set options.
229
+ # @param option [Option] The option to set.
230
+ # @param check_method [Symbol] The method to execute to check option validity. Must return a boolean.
231
+ # @param convert_method [Symbol] The method to execute to convert option.
232
+ # @param invalid_message [String] The string to send in case of invalid arguments.
233
+ def parse_number(command, opts, option, check_method, convert_method, invalid_message)
234
+ parse_option(command, opts, option) do |value|
235
+ raise ::Mamertes::Error.new(option, :invalid_argument, invalid_message) if !value.send(check_method)
236
+ option.set(value.send(convert_method))
237
+ end
238
+ end
239
+
240
+ # Parses an array option.
241
+ #
242
+ # @param command [Command] The command or application to parse.
243
+ # @param opts [Object] The current set options.
244
+ # @param option [Option] The option to set.
245
+ def parse_array(command, opts, option)
246
+ opts.on("#{option.complete_short} #{option.meta || command.i18n.help_arg}", "#{option.complete_long} #{option.meta || command.i18n.help_arg}", Array) do |value|
247
+ option.set(value.ensure_array)
248
+ end
249
+ end
250
+
251
+ # Parses an action option.
252
+ #
253
+ # @param opts [Object] The current set options.
254
+ # @param option [Option] The option to set.
255
+ def parse_boolean(opts, option)
256
+ opts.on("-#{option.short}", "--#{option.long}") do |value|
257
+ option.set(value.to_boolean)
258
+ end
259
+ end
260
+
261
+ # Parses options of a command.
262
+ #
263
+ # @param parser [OptionParser] The option parser.
264
+ # @param command [Command] The command or application to parse.
265
+ # @param args [Array] The arguments to parse.
266
+ # @return [Command|nil] A command to execute or `nil` if no command was found.
267
+ def parse_options(parser, command, args)
268
+ rv = nil
269
+
270
+ # Parse options
271
+ parser.order!(args) do |arg|
272
+ fc = ::Mamertes::Parser.find_command(arg, command, args)
273
+
274
+ if fc.present? then
275
+ rv = fc
276
+ parser.terminate
277
+ else
278
+ command.argument(arg)
279
+ end
280
+ end
281
+
282
+ rv
283
+ end
284
+
285
+ # Checks if all options of a command are present.
286
+ #
287
+ # @param command [Command] The command or application to parse.
288
+ def check_required_options(command)
289
+ # Check if any required option is missing.
290
+ command.options.each_pair do |name, option|
291
+ raise ::Mamertes::Error.new(option, :missing_option, command.i18n.missing_option(option.label)) if option.required && !option.provided?
292
+ end
293
+ end
294
+
295
+ # Finds a command to execute
296
+ #
297
+ # @param command [Command] The command or application to parse.
298
+ # @param args [Array] The arguments to parse.
299
+ # @return [Command|nil] A command to execute or `nil` if no command was found.
300
+ def find_command_to_execute(command, args)
301
+ rv = nil
302
+
303
+ # Try to find a command into the first argument
304
+ fc = ::Mamertes::Parser.find_command(args[0], command, args[1, args.length - 1])
305
+
306
+ if fc.present? then
307
+ rv = fc
308
+ else
309
+ args.each do |arg|
310
+ command.argument(arg)
311
+ end
312
+ end
313
+
314
+ rv
315
+ end
316
+ end
317
+ end