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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/Gemfile +1 -1
- data/README.md +98 -2
- data/bovem.gemspec +3 -3
- data/doc/Bovem.html +25 -6
- data/doc/Bovem/Application.html +3057 -0
- data/doc/Bovem/Command.html +7031 -0
- data/doc/Bovem/CommandMethods.html +125 -0
- data/doc/Bovem/CommandMethods/Children.html +1285 -0
- data/doc/Bovem/CommandMethods/Help.html +209 -0
- data/doc/Bovem/Configuration.html +3 -3
- data/doc/Bovem/Console.html +8 -8
- data/doc/Bovem/ConsoleMethods.html +3 -3
- data/doc/Bovem/ConsoleMethods/Interactions.html +3 -3
- data/doc/Bovem/ConsoleMethods/Interactions/ClassMethods.html +3 -3
- data/doc/Bovem/ConsoleMethods/Logging.html +4 -4
- data/doc/Bovem/ConsoleMethods/Logging/ClassMethods.html +3 -3
- data/doc/Bovem/ConsoleMethods/Output.html +3 -3
- data/doc/Bovem/ConsoleMethods/StyleHandling.html +4 -4
- data/doc/Bovem/ConsoleMethods/StyleHandling/ClassMethods.html +8 -8
- data/doc/Bovem/Errors.html +4 -4
- data/doc/Bovem/Errors/Error.html +631 -0
- data/doc/Bovem/Errors/InvalidConfiguration.html +3 -3
- data/doc/Bovem/Errors/InvalidLogger.html +3 -3
- data/doc/Bovem/Localizer.html +376 -0
- data/doc/Bovem/Logger.html +64 -160
- data/doc/Bovem/Option.html +7009 -0
- data/doc/Bovem/Parser.html +276 -0
- data/doc/Bovem/ParserMethods.html +125 -0
- data/doc/Bovem/ParserMethods/General.html +134 -0
- data/doc/Bovem/ParserMethods/General/ClassMethods.html +574 -0
- data/doc/Bovem/Shell.html +8 -8
- data/doc/Bovem/ShellMethods.html +3 -3
- data/doc/Bovem/ShellMethods/Directories.html +3 -3
- data/doc/Bovem/ShellMethods/Execute.html +3 -3
- data/doc/Bovem/ShellMethods/General.html +3 -3
- data/doc/Bovem/ShellMethods/Read.html +3 -3
- data/doc/Bovem/ShellMethods/Write.html +3 -3
- data/doc/Bovem/Version.html +6 -6
- data/doc/_index.html +119 -11
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +98 -5
- data/doc/frames.html +1 -1
- data/doc/index.html +98 -5
- data/doc/method_list.html +476 -26
- data/doc/top-level-namespace.html +3 -3
- data/lib/bovem.rb +8 -1
- data/lib/bovem/application.rb +158 -0
- data/lib/bovem/command.rb +529 -0
- data/lib/bovem/console.rb +8 -8
- data/lib/bovem/errors.rb +27 -0
- data/lib/bovem/localizer.rb +27 -0
- data/lib/bovem/logger.rb +2 -8
- data/lib/bovem/option.rb +250 -0
- data/lib/bovem/parser.rb +317 -0
- data/lib/bovem/shell.rb +2 -2
- data/lib/bovem/version.rb +3 -3
- data/locales/en.yml +33 -0
- data/locales/it.yml +33 -0
- data/spec/bovem/application_spec.rb +170 -0
- data/spec/bovem/command_spec.rb +526 -0
- data/spec/bovem/configuration_spec.rb +4 -4
- data/spec/bovem/console_spec.rb +22 -22
- data/spec/bovem/errors_spec.rb +18 -0
- data/spec/bovem/logger_spec.rb +22 -12
- data/spec/bovem/option_spec.rb +307 -0
- data/spec/bovem/parser_spec.rb +126 -0
- data/spec/bovem/shell_spec.rb +4 -4
- 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
|
-
|
33
|
-
|
34
|
-
|
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? ? "" :
|
83
|
+
plain || stack.blank? ? "" : Bovem::Console.parse_styles(stack.last)
|
84
84
|
else
|
85
85
|
styles = $3.ensure_string
|
86
|
-
replacement = plain ? "" :
|
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
|
-
|
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((" " * (
|
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 ||=
|
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 =
|
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
|
data/lib/bovem/option.rb
ADDED
@@ -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
|
data/lib/bovem/parser.rb
ADDED
@@ -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
|