rubikon 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.md +13 -10
- data/Rakefile +4 -23
- data/gemspec.yml +17 -0
- data/lib/core_ext/enumerable.rb +27 -0
- data/lib/rubikon.rb +3 -5
- data/lib/rubikon/application/dsl_methods.rb +139 -40
- data/lib/rubikon/application/instance_methods.rb +149 -99
- data/lib/rubikon/application/sandbox.rb +0 -0
- data/lib/rubikon/argument_vector.rb +119 -0
- data/lib/rubikon/command.rb +88 -55
- data/lib/rubikon/config/auto_provider.rb +28 -3
- data/lib/rubikon/config/factory.rb +16 -3
- data/lib/rubikon/config/ini_provider.rb +29 -1
- data/lib/rubikon/config/yaml_provider.rb +16 -1
- data/lib/rubikon/{exceptions.rb → errors.rb} +25 -1
- data/lib/rubikon/has_arguments.rb +140 -40
- data/lib/rubikon/parameter.rb +4 -0
- data/lib/rubikon/version.rb +11 -0
- data/samples/config/global/config.yml +4 -0
- data/samples/config/local/config.yml +4 -0
- data/samples/helloworld/hello_world.rb +14 -10
- data/test/config/0/config.yml +2 -0
- data/test/config/1/config.yml +3 -0
- data/test/config/2/config.yml +3 -0
- data/test/config/test.ini +10 -0
- data/test/{test_helper.rb → helper.rb} +0 -4
- data/test/test_application.rb +37 -35
- data/test/test_argument_vector.rb +156 -0
- data/test/test_command.rb +1 -35
- data/test/test_config.rb +1 -1
- data/test/test_has_arguments.rb +161 -20
- data/test/test_ini_provider.rb +1 -1
- data/test/test_parameter.rb +1 -1
- data/test/test_progress_bar.rb +1 -1
- data/test/test_throbber.rb +1 -1
- data/test/testapps.rb +10 -5
- metadata +73 -69
@@ -1,16 +1,17 @@
|
|
1
1
|
# This code is free software; you can redistribute it and/or modify it under
|
2
2
|
# the terms of the new BSD License.
|
3
3
|
#
|
4
|
-
# Copyright (c) 2009-
|
4
|
+
# Copyright (c) 2009-2011, Sebastian Staudt
|
5
5
|
|
6
6
|
require 'pathname'
|
7
7
|
require 'stringio'
|
8
8
|
|
9
9
|
require 'rubikon/application/sandbox'
|
10
|
+
require 'rubikon/argument_vector'
|
10
11
|
require 'rubikon/colored_io'
|
11
12
|
require 'rubikon/command'
|
12
13
|
require 'rubikon/config/factory'
|
13
|
-
require 'rubikon/
|
14
|
+
require 'rubikon/errors'
|
14
15
|
require 'rubikon/flag'
|
15
16
|
require 'rubikon/option'
|
16
17
|
require 'rubikon/progress_bar'
|
@@ -41,16 +42,17 @@ module Rubikon
|
|
41
42
|
#
|
42
43
|
# @see #set
|
43
44
|
def initialize
|
44
|
-
@commands
|
45
|
-
@current_command
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@global_parameters
|
49
|
-
@hooks
|
50
|
-
@initialized
|
51
|
-
@parameters
|
52
|
-
@sandbox
|
53
|
-
@settings
|
45
|
+
@commands = {}
|
46
|
+
@current_command = nil
|
47
|
+
@current_param = nil
|
48
|
+
@default_config = {}
|
49
|
+
@global_parameters = {}
|
50
|
+
@hooks = {}
|
51
|
+
@initialized = false
|
52
|
+
@parameters = []
|
53
|
+
@sandbox = Sandbox.new(self)
|
54
|
+
@settings = {
|
55
|
+
:autohelp => true,
|
54
56
|
:autorun => true,
|
55
57
|
:colors => true,
|
56
58
|
:config_file => "#{self.class.to_s.downcase}.yml",
|
@@ -60,15 +62,17 @@ module Rubikon
|
|
60
62
|
:raise_errors => false
|
61
63
|
}
|
62
64
|
|
63
|
-
@settings[:config_paths] = []
|
64
65
|
if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
|
65
|
-
|
66
|
+
global_config_path = ENV['ALLUSERSPROFILE']
|
66
67
|
else
|
67
|
-
|
68
|
+
global_config_path = '/etc'
|
68
69
|
end
|
69
|
-
@settings[:config_paths] << File.expand_path('~')
|
70
|
-
@settings[:config_paths] << File.expand_path('.')
|
71
70
|
|
71
|
+
@settings[:config_paths] = [
|
72
|
+
global_config_path, File.expand_path('~'), File.expand_path('.')
|
73
|
+
]
|
74
|
+
|
75
|
+
self.estream = $stderr
|
72
76
|
self.ostream = $stdout
|
73
77
|
end
|
74
78
|
|
@@ -86,32 +90,46 @@ module Rubikon
|
|
86
90
|
|
87
91
|
begin
|
88
92
|
InstanceMethods.instance_method(:init).bind(self).call
|
89
|
-
|
93
|
+
global_params, command, command_params = InstanceMethods.
|
90
94
|
instance_method(:parse_arguments).bind(self).call(args)
|
91
95
|
|
92
|
-
|
93
|
-
@
|
94
|
-
|
95
|
-
parameter.send :active!
|
96
|
-
@current_global_param = nil
|
97
|
-
end
|
96
|
+
@config_factory = Config::Factory.new(@settings[:config_file],
|
97
|
+
@settings[:config_paths], @settings[:config_format])
|
98
|
+
@config = @default_config.merge @config_factory.config
|
98
99
|
|
99
|
-
|
100
|
-
@
|
100
|
+
global_params.each do |param|
|
101
|
+
@current_param = param
|
102
|
+
param.send :active!
|
103
|
+
@current_param = nil
|
104
|
+
end
|
101
105
|
|
102
106
|
@current_command = command
|
103
107
|
hook.call(:pre_execute)
|
104
|
-
|
108
|
+
|
109
|
+
command_params.each do |param|
|
110
|
+
@current_param = param
|
111
|
+
param.send :active!
|
112
|
+
@current_param = nil
|
113
|
+
end
|
114
|
+
|
115
|
+
result = command.send(:run)
|
105
116
|
hook.call(:post_execute)
|
106
117
|
@current_command = nil
|
107
118
|
|
108
119
|
result
|
120
|
+
rescue Interrupt
|
121
|
+
error "\nInterrupted... exiting."
|
109
122
|
rescue
|
110
123
|
raise $! if @settings[:raise_errors]
|
111
124
|
|
112
|
-
|
113
|
-
|
114
|
-
|
125
|
+
if @settings[:autohelp] && @commands.key?(:help) &&
|
126
|
+
$!.is_a?(UnknownCommandError)
|
127
|
+
call :help, $!.command
|
128
|
+
else
|
129
|
+
error "r{Error:}\n #{$!.message}"
|
130
|
+
error " at #{$!.backtrace.join("\n at ")}" if $DEBUG
|
131
|
+
exit 1
|
132
|
+
end
|
115
133
|
ensure
|
116
134
|
InstanceMethods.instance_method(:reset).bind(self).call
|
117
135
|
end
|
@@ -141,7 +159,7 @@ module Rubikon
|
|
141
159
|
# enable debug output.
|
142
160
|
# Using it sets Ruby's global variable <tt>$DEBUG</tt> to +true+.
|
143
161
|
#
|
144
|
-
# @return [Flag]
|
162
|
+
# @return [Flag] The debug flag
|
145
163
|
def debug_flag
|
146
164
|
global_flag :debug do
|
147
165
|
$DEBUG = true
|
@@ -149,6 +167,54 @@ module Rubikon
|
|
149
167
|
global_flag :d => :debug
|
150
168
|
end
|
151
169
|
|
170
|
+
# Sets the error output stream of the application
|
171
|
+
#
|
172
|
+
# If colors are enabled, this checks if the stream supports the
|
173
|
+
# +color_filter+ method and enables the +ColoredIO+ if not.
|
174
|
+
#
|
175
|
+
# @param [IO] estream The output stream to use
|
176
|
+
# @see ColoredIO.add_color_filter
|
177
|
+
# @since 0.6.0
|
178
|
+
def estream=(estream)
|
179
|
+
if !estream.respond_to?(:color_filter)
|
180
|
+
ColoredIO.add_color_filter(estream, @settings[:colors])
|
181
|
+
end
|
182
|
+
@settings[:estream] = estream
|
183
|
+
end
|
184
|
+
|
185
|
+
# Prints a help screen for this application
|
186
|
+
#
|
187
|
+
# @param [String] info A additional information string to be displayed
|
188
|
+
# right after usage information
|
189
|
+
# @since 0.6.0
|
190
|
+
def help(info = nil)
|
191
|
+
help = {}
|
192
|
+
@commands.each_value do |command|
|
193
|
+
help[command.name.to_s] = command.description
|
194
|
+
end
|
195
|
+
help.delete('__default')
|
196
|
+
|
197
|
+
if @commands.key? :__default
|
198
|
+
puts " [command] [args]\n\n"
|
199
|
+
else
|
200
|
+
puts " command [args]\n\n"
|
201
|
+
end
|
202
|
+
|
203
|
+
puts "#{info}\n\n" unless info.nil?
|
204
|
+
|
205
|
+
puts 'Commands:'
|
206
|
+
max_command_length = help.keys.max_by { |a| a.size }.size
|
207
|
+
help.sort_by { |name, description| name }.each do |name, description|
|
208
|
+
puts " #{name.ljust(max_command_length)} #{description}"
|
209
|
+
end
|
210
|
+
|
211
|
+
if @commands.key?(:__default) &&
|
212
|
+
@commands[:__default].description != '<hidden>'
|
213
|
+
put "\nYou can also call this application without a command:"
|
214
|
+
puts @commands[:__default].help(false) + "\n"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
152
218
|
# Defines a command for displaying a help screen
|
153
219
|
#
|
154
220
|
# This takes any defined commands and it's corresponding options and
|
@@ -158,16 +224,12 @@ module Rubikon
|
|
158
224
|
global_parameters = @global_parameters
|
159
225
|
settings = @settings
|
160
226
|
|
161
|
-
command :help,
|
227
|
+
command :help, 'Show help for the application or a single command',
|
228
|
+
:cmd => :optional do
|
162
229
|
put settings[:help_banner]
|
163
230
|
|
164
|
-
help = {}
|
165
|
-
commands.each_value do |command|
|
166
|
-
help[command.name.to_s] = command.description
|
167
|
-
end
|
168
|
-
|
169
231
|
global_params = ''
|
170
|
-
global_parameters.values.uniq.
|
232
|
+
global_parameters.values.uniq.sort_by { |a| a.name.to_s }.each do |param|
|
171
233
|
global_params << ' ['
|
172
234
|
([param.name] + param.aliases).each_with_index do |name, index|
|
173
235
|
name = name.to_s
|
@@ -178,19 +240,19 @@ module Rubikon
|
|
178
240
|
global_params << ' ...' if param.is_a?(Option)
|
179
241
|
global_params << ']'
|
180
242
|
end
|
243
|
+
put global_params
|
181
244
|
|
182
|
-
|
183
|
-
if default_description.nil?
|
184
|
-
puts "#{global_params} command [args]\n\n"
|
185
|
-
else
|
186
|
-
puts "#{global_params} [command] [args]\n\n"
|
187
|
-
puts "Without command: #{default_description}\n\n"
|
188
|
-
end
|
245
|
+
app_help = lambda { |info| @__app__.instance_eval { help(info) } }
|
189
246
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
247
|
+
unless cmd.nil?
|
248
|
+
name = cmd.to_sym
|
249
|
+
if commands.key? name
|
250
|
+
puts commands[name].help
|
251
|
+
else
|
252
|
+
app_help.call("The command \"#{cmd}\" is undefined. The following commands are available:")
|
253
|
+
end
|
254
|
+
else
|
255
|
+
app_help.call(nil)
|
194
256
|
end
|
195
257
|
end
|
196
258
|
end
|
@@ -240,7 +302,8 @@ module Rubikon
|
|
240
302
|
InstanceMethods.instance_method(:help_command).bind(self).call
|
241
303
|
InstanceMethods.instance_method(:verbose_flag).bind(self).call
|
242
304
|
|
243
|
-
if @settings[:help_as_default] &&
|
305
|
+
if @settings[:help_as_default] && @commands.key?(:help) &&
|
306
|
+
!@commands.key?(:__default)
|
244
307
|
default :help
|
245
308
|
end
|
246
309
|
|
@@ -267,7 +330,7 @@ module Rubikon
|
|
267
330
|
# end
|
268
331
|
# @since 0.4.0
|
269
332
|
def method_missing(name, *args, &block)
|
270
|
-
receiver = @current_param || @
|
333
|
+
receiver = @current_param || @current_command
|
271
334
|
if receiver.nil? || (!receiver.respond_to?(name) &&
|
272
335
|
!receiver.public_methods(false).include?(name))
|
273
336
|
super
|
@@ -294,55 +357,40 @@ module Rubikon
|
|
294
357
|
# user. This distinguishes between commands, global flags and command
|
295
358
|
# flags
|
296
359
|
#
|
297
|
-
# @param [
|
298
|
-
# @
|
299
|
-
#
|
300
|
-
#
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
args.dup.each do |arg|
|
326
|
-
if arg.start_with?('--')
|
327
|
-
parameter = @global_parameters[arg[2..-1].to_sym]
|
328
|
-
elsif arg.start_with?('-')
|
329
|
-
parameter = @global_parameters[arg[1..-1].to_sym]
|
360
|
+
# @param [String] argv The command-line arguments
|
361
|
+
# @raise [NoDefaultCommandError] if no command can be found and no
|
362
|
+
# default command exists
|
363
|
+
# @raise [UnknownParameterError] if an unknown parameter is found
|
364
|
+
# @raise [UnknownParameterError] if an unknown command is found
|
365
|
+
# @return [Array<Parameter>] one All global parameters that have been
|
366
|
+
# supplied
|
367
|
+
# @return [Command] two The command to execute, the parameters of this
|
368
|
+
# command that have been supplied
|
369
|
+
# @return [Array<Parameter>] three All parameters of that command that have
|
370
|
+
# been supplied
|
371
|
+
def parse_arguments(argv)
|
372
|
+
argv.extend ArgumentVector
|
373
|
+
|
374
|
+
argv.expand!
|
375
|
+
|
376
|
+
command, command_index = argv.command! @commands
|
377
|
+
raise NoDefaultCommandError if command.nil?
|
378
|
+
|
379
|
+
command_params = argv.params! command.params, command_index
|
380
|
+
global_params = argv.params! @global_parameters
|
381
|
+
|
382
|
+
argv.scoped_args! command
|
383
|
+
|
384
|
+
unless argv.empty?
|
385
|
+
first = argv.first
|
386
|
+
if first.start_with? '-'
|
387
|
+
raise UnknownParameterError.new(first)
|
330
388
|
else
|
331
|
-
|
332
|
-
parameter.args << args.delete(arg)
|
333
|
-
else
|
334
|
-
parameter = nil
|
335
|
-
end
|
336
|
-
next
|
337
|
-
end
|
338
|
-
|
339
|
-
unless parameter.nil?
|
340
|
-
parameters << parameter
|
341
|
-
args.delete(arg)
|
389
|
+
raise UnknownCommandError.new(first)
|
342
390
|
end
|
343
391
|
end
|
344
392
|
|
345
|
-
return
|
393
|
+
return global_params, command, command_params
|
346
394
|
end
|
347
395
|
|
348
396
|
# Resets this application to its initial state
|
@@ -357,8 +405,10 @@ module Rubikon
|
|
357
405
|
# @see Parameter#reset
|
358
406
|
# @since 0.4.0
|
359
407
|
def reset
|
360
|
-
|
361
|
-
|
408
|
+
[estream, ostream].each do |stream|
|
409
|
+
stream.rewind if stream.is_a? StringIO || !stream.stat.chardev?
|
410
|
+
ColoredIO.remove_color_filter(estream)
|
411
|
+
end
|
362
412
|
(@commands.values + @global_parameters.values).uniq.each do |param|
|
363
413
|
param.send :reset
|
364
414
|
end
|
@@ -370,7 +420,7 @@ module Rubikon
|
|
370
420
|
# verbose output.
|
371
421
|
# Using it sets Ruby's global variable <tt>$VERBOSE</tt> to +true+.
|
372
422
|
#
|
373
|
-
# @return [Flag] The
|
423
|
+
# @return [Flag] The verbose Flag object
|
374
424
|
def verbose_flag
|
375
425
|
global_flag :verbose do
|
376
426
|
$VERBOSE = true
|
File without changes
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2011, Sebastian Staudt
|
5
|
+
|
6
|
+
module Rubikon
|
7
|
+
|
8
|
+
# This module will extend the argument array passed to the application,
|
9
|
+
# usually ARGV. It provides functionality to parse Rubikon specific tokens
|
10
|
+
# from the strings contained in the argument list passed to the application.
|
11
|
+
#
|
12
|
+
# @author Sebastian Staudt
|
13
|
+
# @since 0.6.0
|
14
|
+
module ArgumentVector
|
15
|
+
|
16
|
+
# Gets the command to use from the list of arguments passed to the
|
17
|
+
# application. The first argument matching a command name or alias will
|
18
|
+
# cause the corresponding command to be selected.
|
19
|
+
#
|
20
|
+
# The command and all arguments equal to '--' will be removed from the
|
21
|
+
# array.
|
22
|
+
#
|
23
|
+
# @param [Hash<Symbol, Command>] commands A list of available commands
|
24
|
+
# @return [Command] The command found in the argument list
|
25
|
+
# @return [Fixnum] The position of the command in the argument list
|
26
|
+
def command!(commands)
|
27
|
+
command = nil
|
28
|
+
command_index = 0
|
29
|
+
each_with_index do |arg, i|
|
30
|
+
break if arg == '--'
|
31
|
+
|
32
|
+
command = commands[arg.to_sym]
|
33
|
+
unless command.nil?
|
34
|
+
command_index = i
|
35
|
+
delete_at i
|
36
|
+
break
|
37
|
+
end
|
38
|
+
end
|
39
|
+
delete '--'
|
40
|
+
|
41
|
+
command ||= commands[:__default]
|
42
|
+
|
43
|
+
return command, command_index
|
44
|
+
end
|
45
|
+
|
46
|
+
# Turns arguments using a special syntax into arguments that are parseable.
|
47
|
+
#
|
48
|
+
# Single character parameters may be joined together like '-dv'. This
|
49
|
+
# method will split them into separate parameters like '-d -v'.
|
50
|
+
#
|
51
|
+
# Additionally a parameter argument may be attached to the parameter itself
|
52
|
+
# using '=' like '--path=/tmp'. This method will also split them into
|
53
|
+
# '--path /tmp'.
|
54
|
+
def expand!
|
55
|
+
each_with_index do |arg, i|
|
56
|
+
next if !arg.start_with?('-')
|
57
|
+
self[i] = arg.split('=', 2)
|
58
|
+
next if arg.start_with?('--')
|
59
|
+
self[i] = arg[1..-1].split('').uniq.map { |a| '-' + a }
|
60
|
+
end
|
61
|
+
flatten!
|
62
|
+
end
|
63
|
+
|
64
|
+
# Selects active parameters from a list of available parameters
|
65
|
+
#
|
66
|
+
# For every option found in the argument list {#scoped_args!} is called to
|
67
|
+
# find the arguments for that option.
|
68
|
+
#
|
69
|
+
# All parameters found will be removed from the array.
|
70
|
+
#
|
71
|
+
# @param [Hash<Symbol, Parameter>] params A list of available parameters
|
72
|
+
# @param [Fixnum] pos The position of the first argument that should be
|
73
|
+
# checked. All arguments ahead of that position will be skipped.
|
74
|
+
# @return [Array<Parameter>] Parameters called from the given argument list
|
75
|
+
# @see #scoped_args
|
76
|
+
def params!(params, pos = 0)
|
77
|
+
active_params = []
|
78
|
+
to_delete = []
|
79
|
+
each_with_index do |arg, i|
|
80
|
+
next if i < pos || arg.nil? || !arg.start_with?('-')
|
81
|
+
|
82
|
+
param = params[(arg.start_with?('--') ? arg[2..-1] : arg[1..1]).to_sym]
|
83
|
+
unless param.nil?
|
84
|
+
to_delete << i
|
85
|
+
scoped_args! param, i + 1 if param.is_a? Option
|
86
|
+
active_params << param
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
to_delete.reverse.each { |i| delete_at i }
|
91
|
+
|
92
|
+
active_params
|
93
|
+
end
|
94
|
+
|
95
|
+
# Gets all arguments passed to a specific scope, i.e. a command or an
|
96
|
+
# option.
|
97
|
+
#
|
98
|
+
# All arguments in the scope will be removed from the array.
|
99
|
+
#
|
100
|
+
# @param [HasArguments] has_args
|
101
|
+
# @param [Fixnum] pos The position of the first argument that should be
|
102
|
+
# checked. All arguments ahead of that position will be skipped.
|
103
|
+
def scoped_args!(has_args, pos = 0)
|
104
|
+
to_delete = []
|
105
|
+
|
106
|
+
each_with_index do |arg, i|
|
107
|
+
next if i < pos
|
108
|
+
break if arg.start_with?('-') || !has_args.send(:more_args?)
|
109
|
+
|
110
|
+
to_delete << i
|
111
|
+
has_args.send(:<<, arg)
|
112
|
+
end
|
113
|
+
|
114
|
+
to_delete.reverse.each { |i| delete_at i }
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|