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
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -73,13 +73,14 @@ arguments.
|
|
73
73
|
Flags and options are easily added to your application's commands using
|
74
74
|
Rubikon's DSL:
|
75
75
|
|
76
|
-
flag :more
|
77
|
-
option :name,
|
78
|
-
command :hello do
|
76
|
+
flag :more, 'A flag'
|
77
|
+
option :name, 'An option', :who
|
78
|
+
command :hello, 'A command' do
|
79
79
|
puts "Hello #{who}"
|
80
80
|
end
|
81
81
|
|
82
|
-
Please see the
|
82
|
+
Please see the [Wiki][5] for more information on the DSL or the `samples`
|
83
|
+
directory for more in detail sample applications.
|
83
84
|
|
84
85
|
**Warning**:
|
85
86
|
|
@@ -95,13 +96,14 @@ section if you want to help to make Rubikon better.
|
|
95
96
|
* Built-in methods to display progress bars and throbbers
|
96
97
|
* Built-in support for configuration files
|
97
98
|
* Built-in support for colored output
|
98
|
-
* Automatic generation of
|
99
|
+
* Automatic generation of application and command help screens
|
100
|
+
* User defined validation of option arguments
|
99
101
|
|
100
102
|
## Future plans
|
101
103
|
|
102
|
-
* User defined type safety of option arguments
|
103
104
|
* Improved error handling
|
104
|
-
*
|
105
|
+
* General optimizations
|
106
|
+
* Improved tests
|
105
107
|
|
106
108
|
## Requirements
|
107
109
|
|
@@ -149,7 +151,8 @@ LICENSE file.
|
|
149
151
|
|
150
152
|
Follow Rubikon on Twitter [@rubikonrb](http://twitter.com/rubikonrb).
|
151
153
|
|
152
|
-
[1]:
|
153
|
-
[2]:
|
154
|
+
[1]: https://github.com/koraktor/rubikon
|
155
|
+
[2]: https://github.com/koraktor/rubikon/issues
|
154
156
|
[3]: http://koraktor.github.com/rubikon
|
155
|
-
[4]:
|
157
|
+
[4]: https://github.com/koraktor/rubikon/wiki/Compatibility
|
158
|
+
[5]: https://github.com/koraktor/rubikon/wiki
|
data/Rakefile
CHANGED
@@ -19,31 +19,12 @@ Rake::TestTask.new do |t|
|
|
19
19
|
end
|
20
20
|
|
21
21
|
begin
|
22
|
-
|
22
|
+
gem 'ore-tasks', '~> 0.3.0'
|
23
|
+
require 'ore/tasks'
|
23
24
|
|
24
|
-
|
25
|
-
line = File.read('lib/rubikon.rb')[/^\s*VERSION\s*=\s*.*/]
|
26
|
-
gem.version = line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
27
|
-
end
|
28
|
-
|
29
|
-
# Gem specification
|
30
|
-
Jeweler::Tasks.new(gemspec) do |gem|
|
31
|
-
gem.authors = ['Sebastian Staudt']
|
32
|
-
gem.email = 'koraktor@gmail.com'
|
33
|
-
gem.description = 'A simple to use, yet powerful Ruby framework for building console-based applications.'
|
34
|
-
gem.date = Time.now
|
35
|
-
gem.files = %w(.yardopts README.md Rakefile LICENSE) + samples_files + src_files + test_files
|
36
|
-
gem.has_rdoc = false
|
37
|
-
gem.homepage = 'http://koraktor.github.com/rubikon'
|
38
|
-
gem.name = gem.rubyforge_project = 'rubikon'
|
39
|
-
gem.summary = 'Rubikon - A Ruby console app framework'
|
40
|
-
|
41
|
-
gem.add_development_dependency('jeweler')
|
42
|
-
gem.add_development_dependency('shoulda')
|
43
|
-
gem.add_development_dependency('yard')
|
44
|
-
end
|
25
|
+
Ore::Tasks.new
|
45
26
|
rescue LoadError
|
46
|
-
puts
|
27
|
+
puts "Run `gem install ore-tasks` to install 'ore/tasks'."
|
47
28
|
end
|
48
29
|
|
49
30
|
begin
|
data/gemspec.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
name: rubikon
|
2
|
+
summary: Rubikon - A Ruby console app framework
|
3
|
+
description: A simple to use, yet powerful Ruby framework for building
|
4
|
+
console-based applications.
|
5
|
+
|
6
|
+
license: BSD
|
7
|
+
authors: Sebastian Staudt
|
8
|
+
email: koraktor@gmail.com
|
9
|
+
homepage: http://koraktor.github.com/rubikon
|
10
|
+
has_yard: true
|
11
|
+
|
12
|
+
require_paths: lib
|
13
|
+
|
14
|
+
development_dependencies:
|
15
|
+
shoulda: ~> 2.11.3
|
16
|
+
ore-tasks: ~> 0.3.0
|
17
|
+
yard: ~> 0.6.4
|
@@ -0,0 +1,27 @@
|
|
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
|
+
unless Enumerable.method_defined?(:max_by)
|
7
|
+
|
8
|
+
# Extends Ruby's own Enumrable module with method #max_by? for Ruby < 1.8.7
|
9
|
+
#
|
10
|
+
# @author Sebastian Staudt
|
11
|
+
# @since 0.6.0
|
12
|
+
module Enumerable
|
13
|
+
|
14
|
+
# Returns the object in enum that gives the maximum value from the given
|
15
|
+
# block.
|
16
|
+
#
|
17
|
+
# @yield [obj] The block to call on each element in the enum
|
18
|
+
# @yieldparam [Object] obj A single object in the enum
|
19
|
+
# @yieldreturn [Comparable] A value that can be compared (+<=>+) with the
|
20
|
+
# values of the other objects in the enum
|
21
|
+
def max_by(&block)
|
22
|
+
max { |a , b| block.call(a) <=> block.call(b) }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/rubikon.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
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
|
libdir = File.dirname(__FILE__)
|
7
7
|
$:.unshift(libdir) unless $:.include?(libdir)
|
8
8
|
|
9
|
+
require 'core_ext/enumerable'
|
9
10
|
require 'core_ext/object'
|
10
11
|
require 'core_ext/string'
|
11
12
|
require 'rubikon/application/base'
|
13
|
+
require 'rubikon/version'
|
12
14
|
|
13
15
|
# Rubikon is a simple to use, yet powerful Ruby framework for building
|
14
16
|
# console-based applications. Rubikon aims to provide an easy to write and easy
|
@@ -22,8 +24,4 @@ require 'rubikon/application/base'
|
|
22
24
|
# @author Sebastian Staudt
|
23
25
|
# @since 0.1.0
|
24
26
|
module Rubikon
|
25
|
-
|
26
|
-
# This is the current version of the Rubikon gem
|
27
|
-
VERSION = '0.5.3'
|
28
|
-
|
29
27
|
end
|
@@ -1,7 +1,9 @@
|
|
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) 2010, Sebastian Staudt
|
4
|
+
# Copyright (c) 2010-2011, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'rubikon/argument_vector'
|
5
7
|
|
6
8
|
module Rubikon
|
7
9
|
|
@@ -53,12 +55,22 @@ module Rubikon
|
|
53
55
|
#
|
54
56
|
# @param [Symbol] command_name The name of the command to call
|
55
57
|
# @param [Array<String>] args The arguments to pass to the called command
|
58
|
+
# @see ArgumentVector#params!
|
56
59
|
# @see Command#run
|
57
60
|
def call(command_name, *args)
|
58
|
-
|
61
|
+
args.extend ArgumentVector
|
62
|
+
current_command = @current_command
|
63
|
+
current_param = @current_param
|
64
|
+
|
59
65
|
@current_command = @commands[command_name]
|
60
|
-
@current_command.
|
61
|
-
|
66
|
+
args.params!(@current_command.params).each do |param|
|
67
|
+
@current_param = param
|
68
|
+
param.send :active!
|
69
|
+
@current_param = nil
|
70
|
+
end
|
71
|
+
@current_command.send :run
|
72
|
+
@current_command = current_command
|
73
|
+
@current_param = current_param
|
62
74
|
end
|
63
75
|
|
64
76
|
# Define a new application Command or an alias to an existing one
|
@@ -67,8 +79,7 @@ module Rubikon
|
|
67
79
|
# application parameters. This might also be a Hash where every
|
68
80
|
# key will be an alias to the corresponding value, e.g. <tt>{
|
69
81
|
# :alias => :command }</tt>.
|
70
|
-
# @param
|
71
|
-
# the application's help screen
|
82
|
+
# @param options (see HasArguments#initialize)
|
72
83
|
# @param [Proc] block A block that contains the code that should be
|
73
84
|
# executed when this Command is called, i.e. when the application
|
74
85
|
# is called with the associated parameter
|
@@ -77,7 +88,7 @@ module Rubikon
|
|
77
88
|
# @see default
|
78
89
|
# @see Command
|
79
90
|
# @since 0.2.0
|
80
|
-
def command(name,
|
91
|
+
def command(name, *options, &block)
|
81
92
|
command = nil
|
82
93
|
|
83
94
|
if name.is_a? Hash
|
@@ -91,8 +102,7 @@ module Rubikon
|
|
91
102
|
end
|
92
103
|
end
|
93
104
|
else
|
94
|
-
command = Command.new(self, name,
|
95
|
-
command.description = description unless description.nil?
|
105
|
+
command = Command.new(self, name, *options, &block)
|
96
106
|
@commands.each do |command_alias, command_name|
|
97
107
|
if command_name == command.name
|
98
108
|
@commands[command_alias] = command
|
@@ -123,8 +133,7 @@ module Rubikon
|
|
123
133
|
# Define the default Command of the application, i.e. the Command that is
|
124
134
|
# called if no matching Command parameter can be found
|
125
135
|
#
|
126
|
-
# @param
|
127
|
-
# the application's help screen
|
136
|
+
# @param options (see HasArguments#initialize)
|
128
137
|
# @param [Proc] block A block that contains the code that should be
|
129
138
|
# executed when this Command is called, i.e. when no command
|
130
139
|
# parameter is given to the application
|
@@ -133,12 +142,54 @@ module Rubikon
|
|
133
142
|
# @see Command
|
134
143
|
# @see command
|
135
144
|
# @since 0.2.0
|
136
|
-
|
137
|
-
|
138
|
-
|
145
|
+
#
|
146
|
+
# @example Define a default command with an argument
|
147
|
+
# default 'This is the default', :arg do
|
148
|
+
# ...
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# @example Use another command as default
|
152
|
+
# default :other_command
|
153
|
+
def default(*options, &block)
|
154
|
+
if options.size == 1 && options.first.is_a?(Symbol) && !block_given?
|
155
|
+
command :__default => options.first
|
139
156
|
else
|
140
|
-
command
|
157
|
+
command :__default, *options, &block
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Set the default configuration for this application
|
162
|
+
#
|
163
|
+
# @param [Hash] config The default configuration to use
|
164
|
+
# @see #config
|
165
|
+
# @since 0.6.0
|
166
|
+
def default_config=(config)
|
167
|
+
unless config.is_a? Hash
|
168
|
+
raise ArgumentError.new('Configuration has to be a Hash')
|
141
169
|
end
|
170
|
+
|
171
|
+
@default_config = config
|
172
|
+
end
|
173
|
+
|
174
|
+
# Output a line of text using +IO#puts+ of the error output stream
|
175
|
+
#
|
176
|
+
# @param [String] text The text to write into the error output stream
|
177
|
+
# @since 0.6.0
|
178
|
+
def error(text = nil)
|
179
|
+
estream.puts text
|
180
|
+
end
|
181
|
+
|
182
|
+
# Convenience method for accessing the user-defined error output stream
|
183
|
+
#
|
184
|
+
# Use this if you want to work directly with the error output stream
|
185
|
+
#
|
186
|
+
# @return [IO] The error output stream object - usually +$stderr+
|
187
|
+
# @since 0.6.0
|
188
|
+
#
|
189
|
+
# @example
|
190
|
+
# estream.flush
|
191
|
+
def estream
|
192
|
+
@settings[:estream]
|
142
193
|
end
|
143
194
|
|
144
195
|
# Create a new Flag with the given name for the next Command
|
@@ -158,11 +209,13 @@ module Rubikon
|
|
158
209
|
# command :something do
|
159
210
|
# ...
|
160
211
|
# end
|
161
|
-
def flag(name, &block)
|
212
|
+
def flag(name, description = nil, &block)
|
162
213
|
if name.is_a? Hash
|
163
214
|
@parameters << name
|
164
215
|
else
|
165
|
-
|
216
|
+
flag = Flag.new(self, name, &block)
|
217
|
+
flag.description = description unless description.nil?
|
218
|
+
@parameters << flag
|
166
219
|
end
|
167
220
|
end
|
168
221
|
|
@@ -184,7 +237,7 @@ module Rubikon
|
|
184
237
|
# end
|
185
238
|
# @example Define an alias to a global flag
|
186
239
|
# global_flag :q => :quiet
|
187
|
-
def global_flag(name, &block)
|
240
|
+
def global_flag(name, description = nil, &block)
|
188
241
|
if name.is_a? Hash
|
189
242
|
name.each do |alias_name, flag_name|
|
190
243
|
flag = @global_parameters[flag_name]
|
@@ -197,6 +250,7 @@ module Rubikon
|
|
197
250
|
end
|
198
251
|
else
|
199
252
|
flag = Flag.new(self, name, &block)
|
253
|
+
flag.description = description unless description.nil?
|
200
254
|
@global_parameters.each do |flag_alias, flag_name|
|
201
255
|
if flag_name == flag.name
|
202
256
|
@global_parameters[flag_alias] = flag
|
@@ -217,15 +271,15 @@ module Rubikon
|
|
217
271
|
# @see Option
|
218
272
|
# @since 0.2.0
|
219
273
|
#
|
220
|
-
# @example Define a global option
|
221
|
-
# global_option :user,
|
222
|
-
# @example Define a global option with a block to execute
|
223
|
-
# global_option :user,
|
224
|
-
# @user =
|
274
|
+
# @example Define a global option with an optional argument
|
275
|
+
# global_option :user, :login => :optional
|
276
|
+
# @example Define a global option with an argument and a block to execute
|
277
|
+
# global_option :user, :login do
|
278
|
+
# @user = login
|
225
279
|
# end
|
226
280
|
# @example Define an alias to a global option
|
227
281
|
# global_option :u => :user
|
228
|
-
def global_option(name,
|
282
|
+
def global_option(name, *options, &block)
|
229
283
|
if name.is_a? Hash
|
230
284
|
name.each do |alias_name, option_name|
|
231
285
|
option = @global_parameters[option_name]
|
@@ -237,7 +291,7 @@ module Rubikon
|
|
237
291
|
end
|
238
292
|
end
|
239
293
|
else
|
240
|
-
option = Option.new(self, name,
|
294
|
+
option = Option.new(self, name, *options, &block)
|
241
295
|
@global_parameters.each do |option_alias, option_name|
|
242
296
|
if option_name == option.name
|
243
297
|
@global_parameters[option_alias] = option
|
@@ -252,6 +306,9 @@ module Rubikon
|
|
252
306
|
#
|
253
307
|
# @param [String, #to_s] prompt A String or other Object responding to
|
254
308
|
# +to_s+ used for displaying a prompt to the user
|
309
|
+
# @param [Array<String>] expected A list of strings that are accepted as
|
310
|
+
# valid input. If not empty, input will be checked and the prompt
|
311
|
+
# will be repeated if required.
|
255
312
|
# @since 0.2.0
|
256
313
|
#
|
257
314
|
# @example Display a prompt "Please type something: "
|
@@ -261,11 +318,20 @@ module Rubikon
|
|
261
318
|
# # Do something with the data
|
262
319
|
# ...
|
263
320
|
# end
|
264
|
-
|
265
|
-
|
266
|
-
|
321
|
+
#
|
322
|
+
# @example Display a question with validated input
|
323
|
+
# command :question do
|
324
|
+
# good = input 'Do you feel good', 'y', 'n'
|
325
|
+
# ...
|
326
|
+
# end
|
327
|
+
def input(prompt = '', *expected)
|
328
|
+
prompt << " [#{expected.join '/'}]" unless expected.empty?
|
329
|
+
ostream << "#{prompt}: " unless prompt.to_s.empty?
|
330
|
+
input = @settings[:istream].gets[0..-2]
|
331
|
+
unless expected.empty? || expected.include?(input)
|
332
|
+
input = input 'Please provide valid input', *expected
|
267
333
|
end
|
268
|
-
|
334
|
+
input
|
269
335
|
end
|
270
336
|
|
271
337
|
# Create a new Option with the given name for the next Command
|
@@ -275,25 +341,24 @@ module Rubikon
|
|
275
341
|
# options, +--+ for other options). This might also be a Hash
|
276
342
|
# where every key will be an alias to the corresponding value,
|
277
343
|
# e.g. <tt>{ :alias => :option }</tt>.
|
278
|
-
# @param
|
279
|
-
# Use +0+ for no required arguments or a negative value for an
|
280
|
-
# arbitrary number of arguments
|
344
|
+
# @param options (see HasArguments#initialize)
|
281
345
|
# @param [Proc] block An optional code block that should be executed if
|
282
346
|
# this option is used
|
283
347
|
# @see Option
|
284
348
|
# @since 0.2.0
|
285
349
|
#
|
286
|
-
# @example
|
287
|
-
# option :message,
|
350
|
+
# @example Define an option (and its alias) to a command
|
351
|
+
# option :message, 'A message', :text
|
288
352
|
# option :m => :message
|
289
353
|
# command :something do
|
290
|
-
#
|
354
|
+
# puts message.text
|
291
355
|
# end
|
292
|
-
def option(name,
|
356
|
+
def option(name, *options, &block)
|
293
357
|
if name.is_a? Hash
|
294
358
|
@parameters << name
|
295
359
|
else
|
296
|
-
|
360
|
+
option = Option.new(self, name.to_s, *options, &block)
|
361
|
+
@parameters << option
|
297
362
|
end
|
298
363
|
end
|
299
364
|
|
@@ -423,6 +488,7 @@ module Rubikon
|
|
423
488
|
# +colors+:: If +true+, enables colored output using ColoredIO
|
424
489
|
# +config_file+:: The name of the config file to search
|
425
490
|
# +config_paths+:: The paths to search for config files
|
491
|
+
# +estream+:: Defines an error output stream to use
|
426
492
|
# +help_banner+:: Defines a banner for the help message
|
427
493
|
# +istream+:: Defines an input stream to use
|
428
494
|
# +name+:: Defines the name of the application
|
@@ -434,13 +500,46 @@ module Rubikon
|
|
434
500
|
# set :autorun, false
|
435
501
|
def set(setting, value)
|
436
502
|
setting = setting.to_sym
|
437
|
-
|
438
|
-
|
439
|
-
|
503
|
+
if setting == :estream
|
504
|
+
self.estream = value
|
505
|
+
elsif setting == :ostream
|
440
506
|
self.ostream = value
|
507
|
+
else
|
508
|
+
@settings[setting.to_sym] = value
|
441
509
|
end
|
442
510
|
end
|
443
511
|
|
512
|
+
# Saves the current configuration into a configuration file
|
513
|
+
#
|
514
|
+
# The file name and format are specified in the application settings.
|
515
|
+
#
|
516
|
+
# @param [:global, :local, :user, String] Either one of the default
|
517
|
+
# scopes or a specific path. The scopes map to a special path. On
|
518
|
+
# UNIX systems this is +/etc+ for global configurations, the
|
519
|
+
# user's home directory (+~+) for user configurations and the
|
520
|
+
# current working directory (+.+) for local configurations.
|
521
|
+
# @see #default_config=
|
522
|
+
# @since 0.6.0
|
523
|
+
def save_config(scope = :user)
|
524
|
+
case scope
|
525
|
+
when :global
|
526
|
+
if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
|
527
|
+
path = ENV['ALLUSERSPROFILE']
|
528
|
+
else
|
529
|
+
path = '/etc'
|
530
|
+
end
|
531
|
+
when :local
|
532
|
+
path = File.expand_path '.'
|
533
|
+
when :user
|
534
|
+
path = File.expand_path '~'
|
535
|
+
else
|
536
|
+
path = scope
|
537
|
+
end
|
538
|
+
|
539
|
+
@config_factory.save_config @config,
|
540
|
+
File.join(path, @settings[:config_file])
|
541
|
+
end
|
542
|
+
|
444
543
|
# Displays a throbber while the given block is executed
|
445
544
|
#
|
446
545
|
# @param [Proc] block The block to execute while the throbber is
|