rubikon 0.4.1 → 0.5.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/README.md +4 -3
- data/lib/core_ext/object.rb +2 -1
- data/lib/rubikon.rb +1 -1
- data/lib/rubikon/application/class_methods.rb +5 -1
- data/lib/rubikon/application/dsl_methods.rb +39 -15
- data/lib/rubikon/application/instance_methods.rb +75 -28
- data/lib/rubikon/application/sandbox.rb +5 -3
- data/lib/rubikon/colored_io.rb +105 -0
- data/lib/rubikon/command.rb +18 -8
- data/lib/rubikon/config/auto_provider.rb +39 -0
- data/lib/rubikon/config/factory.rb +65 -0
- data/lib/rubikon/config/ini_provider.rb +60 -0
- data/lib/rubikon/config/yaml_provider.rb +30 -0
- data/lib/rubikon/exceptions.rb +12 -0
- data/lib/rubikon/flag.rb +2 -0
- data/lib/rubikon/has_arguments.rb +2 -0
- data/lib/rubikon/parameter.rb +11 -8
- data/lib/rubikon/progress_bar.rb +12 -6
- data/lib/rubikon/throbber.rb +6 -6
- data/samples/config/config_sample.rb +39 -0
- data/samples/helloworld/hello_world.rb +8 -2
- data/test/test_application.rb +6 -6
- data/test/test_command.rb +13 -13
- data/test/test_config.rb +41 -0
- data/test/test_flag.rb +4 -4
- data/test/test_has_arguments.rb +24 -24
- data/test/test_ini_provider.rb +26 -0
- data/test/test_progress_bar.rb +18 -0
- metadata +15 -5
data/README.md
CHANGED
@@ -93,14 +93,15 @@ section if you want to help to make Rubikon better.
|
|
93
93
|
* Automatic checks for option arguments
|
94
94
|
* Built-in methods to capture user input
|
95
95
|
* Built-in methods to display progress bars and throbbers
|
96
|
+
* Built-in support for configuration files
|
97
|
+
* Built-in support for colored output
|
98
|
+
* Automatic generation of a application help screen
|
96
99
|
|
97
100
|
## Future plans
|
98
101
|
|
99
102
|
* User defined type safety of option arguments
|
100
|
-
* Automatic generation of help screens
|
101
103
|
* Improved error handling
|
102
|
-
*
|
103
|
-
* Built-in support for colored output
|
104
|
+
* Automatic generation of command help screens
|
104
105
|
|
105
106
|
## Requirements
|
106
107
|
|
data/lib/core_ext/object.rb
CHANGED
@@ -5,7 +5,8 @@
|
|
5
5
|
|
6
6
|
unless Object.method_defined?(:respond_to_missing?)
|
7
7
|
|
8
|
-
# Extends Ruby's own Object class with method #
|
8
|
+
# Extends Ruby's own Object class with method #respond_to_missing? for Ruby
|
9
|
+
# < 1.9.2
|
9
10
|
#
|
10
11
|
# @author Sebastian Staudt
|
11
12
|
# @since 0.4.0
|
data/lib/rubikon.rb
CHANGED
@@ -34,7 +34,11 @@ module Rubikon
|
|
34
34
|
def inherited(subclass)
|
35
35
|
subclass.class_eval { include Singleton }
|
36
36
|
subclass.send(:base_file=, File.expand_path(caller.first.split(':').first))
|
37
|
-
at_exit
|
37
|
+
at_exit do
|
38
|
+
if subclass.send(:autorun?)
|
39
|
+
InstanceMethods.instance_method(:run).bind(subclass.instance).call
|
40
|
+
end
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
# This is used for convinience. Method calls on the class itself are
|
@@ -19,11 +19,25 @@ module Rubikon
|
|
19
19
|
# @return [String] The (first) definition file of the application
|
20
20
|
attr_reader :base_file
|
21
21
|
|
22
|
+
attr_reader :config
|
23
|
+
|
22
24
|
# @return [String] The absolute path of the application
|
23
25
|
attr_reader :path
|
24
26
|
|
25
27
|
private
|
26
28
|
|
29
|
+
# Call another named command with the given arguments
|
30
|
+
#
|
31
|
+
# @param [Symbol] command_name The name of the command to call
|
32
|
+
# @param [Array<String>] args The arguments to pass to the called command
|
33
|
+
# @see Command#run
|
34
|
+
def call(command_name, *args)
|
35
|
+
command = @current_command
|
36
|
+
@current_command = @commands[command_name]
|
37
|
+
@current_command.send(:run, *args)
|
38
|
+
@current_command = command
|
39
|
+
end
|
40
|
+
|
27
41
|
# Define a new application Command or an alias to an existing one
|
28
42
|
#
|
29
43
|
# @param [String, Hash] name The name of the Command as used in
|
@@ -39,6 +53,8 @@ module Rubikon
|
|
39
53
|
# @return [Command]
|
40
54
|
# @since 0.2.0
|
41
55
|
def command(name, arg_count = nil, description = nil, &block)
|
56
|
+
command = nil
|
57
|
+
|
42
58
|
if name.is_a? Hash
|
43
59
|
name.each do |alias_name, command_name|
|
44
60
|
command = @commands[command_name]
|
@@ -63,7 +79,7 @@ module Rubikon
|
|
63
79
|
|
64
80
|
unless command.nil? || @parameters.empty?
|
65
81
|
@parameters.each do |parameter|
|
66
|
-
command.add_param
|
82
|
+
command.send(:add_param, parameter)
|
67
83
|
end
|
68
84
|
@parameters.clear
|
69
85
|
end
|
@@ -139,7 +155,7 @@ module Rubikon
|
|
139
155
|
parameter = @global_parameters[name]
|
140
156
|
parameter = @current_command.parameters[name] if parameter.nil?
|
141
157
|
return false if parameter.nil?
|
142
|
-
parameter.active?
|
158
|
+
parameter.send(:active?)
|
143
159
|
end
|
144
160
|
alias_method :given?, :active?
|
145
161
|
|
@@ -365,8 +381,8 @@ module Rubikon
|
|
365
381
|
# @param [String] text The text to write into the output stream
|
366
382
|
# @since 0.2.0
|
367
383
|
def put(text)
|
368
|
-
|
369
|
-
|
384
|
+
ostream << text
|
385
|
+
ostream.flush
|
370
386
|
end
|
371
387
|
|
372
388
|
# Output a character using +IO#putc+ of the output stream
|
@@ -375,7 +391,7 @@ module Rubikon
|
|
375
391
|
# stream
|
376
392
|
# @since 0.2.0
|
377
393
|
def putc(char)
|
378
|
-
|
394
|
+
ostream.putc char
|
379
395
|
end
|
380
396
|
|
381
397
|
# Output a line of text using +IO#puts+ of the output stream
|
@@ -383,7 +399,7 @@ module Rubikon
|
|
383
399
|
# @param [String] text The text to write into the output stream
|
384
400
|
# @since 0.2.0
|
385
401
|
def puts(text)
|
386
|
-
|
402
|
+
ostream.puts text
|
387
403
|
end
|
388
404
|
|
389
405
|
# Sets an application setting
|
@@ -393,20 +409,28 @@ module Rubikon
|
|
393
409
|
# @since 0.2.0
|
394
410
|
#
|
395
411
|
# Available settings
|
396
|
-
# +autorun+::
|
397
|
-
#
|
398
|
-
#
|
399
|
-
#
|
400
|
-
# +
|
401
|
-
# +
|
402
|
-
# +
|
403
|
-
# +
|
412
|
+
# +autorun+:: If +true+, let the application run as soon as its
|
413
|
+
# class is defined. This is generally useful for simple
|
414
|
+
# "code and run" applications.
|
415
|
+
# +colors+:: If +true+, enables colored output using ColoredIO
|
416
|
+
# +config_file+:: The name of the config file to search
|
417
|
+
# +config_paths+:: The paths to search for config files
|
418
|
+
# +help_banner+:: Defines a banner for the help message
|
419
|
+
# +istream+:: Defines an input stream to use
|
420
|
+
# +name+:: Defines the name of the application
|
421
|
+
# +ostream+:: Defines an output stream to use
|
422
|
+
# +raise_errors+:: If +true+, raise errors, otherwise fail gracefully
|
404
423
|
#
|
405
424
|
# @example
|
406
425
|
# set :name, 'My App'
|
407
426
|
# set :autorun, false
|
408
427
|
def set(setting, value)
|
409
|
-
|
428
|
+
setting = setting.to_sym
|
429
|
+
unless setting == :ostream
|
430
|
+
@settings[setting.to_sym] = value
|
431
|
+
else
|
432
|
+
self.ostream = value
|
433
|
+
end
|
410
434
|
end
|
411
435
|
|
412
436
|
# Displays a throbber while the given block is executed
|
@@ -7,7 +7,9 @@ require 'pathname'
|
|
7
7
|
require 'stringio'
|
8
8
|
|
9
9
|
require 'rubikon/application/sandbox'
|
10
|
+
require 'rubikon/colored_io'
|
10
11
|
require 'rubikon/command'
|
12
|
+
require 'rubikon/config/factory'
|
11
13
|
require 'rubikon/exceptions'
|
12
14
|
require 'rubikon/flag'
|
13
15
|
require 'rubikon/option'
|
@@ -50,12 +52,24 @@ module Rubikon
|
|
50
52
|
@sandbox = Sandbox.new(self)
|
51
53
|
@settings = {
|
52
54
|
:autorun => true,
|
55
|
+
:colors => true,
|
56
|
+
:config_file => "#{self.class.to_s.downcase}.yml",
|
53
57
|
:help_as_default => true,
|
54
58
|
:istream => $stdin,
|
55
59
|
:name => self.class.to_s,
|
56
|
-
:ostream => $stdout,
|
57
60
|
:raise_errors => false
|
58
61
|
}
|
62
|
+
|
63
|
+
@settings[:config_paths] = []
|
64
|
+
if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
|
65
|
+
@settings[:config_paths] << ENV['ALLUSERSPROFILE']
|
66
|
+
else
|
67
|
+
@settings[:config_paths] << '/etc'
|
68
|
+
end
|
69
|
+
@settings[:config_paths] << File.expand_path('~')
|
70
|
+
@settings[:config_paths] << File.expand_path('.')
|
71
|
+
|
72
|
+
self.ostream = $stdout
|
59
73
|
end
|
60
74
|
|
61
75
|
# Run this application
|
@@ -68,33 +82,38 @@ module Rubikon
|
|
68
82
|
# @param [Array<String>] args The command line arguments that should be
|
69
83
|
# given to the application as options
|
70
84
|
def run(args = ARGV)
|
85
|
+
hook = InstanceMethods.instance_method(:hook).bind(self)
|
86
|
+
|
71
87
|
begin
|
72
|
-
init
|
73
|
-
command, parameters, args =
|
88
|
+
InstanceMethods.instance_method(:init).bind(self).call
|
89
|
+
command, parameters, args = InstanceMethods.
|
90
|
+
instance_method(:parse_arguments).bind(self).call(args)
|
74
91
|
|
75
92
|
parameters.each do |parameter|
|
76
93
|
@current_global_param = parameter
|
77
|
-
if parameter.is_a? Option
|
78
|
-
|
79
|
-
end
|
80
|
-
parameter.active!
|
94
|
+
parameter.send :check_args if parameter.is_a? Option
|
95
|
+
parameter.send :active!
|
81
96
|
@current_global_param = nil
|
82
97
|
end
|
83
98
|
|
99
|
+
@config = Config::Factory.new(@settings[:config_file],
|
100
|
+
@settings[:config_paths], @settings[:config_format]).config
|
101
|
+
|
84
102
|
@current_command = command
|
85
|
-
hook
|
86
|
-
result = command.run
|
87
|
-
hook
|
103
|
+
hook.call(:pre_execute)
|
104
|
+
result = command.send(:run, *args)
|
105
|
+
hook.call(:post_execute)
|
88
106
|
@current_command = nil
|
89
107
|
|
90
|
-
reset
|
91
108
|
result
|
92
109
|
rescue
|
93
110
|
raise $! if @settings[:raise_errors]
|
94
111
|
|
95
|
-
puts "Error
|
112
|
+
puts "r{Error:}\n #{$!.message}"
|
96
113
|
puts " #{$!.backtrace.join("\n ")}" if $DEBUG
|
97
114
|
exit 1
|
115
|
+
ensure
|
116
|
+
InstanceMethods.instance_method(:reset).bind(self).call
|
98
117
|
end
|
99
118
|
end
|
100
119
|
|
@@ -185,13 +204,13 @@ module Rubikon
|
|
185
204
|
# If the block needs to print to the real IO stream, it can access it
|
186
205
|
# using its first parameter.
|
187
206
|
def hidden_output(&block)
|
188
|
-
current_ostream =
|
189
|
-
|
207
|
+
current_ostream = ostream
|
208
|
+
self.ostream = StringIO.new
|
190
209
|
|
191
210
|
block.call(current_ostream)
|
192
211
|
|
193
|
-
current_ostream <<
|
194
|
-
|
212
|
+
current_ostream << ostream.string
|
213
|
+
self.ostream = current_ostream
|
195
214
|
end
|
196
215
|
|
197
216
|
# Executes the hook with the secified name
|
@@ -199,7 +218,7 @@ module Rubikon
|
|
199
218
|
# @param [Symbol] name The name of the hook to execute
|
200
219
|
# @since 0.4.0
|
201
220
|
def hook(name)
|
202
|
-
@sandbox.instance_eval
|
221
|
+
@sandbox.instance_eval(&@hooks[name]) unless @hooks[name].nil?
|
203
222
|
end
|
204
223
|
|
205
224
|
# This method is called once for each application and is used to
|
@@ -209,15 +228,17 @@ module Rubikon
|
|
209
228
|
def init
|
210
229
|
return if @initialized
|
211
230
|
|
231
|
+
hook = InstanceMethods.instance_method(:hook).bind(self)
|
232
|
+
|
212
233
|
@current_command = nil
|
213
234
|
@current_param = nil
|
214
235
|
@current_global_param = nil
|
215
236
|
|
216
|
-
hook
|
237
|
+
hook.call(:pre_init)
|
217
238
|
|
218
|
-
debug_flag
|
219
|
-
help_command
|
220
|
-
verbose_flag
|
239
|
+
InstanceMethods.instance_method(:debug_flag).bind(self).call
|
240
|
+
InstanceMethods.instance_method(:help_command).bind(self).call
|
241
|
+
InstanceMethods.instance_method(:verbose_flag).bind(self).call
|
221
242
|
|
222
243
|
if @settings[:help_as_default] && !@commands.keys.include?(:__default)
|
223
244
|
default :help
|
@@ -225,7 +246,7 @@ module Rubikon
|
|
225
246
|
|
226
247
|
@initialized = true
|
227
248
|
|
228
|
-
hook
|
249
|
+
hook.call(:post_init)
|
229
250
|
end
|
230
251
|
|
231
252
|
# This is used to determine the receiver of a method call inside the
|
@@ -247,13 +268,28 @@ module Rubikon
|
|
247
268
|
# @since 0.4.0
|
248
269
|
def method_missing(name, *args, &block)
|
249
270
|
receiver = @current_param || @current_global_param || @current_command
|
250
|
-
if receiver.nil? || !receiver.respond_to?(name)
|
271
|
+
if receiver.nil? || (!receiver.respond_to?(name) &&
|
272
|
+
!receiver.public_methods(false).include?(name))
|
251
273
|
super
|
252
274
|
else
|
253
275
|
receiver.send(name, *args, &block)
|
254
276
|
end
|
255
277
|
end
|
256
278
|
|
279
|
+
# Sets the output stream of the application
|
280
|
+
#
|
281
|
+
# If colors are enabled, this checks if the stream supports the
|
282
|
+
# +color_filter+ method and enables the +ColoredIO+ if not.
|
283
|
+
#
|
284
|
+
# @param [IO] ostream The output stream to use
|
285
|
+
# @see ColoredIO.add_color_filter
|
286
|
+
def ostream=(ostream)
|
287
|
+
if !ostream.respond_to?(:color_filter)
|
288
|
+
ColoredIO.add_color_filter(ostream, @settings[:colors])
|
289
|
+
end
|
290
|
+
@settings[:ostream] = ostream
|
291
|
+
end
|
292
|
+
|
257
293
|
# Parses the command-line arguments given to the application by the
|
258
294
|
# user. This distinguishes between commands, global flags and command
|
259
295
|
# flags
|
@@ -263,16 +299,20 @@ module Rubikon
|
|
263
299
|
# parameters of this command that have been supplied and any
|
264
300
|
# additional command-line arguments supplied
|
265
301
|
def parse_arguments(args)
|
266
|
-
command_arg = args.
|
267
|
-
if command_arg
|
302
|
+
command_arg = args.find { |arg| arg == '--' || !arg.start_with?('-') }
|
303
|
+
command_arg = nil if command_arg == '--'
|
304
|
+
|
305
|
+
if command_arg.nil?
|
268
306
|
command = @commands[:__default]
|
269
|
-
args.unshift(command_arg) unless command_arg.nil?
|
270
307
|
raise NoDefaultCommandError if command.nil?
|
271
308
|
else
|
272
309
|
command = @commands[command_arg.to_sym]
|
310
|
+
args.delete_at args.find_index(command_arg)
|
273
311
|
raise UnknownCommandError.new(command_arg) if command.nil?
|
274
312
|
end
|
275
313
|
|
314
|
+
args.delete '--'
|
315
|
+
|
276
316
|
parameter = nil
|
277
317
|
parameters = []
|
278
318
|
args.dup.each do |arg|
|
@@ -281,7 +321,7 @@ module Rubikon
|
|
281
321
|
elsif arg.start_with?('-')
|
282
322
|
parameter = @global_parameters[arg[1..-1].to_sym]
|
283
323
|
else
|
284
|
-
if !parameter.nil? && parameter.more_args?
|
324
|
+
if !parameter.nil? && parameter.send(:more_args?)
|
285
325
|
parameter.args << args.delete(arg)
|
286
326
|
else
|
287
327
|
parameter = nil
|
@@ -300,13 +340,20 @@ module Rubikon
|
|
300
340
|
|
301
341
|
# Resets this application to its initial state
|
302
342
|
#
|
343
|
+
# This rewinds the output stream, removes the color features from the and
|
344
|
+
# resets all commands and global parameters.
|
345
|
+
#
|
346
|
+
# @see ColoredIO.remove_color_filter
|
303
347
|
# @see Command#reset
|
304
348
|
# @see HasArguments#reset
|
349
|
+
# @see IO#rewind
|
305
350
|
# @see Parameter#reset
|
306
351
|
# @since 0.4.0
|
307
352
|
def reset
|
353
|
+
ostream.rewind
|
354
|
+
ColoredIO.remove_color_filter(ostream)
|
308
355
|
(@commands.values + @global_parameters.values).uniq.each do |param|
|
309
|
-
param.reset
|
356
|
+
param.send :reset
|
310
357
|
end
|
311
358
|
end
|
312
359
|
|
@@ -35,11 +35,13 @@ module Rubikon
|
|
35
35
|
# InstanceMethods and should therefore be protected
|
36
36
|
# @see InstanceMethods
|
37
37
|
def method_missing(name, *args, &block)
|
38
|
-
if
|
39
|
-
InstanceMethods.
|
38
|
+
if @__app__.class.instance_methods(false).include?(name.to_s) ||
|
39
|
+
!(InstanceMethods.method_defined?(name) ||
|
40
|
+
InstanceMethods.private_method_defined?(name))
|
41
|
+
@__app__.send(name, *args, &block)
|
42
|
+
else
|
40
43
|
raise NoMethodError.new("Method `#{name}' is protected by the application sandbox", name)
|
41
44
|
end
|
42
|
-
@__app__.send(name, *args, &block)
|
43
45
|
end
|
44
46
|
|
45
47
|
# Relay putc to the instance method
|
@@ -0,0 +1,105 @@
|
|
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) 2010, Sebastian Staudt
|
5
|
+
|
6
|
+
module Rubikon
|
7
|
+
|
8
|
+
# This module is used to enhance an IO stream to generate terminal
|
9
|
+
# color codes from simple text tags.
|
10
|
+
#
|
11
|
+
# @author Sebastian Staudt
|
12
|
+
# @since 0.5.0
|
13
|
+
module ColoredIO
|
14
|
+
|
15
|
+
# Color codes that can be used inside a IO enhanced with ColoredIO
|
16
|
+
COLORS = {
|
17
|
+
:black => 30,
|
18
|
+
:bl => 30,
|
19
|
+
:red => 31,
|
20
|
+
:r => 31,
|
21
|
+
:green => 32,
|
22
|
+
:g => 32,
|
23
|
+
:yellow => 33,
|
24
|
+
:y => 33,
|
25
|
+
:blue => 34,
|
26
|
+
:b => 34,
|
27
|
+
:purple => 35,
|
28
|
+
:p => 35,
|
29
|
+
:cyan => 37,
|
30
|
+
:c => 36,
|
31
|
+
:white => 37,
|
32
|
+
:w => 37,
|
33
|
+
}
|
34
|
+
|
35
|
+
# The keys of the color codes joined for the regular expression
|
36
|
+
COLOR_MATCHER = COLORS.keys.join('|')
|
37
|
+
|
38
|
+
# Enables color filtering on the given output stream (or another object
|
39
|
+
# responding to +puts+)
|
40
|
+
#
|
41
|
+
# This wraps the IO's +puts+ method into a call to +color_filter+. The
|
42
|
+
# +color_filter+ method is added dynamically to the singleton class of the
|
43
|
+
# object and does either turn color tags given to the output stream into
|
44
|
+
# their corresponding color code or it simply removes the color tags, if
|
45
|
+
# coloring is disabled.
|
46
|
+
#
|
47
|
+
# @param [IO] io The IO object to add color filtering to
|
48
|
+
# @raise TypeError if the given object does not respond to +puts+
|
49
|
+
# @see .remove_color_filter
|
50
|
+
def self.add_color_filter(io, enabled = true)
|
51
|
+
raise TypeError unless io.respond_to? :puts
|
52
|
+
return if io.respond_to?(:color_filter)
|
53
|
+
|
54
|
+
enabled = enabled && ENV['TERM'] != 'dumb'
|
55
|
+
if enabled && RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
|
56
|
+
begin
|
57
|
+
require 'Win32/Console/ANSI'
|
58
|
+
rescue LoadError
|
59
|
+
enabled = false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class << io
|
64
|
+
const_set :COLORS, COLORS
|
65
|
+
|
66
|
+
def puts(text = '')
|
67
|
+
super color_filter(text.to_s)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if enabled
|
72
|
+
class << io
|
73
|
+
def color_filter(text)
|
74
|
+
text.gsub(/(#{COLOR_MATCHER})\{(.*?)\}/i) do
|
75
|
+
"\e[0;#{COLORS[$1.downcase.to_sym]}m#{$2}\e[0m"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
else
|
80
|
+
class << io
|
81
|
+
def color_filter(text)
|
82
|
+
text.gsub(/(#{COLOR_MATCHER})\{(.*?)\}/i, '\2')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Disables color filtering on the given output stream
|
89
|
+
#
|
90
|
+
# This reverts the actions of +add_color_filter+
|
91
|
+
#
|
92
|
+
# @param [IO] io The IO object to remove color filtering from
|
93
|
+
# @see .add_color_filter
|
94
|
+
def self.remove_color_filter(io)
|
95
|
+
return unless io.respond_to?(:color_filter)
|
96
|
+
class << io
|
97
|
+
remove_const :COLORS
|
98
|
+
remove_method :color_filter
|
99
|
+
remove_method :puts
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|