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