bovem 2.4.1 → 3.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/Gemfile +1 -1
  4. data/README.md +98 -2
  5. data/bovem.gemspec +3 -3
  6. data/doc/Bovem.html +25 -6
  7. data/doc/Bovem/Application.html +3057 -0
  8. data/doc/Bovem/Command.html +7031 -0
  9. data/doc/Bovem/CommandMethods.html +125 -0
  10. data/doc/Bovem/CommandMethods/Children.html +1285 -0
  11. data/doc/Bovem/CommandMethods/Help.html +209 -0
  12. data/doc/Bovem/Configuration.html +3 -3
  13. data/doc/Bovem/Console.html +8 -8
  14. data/doc/Bovem/ConsoleMethods.html +3 -3
  15. data/doc/Bovem/ConsoleMethods/Interactions.html +3 -3
  16. data/doc/Bovem/ConsoleMethods/Interactions/ClassMethods.html +3 -3
  17. data/doc/Bovem/ConsoleMethods/Logging.html +4 -4
  18. data/doc/Bovem/ConsoleMethods/Logging/ClassMethods.html +3 -3
  19. data/doc/Bovem/ConsoleMethods/Output.html +3 -3
  20. data/doc/Bovem/ConsoleMethods/StyleHandling.html +4 -4
  21. data/doc/Bovem/ConsoleMethods/StyleHandling/ClassMethods.html +8 -8
  22. data/doc/Bovem/Errors.html +4 -4
  23. data/doc/Bovem/Errors/Error.html +631 -0
  24. data/doc/Bovem/Errors/InvalidConfiguration.html +3 -3
  25. data/doc/Bovem/Errors/InvalidLogger.html +3 -3
  26. data/doc/Bovem/Localizer.html +376 -0
  27. data/doc/Bovem/Logger.html +64 -160
  28. data/doc/Bovem/Option.html +7009 -0
  29. data/doc/Bovem/Parser.html +276 -0
  30. data/doc/Bovem/ParserMethods.html +125 -0
  31. data/doc/Bovem/ParserMethods/General.html +134 -0
  32. data/doc/Bovem/ParserMethods/General/ClassMethods.html +574 -0
  33. data/doc/Bovem/Shell.html +8 -8
  34. data/doc/Bovem/ShellMethods.html +3 -3
  35. data/doc/Bovem/ShellMethods/Directories.html +3 -3
  36. data/doc/Bovem/ShellMethods/Execute.html +3 -3
  37. data/doc/Bovem/ShellMethods/General.html +3 -3
  38. data/doc/Bovem/ShellMethods/Read.html +3 -3
  39. data/doc/Bovem/ShellMethods/Write.html +3 -3
  40. data/doc/Bovem/Version.html +6 -6
  41. data/doc/_index.html +119 -11
  42. data/doc/class_list.html +1 -1
  43. data/doc/file.README.html +98 -5
  44. data/doc/frames.html +1 -1
  45. data/doc/index.html +98 -5
  46. data/doc/method_list.html +476 -26
  47. data/doc/top-level-namespace.html +3 -3
  48. data/lib/bovem.rb +8 -1
  49. data/lib/bovem/application.rb +158 -0
  50. data/lib/bovem/command.rb +529 -0
  51. data/lib/bovem/console.rb +8 -8
  52. data/lib/bovem/errors.rb +27 -0
  53. data/lib/bovem/localizer.rb +27 -0
  54. data/lib/bovem/logger.rb +2 -8
  55. data/lib/bovem/option.rb +250 -0
  56. data/lib/bovem/parser.rb +317 -0
  57. data/lib/bovem/shell.rb +2 -2
  58. data/lib/bovem/version.rb +3 -3
  59. data/locales/en.yml +33 -0
  60. data/locales/it.yml +33 -0
  61. data/spec/bovem/application_spec.rb +170 -0
  62. data/spec/bovem/command_spec.rb +526 -0
  63. data/spec/bovem/configuration_spec.rb +4 -4
  64. data/spec/bovem/console_spec.rb +22 -22
  65. data/spec/bovem/errors_spec.rb +18 -0
  66. data/spec/bovem/logger_spec.rb +22 -12
  67. data/spec/bovem/option_spec.rb +307 -0
  68. data/spec/bovem/parser_spec.rb +126 -0
  69. data/spec/bovem/shell_spec.rb +4 -4
  70. metadata +32 -5
data/lib/bovem/console.rb CHANGED
@@ -29,9 +29,9 @@ module Bovem
29
29
  style = style.ensure_string.strip.parameterize
30
30
 
31
31
  if style.present? then
32
- ::Bovem::Console.replace_term_code(Bovem::TERM_EFFECTS, style, 0) ||
33
- ::Bovem::Console.replace_term_code(Bovem::TERM_COLORS, style, 30) ||
34
- ::Bovem::Console.replace_term_code(Bovem::TERM_COLORS, style.gsub(/^bg_/, ""), 40) ||
32
+ Bovem::Console.replace_term_code(Bovem::TERM_EFFECTS, style, 0) ||
33
+ Bovem::Console.replace_term_code(Bovem::TERM_COLORS, style, 30) ||
34
+ Bovem::Console.replace_term_code(Bovem::TERM_COLORS, style.gsub(/^bg_/, ""), 40) ||
35
35
  ""
36
36
  else
37
37
  ""
@@ -80,10 +80,10 @@ module Bovem
80
80
  message.ensure_string.gsub(/((\{mark=([a-z\-_\s,]+)\})|(\{\/mark\}))/mi) do
81
81
  if $1 == "{/mark}" then # If it is a tag, pop from the latest opened.
82
82
  stack.pop
83
- plain || stack.blank? ? "" : ::Bovem::Console.parse_styles(stack.last)
83
+ plain || stack.blank? ? "" : Bovem::Console.parse_styles(stack.last)
84
84
  else
85
85
  styles = $3.ensure_string
86
- replacement = plain ? "" : ::Bovem::Console.parse_styles(styles)
86
+ replacement = plain ? "" : Bovem::Console.parse_styles(styles)
87
87
 
88
88
  if replacement.length > 0 then
89
89
  stack << "reset" if stack.blank?
@@ -104,7 +104,7 @@ module Bovem
104
104
  # @param plain [Boolean] If ignore (cleanify) color markers into the message.
105
105
  # @return [String] The replaced message.
106
106
  def replace_markers(message, plain = false)
107
- ::Bovem::Console.replace_markers(message, plain)
107
+ Bovem::Console.replace_markers(message, plain)
108
108
  end
109
109
  end
110
110
 
@@ -281,7 +281,7 @@ module Bovem
281
281
  #
282
282
  # @see #format
283
283
  def write_banner_aligned(message, suffix = "\n", indent = true, wrap = false, plain = false, print = true)
284
- write((" " * (::Bovem::Console.min_banner_length + 3)) + message.ensure_string, suffix, indent, wrap, plain, print)
284
+ write((" " * (Bovem::Console.min_banner_length + 3)) + message.ensure_string, suffix, indent, wrap, plain, print)
285
285
  end
286
286
 
287
287
  # Writes a status to the output. Valid values are `:ok`, `:pass`, `:fail`, `:warn`.
@@ -604,7 +604,7 @@ module Bovem
604
604
  #
605
605
  # @return [Console] A new instance.
606
606
  def self.instance
607
- @instance ||= ::Bovem::Console.new
607
+ @instance ||= Bovem::Console.new
608
608
  end
609
609
 
610
610
  # Initializes a new Console.
data/lib/bovem/errors.rb CHANGED
@@ -14,5 +14,32 @@ module Bovem
14
14
  # This exception is raised if a {Logger Logger} is invalid.
15
15
  class InvalidLogger < ::ArgumentError
16
16
  end
17
+
18
+ # This exception is raised when something goes wrong.
19
+ #
20
+ # @attribute [r] target
21
+ # @return [Object] The target of this error.
22
+ # @attribute [r] reason
23
+ # @return [Symbol] The reason of failure.
24
+ # @attribute [r] message
25
+ # @return [String] A human readable message.
26
+ class Error < ArgumentError
27
+ attr_reader :target
28
+ attr_reader :reason
29
+ attr_reader :message
30
+
31
+ # Initializes a new error
32
+ #
33
+ # @param target [Object] The target of this error.
34
+ # @param reason [Symbol] The reason of failure.
35
+ # @param message [String] A human readable message.
36
+ def initialize(target, reason, message)
37
+ super(message)
38
+
39
+ @target = target
40
+ @reason = reason
41
+ @message = message
42
+ end
43
+ end
17
44
  end
18
45
  end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the bovem 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 Bovem
8
+ # This class is used to localize strings inside classes methods.
9
+ class Localizer < ::Lazier::Localizer
10
+ # Initialize a new localizer.
11
+ #
12
+ # @param locale [String|Symbol] The locale to use for localization.
13
+ def initialize(locale)
14
+ super("bovem.application", ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/"), locale)
15
+ end
16
+
17
+ # Localize a message in a specified locale.
18
+ #
19
+ # @param locale [String|Symbol] The locale to use for localization.
20
+ # @param message [String|Symbol] The message to localize.
21
+ # @param args [Array] Optional arguments to localize the message.
22
+ # @return [String|R18n::Untranslated] The localized message.
23
+ def self.localize_on_locale(locale, message, *args)
24
+ new(locale).i18n.send(message, *args)
25
+ end
26
+ end
27
+ end
data/lib/bovem/logger.rb CHANGED
@@ -10,12 +10,6 @@ module Bovem
10
10
  # @attribute [r] device
11
11
  # @return [IO|String] The file or device to log messages to.
12
12
  class Logger < ::Logger
13
- # @attribute start_time
14
- # @return [Time] The start time of first line. This allows to show a `T+0.1234` information into the log.
15
- class << self
16
- attr_accessor :start_time
17
- end
18
-
19
13
  attr_reader :device
20
14
 
21
15
  # Creates a new logger.
@@ -78,14 +72,14 @@ module Bovem
78
72
  else :white
79
73
  end
80
74
 
81
- header = ::Bovem::Console.replace_markers("{mark=bright-#{color}}[%s T+%0.5f] %s:{/mark}" %[datetime.strftime("%Y/%b/%d %H:%M:%S"), [datetime.to_f - start_time.to_f, 0].max, severity.rjust(5)])
75
+ header = Bovem::Console.replace_markers("{mark=bright-#{color}}[%s T+%0.5f] %s:{/mark}" %[datetime.strftime("%Y/%b/%d %H:%M:%S"), [datetime.to_f - start_time.to_f, 0].max, severity.rjust(5)])
82
76
  "%s %s\n" % [header, msg]
83
77
  }
84
78
  end
85
79
 
86
80
  # The log time of the first logger. This allows to show a `T+0.1234` information into the log.
87
81
  # @return [Time] The log time of the first logger.
88
- def start_time
82
+ def self.start_time
89
83
  @start_time ||= ::Time.now
90
84
  end
91
85
  end
@@ -0,0 +1,250 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the bovem 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 Bovem
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, a Regexp or a Proc which takes one argument and returns a boolean.
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) if !value.nil? && !value.is_a?(Regexp) && !value.is_a?(Proc)
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 || Bovem::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 = get_validator_method(@validator)
152
+ rv = vs ? @validator.send(vs, value) : true
153
+
154
+ if rv then
155
+ @value = value
156
+ @provided = true
157
+ else # Validation failed
158
+ @value = nil
159
+ @provided = false
160
+ handle_set_failure(vs) if raise_error
161
+ false
162
+ end
163
+ end
164
+
165
+ # Executes the action associated to this option.
166
+ def execute_action
167
+ if @action.present? then
168
+ @provided = true
169
+ @action.call(parent, self)
170
+ end
171
+ end
172
+
173
+ # Checks if this option requires an argument.
174
+ #
175
+ # @return [Boolean] `true` if this option requires an argument, `false` otherwise.
176
+ def requires_argument?
177
+ [String, Integer, Float, Array].include?(@type) && @action.blank?
178
+ end
179
+
180
+ # If this option was provided.
181
+ #
182
+ # @return [Boolean] `true` if this option was provided, `false` otherwise.
183
+ def provided?
184
+ @provided
185
+ end
186
+
187
+ # Check if this command has a help.
188
+ #
189
+ # @return [Boolean] `true` if this command has a help, `false` otherwise.
190
+ def has_help?
191
+ @help.present?
192
+ end
193
+
194
+ # Get the current value for this option.
195
+ #
196
+ # @return [Object] The current value of this option.
197
+ def value
198
+ provided? ? @value : default
199
+ end
200
+
201
+ private
202
+ # Setups the forms of the this option.
203
+ #
204
+ # @param forms [Array] An array of short and long forms for this option. Missing forms will be inferred by the name.
205
+ def setup_forms(forms)
206
+ self.short = forms.length > 0 ? forms[0] : @name[0, 1]
207
+ self.long = forms.length == 2 ? forms[1] : @name
208
+ end
209
+
210
+ # Setups the settings of the this option.
211
+ #
212
+ # @param options [Hash] The settings for this option.
213
+ def setup_options(options)
214
+ (options.is_a?(::Hash) ? options : {}).each_pair do |option, value|
215
+ send("#{option}=", value) if respond_to?("#{option}=")
216
+ end
217
+ end
218
+
219
+ # Setups the action of the this option.
220
+ #
221
+ # @param action [Proc] The action of this option.
222
+ def setup_action(action)
223
+ @action = action if action.present? && action.respond_to?(:call) && action.try(:arity) == 2
224
+ end
225
+
226
+ # Handle failure in setting an option.
227
+ #
228
+ # @param vs [Symbol] The type of validator.
229
+ def handle_set_failure(vs)
230
+ if vs == "include?" then
231
+ raise Bovem::Errors::Error.new(self, :validation_failed, @parent.i18n.invalid_value(label, Bovem::Parser.smart_join(@validator)))
232
+ else
233
+ raise Bovem::Errors::Error.new(self, :validation_failed, @parent.i18n.invalid_for_regexp(label, @validator.is_a?(::Proc) ? "[FUNCTION]" : @validator.inspect))
234
+ end
235
+ end
236
+
237
+ # Gets the method required to verify a validator.
238
+ #
239
+ # @param validator [Object] The type of the validator.
240
+ # @return [String] A method to call to verify the validator.
241
+ def get_validator_method(validator)
242
+ case validator.class.to_s
243
+ when "Array" then "include?"
244
+ when "Regexp" then "match"
245
+ when "Proc" then "call"
246
+ else false
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,317 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the bovem 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 Bovem
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 Bovem::Errors::Error.new(command, :ambiguous_command, command.i18n.ambigous_command(arg, Bovem::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
+ Bovem::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 Bovem::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 Bovem::Errors::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 Bovem::Errors::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 Bovem::Errors::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 = Bovem::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 Bovem::Errors::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 = Bovem::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