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