rubikon 0.5.3 → 0.6.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.
- 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
|