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