rubikon 0.1.0 → 0.2.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 +0 -0
- data/README.md +2 -2
- data/Rakefile +2 -2
- data/VERSION.yml +3 -2
- data/lib/rubikon/action.rb +13 -10
- data/lib/rubikon/application/base.rb +36 -0
- data/lib/rubikon/application/class_methods.rb +67 -0
- data/lib/rubikon/application/instance_methods.rb +345 -0
- data/lib/rubikon/exceptions.rb +0 -0
- data/lib/rubikon/progress_bar.rb +69 -0
- data/lib/rubikon/setter.rb +17 -0
- data/lib/rubikon/throbber.rb +36 -0
- data/lib/rubikon.rb +1 -1
- data/test/action_tests.rb +36 -0
- data/test/{test.rb → application_tests.rb} +9 -43
- data/test/progress_bar_tests.rb +95 -0
- data/test/test_helper.rb +16 -0
- data/test/testapp.rb +1 -1
- data/test/throbber_tests.rb +23 -0
- metadata +18 -5
- data/lib/rubikon/application.rb +0 -331
data/LICENSE
CHANGED
File without changes
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Rubikon
|
|
4
4
|
Rubikon is a simple to use, yet powerful Ruby framework for building
|
5
5
|
console-based applications.
|
6
6
|
|
7
|
-
|
7
|
+
## Installation
|
8
8
|
|
9
9
|
You can install Rubikon using RubyGem. This is the easiest way of installing
|
10
10
|
and recommended for most users.
|
@@ -30,7 +30,7 @@ Creating a Rubikon application is as simple as creating a Ruby class:
|
|
30
30
|
If you save this code in a file called `myapp.rb` you can run it using
|
31
31
|
`ruby myapp.rb`. Or you could even add a *shebang* (`#!/usr/bin/env ruby`) to
|
32
32
|
the top of the file and make it executable. You would then be able to run it
|
33
|
-
even more
|
33
|
+
even more easily by typing `./myapp.rb`.
|
34
34
|
|
35
35
|
Now go on and define what your application should do when the user runs it.
|
36
36
|
This is done using `default`:
|
data/Rakefile
CHANGED
data/VERSION.yml
CHANGED
data/lib/rubikon/action.rb
CHANGED
@@ -9,8 +9,6 @@ module Rubikon
|
|
9
9
|
# be executed when running the application.
|
10
10
|
class Action
|
11
11
|
|
12
|
-
@@action_count = 0
|
13
|
-
|
14
12
|
attr_reader :block, :description, :name, :param_type
|
15
13
|
|
16
14
|
# Create a new Action using the given name, options and code block.
|
@@ -24,38 +22,43 @@ module Rubikon
|
|
24
22
|
# moment.
|
25
23
|
# +param_type+:: A single Class or a Array of classes that represent the
|
26
24
|
# type(s) of argument(s) this action expects
|
27
|
-
def initialize(
|
25
|
+
def initialize(options = {}, &block)
|
28
26
|
raise BlockMissingError unless block_given?
|
29
27
|
|
30
|
-
@name = name
|
31
|
-
|
32
28
|
@description = options[:description] || ''
|
33
29
|
@param_type = options[:param_type] || Object
|
34
30
|
|
35
31
|
@block = block
|
32
|
+
@arg_count = block.arity
|
36
33
|
end
|
37
34
|
|
38
35
|
# Run this action's code block
|
39
36
|
#
|
40
37
|
# +args+:: The argument which should be relayed to the block of this Action
|
41
38
|
def run(*args)
|
42
|
-
|
43
|
-
raise MissingArgumentError
|
44
|
-
end
|
39
|
+
raise MissingArgumentError unless check_argument_count(args.size)
|
45
40
|
raise Rubikon::ArgumentTypeError unless check_argument_types(args)
|
46
41
|
@block[*args]
|
47
42
|
end
|
48
43
|
|
49
44
|
private
|
50
45
|
|
46
|
+
# Checks if the number of arguments given fits the number of arguments of
|
47
|
+
# this Action
|
48
|
+
#
|
49
|
+
# +count+:: The number of arguments
|
50
|
+
def check_argument_count(count)
|
51
|
+
!((@arg_count >= 0 && count < @arg_count) || (@arg_count < 0 && count < -@arg_count - 1))
|
52
|
+
end
|
53
|
+
|
51
54
|
# Checks the types of the given arguments using the Class or Array of
|
52
55
|
# classes given in the +:param_type+ option of this action.
|
53
56
|
#
|
54
57
|
# +args+:: The arguments which should be checked
|
55
58
|
def check_argument_types(args)
|
56
59
|
if @param_type.is_a? Array
|
57
|
-
args.each_index do |
|
58
|
-
return false unless args[
|
60
|
+
args.each_index do |arg_index|
|
61
|
+
return false unless args[arg_index].is_a? @param_type[arg_index]
|
59
62
|
end
|
60
63
|
else
|
61
64
|
args.each do |arg|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'singleton'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
require 'rubikon/action'
|
10
|
+
require 'rubikon/application/class_methods'
|
11
|
+
require 'rubikon/application/instance_methods'
|
12
|
+
require 'rubikon/exceptions'
|
13
|
+
|
14
|
+
module Rubikon
|
15
|
+
|
16
|
+
version = YAML.load_file(File.join(File.dirname(__FILE__), '..', '..', '..', 'VERSION.yml'))
|
17
|
+
VERSION = "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
|
18
|
+
|
19
|
+
module Application
|
20
|
+
|
21
|
+
# The main class of Rubikon. Let your own application class inherit from this
|
22
|
+
# one.
|
23
|
+
class Base
|
24
|
+
|
25
|
+
class << self
|
26
|
+
include Rubikon::Application::ClassMethods
|
27
|
+
end
|
28
|
+
|
29
|
+
include Rubikon::Application::InstanceMethods
|
30
|
+
include Singleton
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
module Rubikon
|
7
|
+
|
8
|
+
module Application
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# Returns whether this application should be ran automatically
|
15
|
+
def autorun?
|
16
|
+
instance.instance_variable_get(:@settings)[:autorun] || false
|
17
|
+
end
|
18
|
+
|
19
|
+
# Enables autorun functionality using <tt>Kernel#at_exit</tt>
|
20
|
+
#
|
21
|
+
# +subclass+:: The subclass inheriting from Application. This is the user's
|
22
|
+
# application.
|
23
|
+
#
|
24
|
+
# <em>This is called automatically when subclassing Application.</em>
|
25
|
+
def inherited(subclass)
|
26
|
+
super
|
27
|
+
Singleton.__init__(subclass)
|
28
|
+
at_exit { subclass.run if subclass.send(:autorun?) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# This is used for convinience. Method calls on the class itself are
|
32
|
+
# relayed to the singleton instance.
|
33
|
+
#
|
34
|
+
# +method_name+:: The name of the method being called
|
35
|
+
# +args+:: Any arguments that are given to the method
|
36
|
+
# +block+:: A block that may be given to the method
|
37
|
+
#
|
38
|
+
# <em>This is called automatically when calling methods on the class.</em>
|
39
|
+
def method_missing(method_name, *args, &block)
|
40
|
+
instance.send(method_name, *args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Relay putc to the instance method
|
44
|
+
#
|
45
|
+
# This is used to hide <tt>Kernel#putc</tt> so that the Application's
|
46
|
+
# output IO object is used for printing text
|
47
|
+
#
|
48
|
+
# +text+:: The text to write into the output stream
|
49
|
+
def putc(text)
|
50
|
+
instance.putc text
|
51
|
+
end
|
52
|
+
|
53
|
+
# Relay puts to the instance method
|
54
|
+
#
|
55
|
+
# This is used to hide <tt>Kernel#puts</tt> so that the Application's
|
56
|
+
# output IO object is used for printing text
|
57
|
+
#
|
58
|
+
# +text+:: The text to write into the output stream
|
59
|
+
def puts(text)
|
60
|
+
instance.puts text
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'rubikon/progress_bar'
|
7
|
+
require 'rubikon/throbber'
|
8
|
+
|
9
|
+
module Rubikon
|
10
|
+
|
11
|
+
module Application
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
|
15
|
+
# Initialize with default settings (see set for more detail)
|
16
|
+
#
|
17
|
+
# If you really need to override this in your application class, be sure to
|
18
|
+
# call +super+
|
19
|
+
def initialize
|
20
|
+
@actions = {}
|
21
|
+
@aliases = {}
|
22
|
+
@default = nil
|
23
|
+
@initialized = false
|
24
|
+
@settings = {
|
25
|
+
:autorun => true,
|
26
|
+
:auto_shortopts => true,
|
27
|
+
:dashed_options => true,
|
28
|
+
:help_banner => "Usage: #{$0}",
|
29
|
+
:istream => $stdin,
|
30
|
+
:name => self.class.to_s,
|
31
|
+
:ostream => $stdout,
|
32
|
+
:raise_errors => false
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Define an Application Action
|
37
|
+
#
|
38
|
+
# +name+:: The name of the action. Used as an option parameter.
|
39
|
+
# +options+:: A Hash of options to be used on the created Action
|
40
|
+
# (default: <tt>{}</tt>)
|
41
|
+
# +block+:: A block containing the code that should be executed when this
|
42
|
+
# Action is called, i.e. when the Application is called with
|
43
|
+
# the associated option parameter
|
44
|
+
def action(name, options = {}, &block)
|
45
|
+
raise "No block given" unless block_given?
|
46
|
+
|
47
|
+
action = Action.new(options, &block)
|
48
|
+
|
49
|
+
key = name.to_s
|
50
|
+
if @settings[:dashed_options]
|
51
|
+
if @settings[:auto_shortopts]
|
52
|
+
short_key = "-#{key[0..0]}"
|
53
|
+
@actions[short_key.to_sym] = action unless @actions.key? short_key
|
54
|
+
end
|
55
|
+
key = "--#{key}"
|
56
|
+
end
|
57
|
+
|
58
|
+
@actions[key.to_sym] = action
|
59
|
+
end
|
60
|
+
|
61
|
+
# Define an alias to an Action
|
62
|
+
#
|
63
|
+
# +name+:: The name of the alias
|
64
|
+
# +action+:: The name of the Action that should be aliased
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# action_alias :doit, :dosomething
|
69
|
+
def action_alias(name, action)
|
70
|
+
@aliases[name.to_sym] = action.to_sym
|
71
|
+
end
|
72
|
+
|
73
|
+
# Define the default Action of the Application
|
74
|
+
#
|
75
|
+
# +options+:: A Hash of options to be used on the created Action
|
76
|
+
# (default: <tt>{}</tt>)
|
77
|
+
# +block+:: A block containing the code that should be executed when this
|
78
|
+
# Action is called, i.e. when no option is given to the
|
79
|
+
# Application
|
80
|
+
def default(options = {}, &block)
|
81
|
+
@default = Action.new(options, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Prompts the user for input
|
85
|
+
#
|
86
|
+
# If +prompt+ is not empty this will display a prompt using
|
87
|
+
# <tt>prompt.to_s</tt>.
|
88
|
+
#
|
89
|
+
# +prompt+:: A String or other Object responding to +to_s+ used for
|
90
|
+
# displaying a prompt to the user (default: <tt>''</tt>)
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
#
|
94
|
+
# action 'interactive' do
|
95
|
+
# # Display a prompt "Please type something: "
|
96
|
+
# user_provided_value = input 'Please type something'
|
97
|
+
#
|
98
|
+
# # Do something with the data
|
99
|
+
# ...
|
100
|
+
# end
|
101
|
+
def input(prompt = '')
|
102
|
+
unless prompt.to_s.empty?
|
103
|
+
ostream << "#{prompt}: "
|
104
|
+
end
|
105
|
+
@settings[:istream].gets[0..-2]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Convenience method for accessing the user-defined output stream
|
109
|
+
#
|
110
|
+
# Use this if you want to work directly with the output stream
|
111
|
+
#
|
112
|
+
# Example:
|
113
|
+
#
|
114
|
+
# ostream.flush
|
115
|
+
def ostream
|
116
|
+
@settings[:ostream]
|
117
|
+
end
|
118
|
+
|
119
|
+
# Displays a progress bar while the given block is executed
|
120
|
+
#
|
121
|
+
# Inside the block you have access to a instance of ProgressBar. So you
|
122
|
+
# can update the progress using <tt>ProgressBar#+</tt>.
|
123
|
+
#
|
124
|
+
# +options+:: A Hash of options that should be passed to the ProgressBar
|
125
|
+
# object. For available options see ProgressBar
|
126
|
+
# +block+:: The block to execute
|
127
|
+
#
|
128
|
+
# Example:
|
129
|
+
#
|
130
|
+
# progress_bar(:maximum => 5) do |progress|
|
131
|
+
# 5.times do |file|
|
132
|
+
# File.read("any#{file}.txt")
|
133
|
+
# progress.+
|
134
|
+
# end
|
135
|
+
# end
|
136
|
+
def progress_bar(*options, &block)
|
137
|
+
hidden_output do |ostream|
|
138
|
+
options = options[0]
|
139
|
+
options[:ostream] = ostream
|
140
|
+
|
141
|
+
progress = ProgressBar.new(options)
|
142
|
+
|
143
|
+
block.call(progress)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Output text using +IO#<<+ of the output stream
|
148
|
+
#
|
149
|
+
# +text+:: The text to write into the output stream
|
150
|
+
def put(text)
|
151
|
+
@settings[:ostream] << text
|
152
|
+
@settings[:ostream].flush
|
153
|
+
end
|
154
|
+
|
155
|
+
# Output a character using +IO#putc+ of the output stream
|
156
|
+
#
|
157
|
+
# +char+:: The character to write into the output stream
|
158
|
+
def putc(char)
|
159
|
+
@settings[:ostream].putc char
|
160
|
+
end
|
161
|
+
|
162
|
+
# Output a line of text using +IO#puts+ of the output stream
|
163
|
+
#
|
164
|
+
# +text+:: The text to write into the output stream
|
165
|
+
def puts(text)
|
166
|
+
@settings[:ostream].puts text
|
167
|
+
end
|
168
|
+
|
169
|
+
# Run this application
|
170
|
+
#
|
171
|
+
# +args+:: The command line arguments that should be given to the
|
172
|
+
# application as options
|
173
|
+
#
|
174
|
+
# Calling this method explicitly is not required when you want to create a
|
175
|
+
# simple application (having one main class inheriting from
|
176
|
+
# Rubikon::Application). But it's useful for testing or if you want to have
|
177
|
+
# some sort of sub-applications.
|
178
|
+
def run(args = ARGV)
|
179
|
+
init unless @initialized
|
180
|
+
action_results = []
|
181
|
+
|
182
|
+
begin
|
183
|
+
if !@default.nil? and args.empty?
|
184
|
+
action_results << @default.run
|
185
|
+
else
|
186
|
+
parse_options(args).each do |action, args|
|
187
|
+
action_results << @actions[action].run(*args)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
rescue
|
191
|
+
raise $! if @settings[:raise_errors]
|
192
|
+
|
193
|
+
puts "Error:\n #{$!.message}"
|
194
|
+
puts " #{$!.backtrace.join("\n ")}" if $DEBUG
|
195
|
+
exit 1
|
196
|
+
end
|
197
|
+
|
198
|
+
action_results
|
199
|
+
end
|
200
|
+
|
201
|
+
# Sets an application setting
|
202
|
+
#
|
203
|
+
# +setting+:: The name of the setting to change, will be symbolized first.
|
204
|
+
# +value+:: The value the setting should be changed to
|
205
|
+
#
|
206
|
+
# Available settings
|
207
|
+
# +autorun+:: If true, let the application run as soon as its class
|
208
|
+
# is defined
|
209
|
+
# +dashed_options+:: If true, each option is prepended with a double-dash
|
210
|
+
# (<tt>-</tt><tt>-</tt>)
|
211
|
+
# +help_banner+:: Defines a banner for the help message (<em>unused</em>)
|
212
|
+
# +istream+:: Defines an input stream to use
|
213
|
+
# +name+:: Defines the name of the application
|
214
|
+
# +ostream+:: Defines an output stream to use
|
215
|
+
# +raise_errors+:: If true, raise errors, otherwise fail gracefully
|
216
|
+
#
|
217
|
+
# Example:
|
218
|
+
#
|
219
|
+
# set :name, 'My App'
|
220
|
+
# set :autorun, false
|
221
|
+
def set(setting, value)
|
222
|
+
@settings[setting.to_sym] = value
|
223
|
+
end
|
224
|
+
|
225
|
+
# Displays a throbber while the given block is executed
|
226
|
+
#
|
227
|
+
# Example:
|
228
|
+
#
|
229
|
+
# action 'slow' do
|
230
|
+
# throbber do
|
231
|
+
# # Add some long running code here
|
232
|
+
# ...
|
233
|
+
# end
|
234
|
+
# end
|
235
|
+
def throbber(&block)
|
236
|
+
hidden_output do |ostream|
|
237
|
+
code_thread = Thread.new { block.call }
|
238
|
+
throbber_thread = Throbber.new(ostream, code_thread)
|
239
|
+
|
240
|
+
code_thread.join
|
241
|
+
throbber_thread.join
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
# Assigns aliases to the actions that have been defined using action_alias
|
248
|
+
#
|
249
|
+
# Clears the aliases Hash afterwards
|
250
|
+
def assign_aliases
|
251
|
+
@aliases.each do |key, action|
|
252
|
+
if @settings[:dashed_options]
|
253
|
+
action = "--#{action}".to_sym
|
254
|
+
key = "--#{key}".to_sym
|
255
|
+
end
|
256
|
+
|
257
|
+
unless @actions.key? key
|
258
|
+
@actions[key] = @actions[action]
|
259
|
+
else
|
260
|
+
warn "There's already an action called \"#{key}\"."
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Defines an action for displaying a help screen
|
266
|
+
#
|
267
|
+
# This takes any defined action and it's corresponding options and
|
268
|
+
# descriptions and displays them in a user-friendly manner.
|
269
|
+
def help_action
|
270
|
+
action 'help', { :description => 'Display this help screen' } do
|
271
|
+
help = {}
|
272
|
+
@actions.each do |option, action|
|
273
|
+
help[action] = [] if help[action].nil?
|
274
|
+
help[action] << option.to_s
|
275
|
+
end
|
276
|
+
|
277
|
+
put @settings[:help_banner]
|
278
|
+
puts " [options]" unless @default.nil?
|
279
|
+
puts ''
|
280
|
+
|
281
|
+
help.each do |action, options|
|
282
|
+
help[action] = options.sort.join(', ')
|
283
|
+
end
|
284
|
+
max_options_length = help.values.max { |a,b| a.size <=> b.size }.size
|
285
|
+
help.sort_by { |action, options| options }.each do |action, options|
|
286
|
+
puts options.ljust(max_options_length) << " " << action.description
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Hide output inside the given block and print it after the block has
|
292
|
+
# finished
|
293
|
+
#
|
294
|
+
# +block+:: The block that should not print output while it's running
|
295
|
+
#
|
296
|
+
# If the block needs to print to the real IO stream, it can access it
|
297
|
+
# using its first parameter.
|
298
|
+
def hidden_output(&block)
|
299
|
+
current_ostream = @settings[:ostream]
|
300
|
+
@settings[:ostream] = StringIO.new
|
301
|
+
|
302
|
+
block.call(current_ostream)
|
303
|
+
|
304
|
+
current_ostream << @settings[:ostream].string
|
305
|
+
@settings[:ostream] = current_ostream
|
306
|
+
end
|
307
|
+
|
308
|
+
# This method is called once for each application and is used to
|
309
|
+
# initialize anything that needs to be ready before the application is
|
310
|
+
# run, but <em>after</em> the application is setup, i.e. after the user
|
311
|
+
# has defined the application class.
|
312
|
+
def init
|
313
|
+
help_action
|
314
|
+
assign_aliases
|
315
|
+
@initialized = true
|
316
|
+
end
|
317
|
+
|
318
|
+
# Parses the options used when starting the application
|
319
|
+
#
|
320
|
+
# +options+:: An Array of Strings that should be used as application
|
321
|
+
# options. Usually +ARGV+ is used for this.
|
322
|
+
def parse_options(options)
|
323
|
+
actions_to_call = {}
|
324
|
+
last_action = nil
|
325
|
+
|
326
|
+
options.each do |option|
|
327
|
+
option_sym = option.to_s.to_sym
|
328
|
+
if @actions.keys.include? option_sym
|
329
|
+
actions_to_call[option_sym] = []
|
330
|
+
last_action = option_sym
|
331
|
+
elsif last_action.nil? || (option.is_a?(String) && @settings[:dashed_options] && option[0..1] == '--')
|
332
|
+
raise UnknownOptionError.new(option)
|
333
|
+
else
|
334
|
+
actions_to_call[last_action] << option
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
actions_to_call
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
data/lib/rubikon/exceptions.rb
CHANGED
File without changes
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
module Rubikon
|
7
|
+
|
8
|
+
# A class for displaying and managing progress bars
|
9
|
+
class ProgressBar
|
10
|
+
|
11
|
+
# Create a new ProgressBar using the given options.
|
12
|
+
#
|
13
|
+
# +ostream+:: The output stream where the progress bar should be displayed
|
14
|
+
# +options+:: An Hash of options to customize the progress bar
|
15
|
+
#
|
16
|
+
# Options:
|
17
|
+
#
|
18
|
+
# +char+:: The character used for progress bar display (default: +#+)
|
19
|
+
# +maximum+:: The maximum value of this progress bar (default: +100+)
|
20
|
+
# +size+:: The actual size of the progress bar (default: +20+)
|
21
|
+
# +start+:: The start value of the progress bar (default: +0+)
|
22
|
+
def initialize(options = {})
|
23
|
+
if options.is_a? Numeric
|
24
|
+
@maximum = options
|
25
|
+
options = {}
|
26
|
+
else
|
27
|
+
@maximum = options[:maximum] || 100
|
28
|
+
end
|
29
|
+
@maximum.round
|
30
|
+
|
31
|
+
@progress_char = options[:char] || '#'
|
32
|
+
@factor = (options[:size] || 20).round.to_f / @maximum
|
33
|
+
@ostream = options[:ostream] || $stdout
|
34
|
+
@progress = 0
|
35
|
+
@value = 0
|
36
|
+
|
37
|
+
self + (options[:start] || 0)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add an amount to the current value of the progress bar
|
41
|
+
#
|
42
|
+
# This triggers a refresh of the progress bar, if the added value actually
|
43
|
+
# changes the displayed bar.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
#
|
47
|
+
# progress_bar + 1 # (will add 1)
|
48
|
+
# progress_bar + 5 # (will add 5)
|
49
|
+
# progress_bar.+ # (will add 1)
|
50
|
+
def +(value = 1)
|
51
|
+
return if value <= 0
|
52
|
+
value = value.round
|
53
|
+
old_progress = @progress
|
54
|
+
@value += value
|
55
|
+
add_progress = (value * @factor).round
|
56
|
+
@progress += add_progress
|
57
|
+
|
58
|
+
@progress = 100 if @progress > 100
|
59
|
+
|
60
|
+
difference = @progress - old_progress
|
61
|
+
if difference > 0 && @progress <= @maximum
|
62
|
+
@ostream << @progress_char * difference
|
63
|
+
@ostream.flush
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
module Rubikon
|
7
|
+
|
8
|
+
class Setter < Action
|
9
|
+
|
10
|
+
def run(*args)
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
module Rubikon
|
7
|
+
|
8
|
+
# A class for displaying and managing throbbers
|
9
|
+
class Throbber < Thread
|
10
|
+
|
11
|
+
SPINNER = '-\|/'
|
12
|
+
|
13
|
+
# Creates and runs a Throbber that outputs to the given IO stream while the
|
14
|
+
# given thread is alive
|
15
|
+
#
|
16
|
+
# # +ostream+:: The IO stream the throbber should be written to
|
17
|
+
# +thread+:: The thread that should be watched
|
18
|
+
def initialize(ostream, thread)
|
19
|
+
proc = Proc.new do |ostream, thread|
|
20
|
+
step = 0
|
21
|
+
ostream.putc 32
|
22
|
+
while thread.alive?
|
23
|
+
ostream << "\b#{SPINNER[step].chr}"
|
24
|
+
ostream.flush
|
25
|
+
step = (step + 1) % 4
|
26
|
+
sleep 0.25
|
27
|
+
end
|
28
|
+
ostream.putc 8
|
29
|
+
end
|
30
|
+
|
31
|
+
super { proc.call(ostream, thread) }
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/rubikon.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'test_helper'
|
7
|
+
|
8
|
+
class ActionTests < Test::Unit::TestCase
|
9
|
+
|
10
|
+
context 'A Rubikon action' do
|
11
|
+
|
12
|
+
should 'throw an exception when no code block is given' do
|
13
|
+
assert_raise Rubikon::BlockMissingError do
|
14
|
+
Rubikon::Action.new
|
15
|
+
end
|
16
|
+
assert_raise Rubikon::BlockMissingError do
|
17
|
+
options = {}
|
18
|
+
Rubikon::Action.new options
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'not raise an exception when created without options' do
|
23
|
+
action_options = {
|
24
|
+
:description => 'this is an action',
|
25
|
+
:param_type => String
|
26
|
+
}
|
27
|
+
assert_nothing_raised do
|
28
|
+
action = Rubikon::Action.new action_options do end
|
29
|
+
assert_equal action_options[:description], action.description
|
30
|
+
assert_equal action_options[:param_type], action.param_type
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -3,17 +3,10 @@
|
|
3
3
|
#
|
4
4
|
# Copyright (c) 2009, Sebastian Staudt
|
5
5
|
|
6
|
-
require '
|
7
|
-
require 'shoulda'
|
8
|
-
|
9
|
-
begin require 'redgreen'; rescue LoadError; end
|
10
|
-
|
11
|
-
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
12
|
-
$: << File.dirname(__FILE__)
|
13
|
-
require 'rubikon'
|
6
|
+
require 'test_helper'
|
14
7
|
require 'testapp'
|
15
8
|
|
16
|
-
class
|
9
|
+
class ApplicationTests < Test::Unit::TestCase
|
17
10
|
|
18
11
|
context 'A Rubikon application\'s class' do
|
19
12
|
|
@@ -21,6 +14,12 @@ class RubikonTests < Test::Unit::TestCase
|
|
21
14
|
@app = RubikonTestApp.instance
|
22
15
|
end
|
23
16
|
|
17
|
+
should 'be a singleton' do
|
18
|
+
assert_raise NoMethodError do
|
19
|
+
RubikonTestApp.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
24
23
|
should 'run it\'s instance for called methods' do
|
25
24
|
assert_equal @app.run(%w{--object_id}), RubikonTestApp.run(%w{--object_id})
|
26
25
|
end
|
@@ -34,12 +33,6 @@ class RubikonTests < Test::Unit::TestCase
|
|
34
33
|
@app.set :ostream, @ostream
|
35
34
|
end
|
36
35
|
|
37
|
-
should 'be a singleton' do
|
38
|
-
assert_raise NoMethodError do
|
39
|
-
RubikonTestApp.new
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
36
|
should 'exit gracefully' do
|
44
37
|
unknown = '--unknown'
|
45
38
|
@app.set :raise_errors, false
|
@@ -56,7 +49,7 @@ class RubikonTests < Test::Unit::TestCase
|
|
56
49
|
end
|
57
50
|
|
58
51
|
should 'run it\'s default action without options' do
|
59
|
-
result = @app.run
|
52
|
+
result = @app.run([])
|
60
53
|
assert_equal 1, result.size
|
61
54
|
assert_equal 'default action', result.first
|
62
55
|
end
|
@@ -143,31 +136,4 @@ class RubikonTests < Test::Unit::TestCase
|
|
143
136
|
|
144
137
|
end
|
145
138
|
|
146
|
-
context 'A Rubikon action' do
|
147
|
-
|
148
|
-
should 'throw an exception when no code block is given' do
|
149
|
-
assert_raise Rubikon::BlockMissingError do
|
150
|
-
Rubikon::Action.new 'name'
|
151
|
-
end
|
152
|
-
assert_raise Rubikon::BlockMissingError do
|
153
|
-
Rubikon::Action.new 'name', {}
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
should 'not raise an exception when created without options' do
|
158
|
-
action_name = 'someaction'
|
159
|
-
action_options = {
|
160
|
-
:description => 'this is an action',
|
161
|
-
:param_type => String
|
162
|
-
}
|
163
|
-
assert_nothing_raised do
|
164
|
-
action = Rubikon::Action.new action_name, action_options do end
|
165
|
-
assert_equal action_name, action.name
|
166
|
-
assert_equal action_options[:description], action.description
|
167
|
-
assert_equal action_options[:param_type], action.param_type
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
end
|
172
|
-
|
173
139
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'test_helper'
|
7
|
+
|
8
|
+
class ProgressBarTests < Test::Unit::TestCase
|
9
|
+
|
10
|
+
context 'A progress bar' do
|
11
|
+
|
12
|
+
should 'have default settings' do
|
13
|
+
@bar = Rubikon::ProgressBar.new
|
14
|
+
assert_equal 0.2, @bar.instance_variable_get(:@factor)
|
15
|
+
assert_equal 100, @bar.instance_variable_get(:@maximum)
|
16
|
+
assert_equal $stdout, @bar.instance_variable_get(:@ostream)
|
17
|
+
assert_equal 0, @bar.instance_variable_get(:@progress)
|
18
|
+
assert_equal '#', @bar.instance_variable_get(:@progress_char)
|
19
|
+
assert_equal 0, @bar.instance_variable_get(:@value)
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'be customizable' do
|
23
|
+
options = {
|
24
|
+
:char => '+',
|
25
|
+
:maximum => 10,
|
26
|
+
:ostream => StringIO.new,
|
27
|
+
:size => 100,
|
28
|
+
:start => 5
|
29
|
+
}
|
30
|
+
@bar = Rubikon::ProgressBar.new(options)
|
31
|
+
assert_equal options[:size].to_f / options[:maximum], @bar.instance_variable_get(:@factor)
|
32
|
+
assert_equal options[:maximum], @bar.instance_variable_get(:@maximum)
|
33
|
+
assert_equal options[:ostream], @bar.instance_variable_get(:@ostream)
|
34
|
+
assert_equal options[:start].to_f / options[:maximum] * options[:size], @bar.instance_variable_get(:@progress)
|
35
|
+
assert_equal options[:char], @bar.instance_variable_get(:@progress_char)
|
36
|
+
assert_equal options[:start], @bar.instance_variable_get(:@value)
|
37
|
+
end
|
38
|
+
|
39
|
+
should 'draw correctly for different sizes' do
|
40
|
+
ostream = StringIO.new
|
41
|
+
options = { :ostream => ostream, :start => 50 }
|
42
|
+
|
43
|
+
@bar = Rubikon::ProgressBar.new(options)
|
44
|
+
assert_equal "#" * 10, ostream.string
|
45
|
+
@bar + 50
|
46
|
+
assert_equal "#" * 20, ostream.string
|
47
|
+
|
48
|
+
ostream.string = ''
|
49
|
+
options[:size] = 10
|
50
|
+
|
51
|
+
@bar = Rubikon::ProgressBar.new(options)
|
52
|
+
assert_equal "#" * 5, ostream.string
|
53
|
+
@bar + 10
|
54
|
+
assert_equal "#" * 6, ostream.string
|
55
|
+
|
56
|
+
ostream.string = ''
|
57
|
+
options[:size] = 100
|
58
|
+
|
59
|
+
@bar = Rubikon::ProgressBar.new(options)
|
60
|
+
assert_equal "#" * 50, ostream.string
|
61
|
+
@bar + 30
|
62
|
+
assert_equal "#" * 80, ostream.string
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'not overflow' do
|
66
|
+
ostream = StringIO.new
|
67
|
+
options = { :ostream => ostream, :size => 100 }
|
68
|
+
|
69
|
+
@bar = Rubikon::ProgressBar.new options
|
70
|
+
@bar + 101
|
71
|
+
assert_equal "#" * 100, ostream.string
|
72
|
+
assert_equal 100, @bar.instance_variable_get(:@progress)
|
73
|
+
assert_equal 101, @bar.instance_variable_get(:@value)
|
74
|
+
|
75
|
+
ostream.string = ''
|
76
|
+
options[:start] = 50
|
77
|
+
|
78
|
+
@bar = Rubikon::ProgressBar.new options
|
79
|
+
@bar + 51
|
80
|
+
assert_equal "#" * 100, ostream.string
|
81
|
+
assert_equal 100, @bar.instance_variable_get(:@progress)
|
82
|
+
assert_equal 101, @bar.instance_variable_get(:@value)
|
83
|
+
|
84
|
+
ostream.string = ''
|
85
|
+
options[:start] = 101
|
86
|
+
|
87
|
+
@bar = Rubikon::ProgressBar.new options
|
88
|
+
assert_equal "#" * 100, ostream.string
|
89
|
+
assert_equal 100, @bar.instance_variable_get(:@progress)
|
90
|
+
assert_equal 101, @bar.instance_variable_get(:@value)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
7
|
+
$: << File.dirname(__FILE__)
|
8
|
+
require 'rubikon'
|
9
|
+
include Rubikon
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'shoulda'
|
13
|
+
|
14
|
+
unless RUBY_VERSION[0..2] == '1.9'
|
15
|
+
begin require 'redgreen'; rescue LoadError; end
|
16
|
+
end
|
data/test/testapp.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'test_helper'
|
7
|
+
|
8
|
+
class ThrobberTests < Test::Unit::TestCase
|
9
|
+
|
10
|
+
context 'A throbber' do
|
11
|
+
|
12
|
+
should 'be a subclass of Thread' do
|
13
|
+
assert_equal Thread, Rubikon::Throbber.superclass
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'have default throbber strings' do
|
17
|
+
assert_equal %w{SPINNER}, Throbber.constants
|
18
|
+
assert_equal '-\|/', Rubikon::Throbber.const_get(:SPINNER)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubikon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Staudt
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-11-07 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -29,10 +29,19 @@ files:
|
|
29
29
|
- VERSION.yml
|
30
30
|
- lib/rubikon.rb
|
31
31
|
- lib/rubikon/action.rb
|
32
|
-
- lib/rubikon/application.rb
|
32
|
+
- lib/rubikon/application/base.rb
|
33
|
+
- lib/rubikon/application/class_methods.rb
|
34
|
+
- lib/rubikon/application/instance_methods.rb
|
33
35
|
- lib/rubikon/exceptions.rb
|
34
|
-
-
|
36
|
+
- lib/rubikon/progress_bar.rb
|
37
|
+
- lib/rubikon/setter.rb
|
38
|
+
- lib/rubikon/throbber.rb
|
39
|
+
- test/action_tests.rb
|
40
|
+
- test/application_tests.rb
|
41
|
+
- test/progress_bar_tests.rb
|
42
|
+
- test/test_helper.rb
|
35
43
|
- test/testapp.rb
|
44
|
+
- test/throbber_tests.rb
|
36
45
|
has_rdoc: true
|
37
46
|
homepage: http://koraktor.github.com/rubikon
|
38
47
|
licenses: []
|
@@ -66,5 +75,9 @@ signing_key:
|
|
66
75
|
specification_version: 3
|
67
76
|
summary: Rubikon - A Ruby console app framework
|
68
77
|
test_files:
|
69
|
-
- test/
|
78
|
+
- test/action_tests.rb
|
79
|
+
- test/application_tests.rb
|
80
|
+
- test/progress_bar_tests.rb
|
81
|
+
- test/test_helper.rb
|
70
82
|
- test/testapp.rb
|
83
|
+
- test/throbber_tests.rb
|
data/lib/rubikon/application.rb
DELETED
@@ -1,331 +0,0 @@
|
|
1
|
-
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
-
# terms of the new BSD License.
|
3
|
-
#
|
4
|
-
# Copyright (c) 2009, Sebastian Staudt
|
5
|
-
|
6
|
-
require 'singleton'
|
7
|
-
require 'yaml'
|
8
|
-
|
9
|
-
require 'rubikon/action'
|
10
|
-
require 'rubikon/exceptions'
|
11
|
-
|
12
|
-
module Rubikon
|
13
|
-
|
14
|
-
version = YAML.load_file(File.join(File.dirname(__FILE__), '..', '..', 'VERSION.yml'))
|
15
|
-
VERSION = "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
|
16
|
-
|
17
|
-
# The main class of Rubikon. Let your own application class inherit from this
|
18
|
-
# one.
|
19
|
-
class Application
|
20
|
-
|
21
|
-
include Singleton
|
22
|
-
|
23
|
-
attr_reader :settings
|
24
|
-
|
25
|
-
# Initialize with default settings (see set for more detail)
|
26
|
-
#
|
27
|
-
# If you really need to override this in your application class, be sure to
|
28
|
-
# call +super+
|
29
|
-
def initialize
|
30
|
-
@actions = {}
|
31
|
-
@aliases = {}
|
32
|
-
@default = nil
|
33
|
-
@settings = {
|
34
|
-
:autorun => true,
|
35
|
-
:dashed_options => true,
|
36
|
-
:help_banner => "Usage: #{$0}",
|
37
|
-
:istream => $stdin,
|
38
|
-
:name => self.class.to_s,
|
39
|
-
:ostream => $stdout,
|
40
|
-
:raise_errors => false
|
41
|
-
}
|
42
|
-
end
|
43
|
-
|
44
|
-
# Define an Application Action
|
45
|
-
#
|
46
|
-
# +name+:: The name of the action. Used as an option parameter.
|
47
|
-
# +options+:: A Hash of options to be used on the created Action
|
48
|
-
# (default: <tt>{}</tt>)
|
49
|
-
# +block+:: A block containing the code that should be executed when this
|
50
|
-
# Action is called, i.e. when the Application is called with
|
51
|
-
# the associated option parameter
|
52
|
-
def action(name, options = {}, &block)
|
53
|
-
raise "No block given" unless block_given?
|
54
|
-
|
55
|
-
key = name
|
56
|
-
key = "--#{key}" if @settings[:dashed_options]
|
57
|
-
|
58
|
-
@actions[key.to_sym] = Action.new(name, options, &block)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Define an alias to an Action
|
62
|
-
#
|
63
|
-
# +name+:: The name of the alias
|
64
|
-
# +action+:: The name of the Action that should be aliased
|
65
|
-
#
|
66
|
-
# Example:
|
67
|
-
#
|
68
|
-
# action_alias :doit, :dosomething
|
69
|
-
def action_alias(name, action)
|
70
|
-
@aliases[name.to_sym] = action.to_sym
|
71
|
-
end
|
72
|
-
|
73
|
-
# Define the default Action of the Application
|
74
|
-
#
|
75
|
-
# +options+:: A Hash of options to be used on the created Action
|
76
|
-
# (default: <tt>{}</tt>)
|
77
|
-
# +block+:: A block containing the code that should be executed when this
|
78
|
-
# Action is called, i.e. when no option is given to the
|
79
|
-
# Application
|
80
|
-
def default(options = {}, &block)
|
81
|
-
@default = Action.new(:default, options, &block)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Prompts the user for input
|
85
|
-
#
|
86
|
-
# If +prompt+ is not empty this will display a prompt using
|
87
|
-
# <tt>prompt.to_s</tt>.
|
88
|
-
#
|
89
|
-
# +prompt+:: A String or other Object responding to +to_s+ used for
|
90
|
-
# displaying a prompt to the user (default: <tt>''</tt>)
|
91
|
-
#
|
92
|
-
# Example:
|
93
|
-
#
|
94
|
-
# action 'interactive' do
|
95
|
-
# # Display a prompt "Please type something: "
|
96
|
-
# user_provided_value = input 'Please type something'
|
97
|
-
#
|
98
|
-
# # Do something with the data
|
99
|
-
# ...
|
100
|
-
# end
|
101
|
-
def input(prompt = '')
|
102
|
-
unless prompt.to_s.empty?
|
103
|
-
ostream << "#{prompt}: "
|
104
|
-
end
|
105
|
-
@settings[:istream].gets[0..-2]
|
106
|
-
end
|
107
|
-
|
108
|
-
# Convenience method for accessing the user-defined output stream
|
109
|
-
#
|
110
|
-
# Use this if you want to work directly with the output stream
|
111
|
-
#
|
112
|
-
# Example:
|
113
|
-
#
|
114
|
-
# ostream.flush
|
115
|
-
def ostream
|
116
|
-
@settings[:ostream]
|
117
|
-
end
|
118
|
-
|
119
|
-
# Output text using +IO#<<+ of the output stream
|
120
|
-
#
|
121
|
-
# +text+:: The text to write into the output stream
|
122
|
-
def put(text)
|
123
|
-
ostream << text
|
124
|
-
ostream.flush
|
125
|
-
end
|
126
|
-
|
127
|
-
# Output a character using +IO#putc+ of the output stream
|
128
|
-
#
|
129
|
-
# +char+:: The character to write into the output stream
|
130
|
-
def putc(char)
|
131
|
-
ostream.putc char
|
132
|
-
end
|
133
|
-
|
134
|
-
# Output a line of text using +IO#puts+ of the output stream
|
135
|
-
#
|
136
|
-
# +text+:: The text to write into the output stream
|
137
|
-
def puts(text)
|
138
|
-
ostream.puts text
|
139
|
-
end
|
140
|
-
|
141
|
-
# Run this application
|
142
|
-
#
|
143
|
-
# +args+:: The command line arguments that should be given to the
|
144
|
-
# application as options
|
145
|
-
#
|
146
|
-
# Calling this method explicitly is not required when you want to create a
|
147
|
-
# simple application (having one main class inheriting from
|
148
|
-
# Rubikon::Application). But it's useful for testing or if you want to have
|
149
|
-
# some sort of sub-applications.
|
150
|
-
def run(args = ARGV)
|
151
|
-
begin
|
152
|
-
assign_aliases unless @aliases.empty?
|
153
|
-
action_results = []
|
154
|
-
|
155
|
-
if !@default.nil? and args.empty?
|
156
|
-
action_results << @default.run
|
157
|
-
else
|
158
|
-
parse_options(args).each do |action, args|
|
159
|
-
action_results << @actions[action].run(*args)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
rescue
|
163
|
-
if @settings[:raise_errors]
|
164
|
-
raise $!
|
165
|
-
else
|
166
|
-
puts "Error:\n #{$!.message}"
|
167
|
-
puts " #{$!.backtrace.join("\n ")}" if $DEBUG
|
168
|
-
exit 1
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
action_results
|
173
|
-
end
|
174
|
-
|
175
|
-
# Sets an application setting
|
176
|
-
#
|
177
|
-
# +setting+:: The name of the setting to change, will be symbolized first.
|
178
|
-
# +value+:: The value the setting should be changed to
|
179
|
-
#
|
180
|
-
# Available settings
|
181
|
-
# +autorun+:: If true, let the application run as soon as its class
|
182
|
-
# is defined
|
183
|
-
# +dashed_options+:: If true, each option is prepended with a double-dash
|
184
|
-
# (<tt>-</tt><tt>-</tt>)
|
185
|
-
# +help_banner+:: Defines a banner for the help message (<em>unused</em>)
|
186
|
-
# +istream+:: Defines an input stream to use
|
187
|
-
# +name+:: Defines the name of the application
|
188
|
-
# +ostream+:: Defines an output stream to use
|
189
|
-
# +raise_errors+:: If true, raise errors, otherwise fail gracefully
|
190
|
-
#
|
191
|
-
# Example:
|
192
|
-
#
|
193
|
-
# set :name, 'My App'
|
194
|
-
# set :autorun, false
|
195
|
-
def set(setting, value)
|
196
|
-
@settings[setting.to_sym] = value
|
197
|
-
end
|
198
|
-
|
199
|
-
# Displays a throbber while the given block is executed
|
200
|
-
#
|
201
|
-
# Example:
|
202
|
-
#
|
203
|
-
# action 'slow' do
|
204
|
-
# throbber do
|
205
|
-
# # Add some long running code here
|
206
|
-
# ...
|
207
|
-
# end
|
208
|
-
# end
|
209
|
-
def throbber(&block)
|
210
|
-
spinner = '-\|/'
|
211
|
-
current_ostream = ostream
|
212
|
-
@settings[:ostream] = StringIO.new
|
213
|
-
|
214
|
-
code_thread = Thread.new { block.call }
|
215
|
-
|
216
|
-
throbber_thread = Thread.new do
|
217
|
-
i = 0
|
218
|
-
current_ostream.putc 32
|
219
|
-
while code_thread.alive?
|
220
|
-
current_ostream.putc 8
|
221
|
-
current_ostream.putc spinner[i]
|
222
|
-
current_ostream.flush
|
223
|
-
i = (i + 1) % 4
|
224
|
-
sleep 0.25
|
225
|
-
end
|
226
|
-
current_ostream.putc 8
|
227
|
-
end
|
228
|
-
|
229
|
-
code_thread.join
|
230
|
-
throbber_thread.join
|
231
|
-
|
232
|
-
current_ostream << ostream.string
|
233
|
-
@settings[:ostream] = current_ostream
|
234
|
-
end
|
235
|
-
|
236
|
-
private
|
237
|
-
|
238
|
-
# Returns whether this application should be ran automatically
|
239
|
-
def self.autorun?
|
240
|
-
instance.settings[:autorun] || false
|
241
|
-
end
|
242
|
-
|
243
|
-
# Enables autorun functionality using <tt>Kernel#at_exit</tt>
|
244
|
-
#
|
245
|
-
# +subclass+:: The subclass inheriting from Application. This is the user's
|
246
|
-
# application.
|
247
|
-
#
|
248
|
-
# <em>This is called automatically when subclassing Application.</em>
|
249
|
-
def self.inherited(subclass)
|
250
|
-
Singleton.__init__(subclass)
|
251
|
-
at_exit { subclass.run if subclass.autorun? }
|
252
|
-
end
|
253
|
-
|
254
|
-
# This is used for convinience. Method calls on the class itself are
|
255
|
-
# relayed to the singleton instance.
|
256
|
-
#
|
257
|
-
# +method_name+:: The name of the method being called
|
258
|
-
# +args+:: Any arguments that are given to the method
|
259
|
-
# +block+:: A block that may be given to the method
|
260
|
-
#
|
261
|
-
# <em>This is called automatically when calling methods on the class.</em>
|
262
|
-
def self.method_missing(method_name, *args, &block)
|
263
|
-
instance.send(method_name, *args, &block)
|
264
|
-
end
|
265
|
-
|
266
|
-
# Relay putc to the instance method
|
267
|
-
#
|
268
|
-
# This is used to hide <tt>Kernel#putc</tt> so that the Application's
|
269
|
-
# output IO object is used for printing text
|
270
|
-
#
|
271
|
-
# +text+:: The text to write into the output stream
|
272
|
-
def self.putc(text)
|
273
|
-
instance.putc text
|
274
|
-
end
|
275
|
-
|
276
|
-
# Relay puts to the instance method
|
277
|
-
#
|
278
|
-
# This is used to hide <tt>Kernel#puts</tt> so that the Application's
|
279
|
-
# output IO object is used for printing text
|
280
|
-
#
|
281
|
-
# +text+:: The text to write into the output stream
|
282
|
-
def self.puts(text)
|
283
|
-
instance.puts text
|
284
|
-
end
|
285
|
-
|
286
|
-
# Assigns aliases to the actions that have been defined using action_alias
|
287
|
-
#
|
288
|
-
# Clears the aliases Hash afterwards
|
289
|
-
def assign_aliases
|
290
|
-
@aliases.each do |key, action|
|
291
|
-
if @settings[:dashed_options]
|
292
|
-
action = "--#{action}".to_sym
|
293
|
-
key = "--#{key}".to_sym
|
294
|
-
end
|
295
|
-
|
296
|
-
unless @actions.key? key
|
297
|
-
@actions[key] = @actions[action]
|
298
|
-
else
|
299
|
-
warn "There's already an action called \"#{key}\"."
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
@aliases = {}
|
304
|
-
end
|
305
|
-
|
306
|
-
# Parses the options used when starting the application
|
307
|
-
#
|
308
|
-
# +options+:: An Array of Strings that should be used as application
|
309
|
-
# options. Usually +ARGV+ is used for this.
|
310
|
-
def parse_options(options)
|
311
|
-
actions_to_call = {}
|
312
|
-
last_action = nil
|
313
|
-
|
314
|
-
options.each do |option|
|
315
|
-
option_sym = option.to_sym
|
316
|
-
if @actions.keys.include? option_sym
|
317
|
-
actions_to_call[option_sym] = []
|
318
|
-
last_action = option_sym
|
319
|
-
elsif last_action.nil? || (option.is_a?(String) && @settings[:dashed_options] && option[0..1] == '--')
|
320
|
-
raise UnknownOptionError.new(option)
|
321
|
-
else
|
322
|
-
actions_to_call[last_action] << option
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
actions_to_call
|
327
|
-
end
|
328
|
-
|
329
|
-
end
|
330
|
-
|
331
|
-
end
|