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 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
- ### Installation
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 easy by typing `./myapp.rb`.
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
@@ -13,8 +13,8 @@ task :default => :test
13
13
 
14
14
  # Test task
15
15
  Rake::TestTask.new do |t|
16
- t.libs << 'lib'
17
- t.test_files = test_files
16
+ t.libs << 'lib' << 'test'
17
+ t.pattern = 'test/**/*_tests.rb'
18
18
  t.verbose = true
19
19
  end
20
20
 
data/VERSION.yml CHANGED
@@ -1,4 +1,5 @@
1
- ---
1
+ ---
2
2
  :major: 0
3
- :minor: 1
3
+ :minor: 2
4
4
  :patch: 0
5
+ :build:
@@ -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(name, options = {}, &block)
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
- if (@block.arity >= 0 and args.size < @block.arity) or (@block.arity < 0 and args.size < -@block.arity - 1)
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 |i|
58
- return false unless args[i].is_a? @param_type[i]
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
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
@@ -6,4 +6,4 @@
6
6
  libdir = File.dirname(__FILE__)
7
7
  $:.unshift(libdir) unless $:.include?(libdir)
8
8
 
9
- require 'rubikon/application'
9
+ require 'rubikon/application/base'
@@ -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 'rubygems'
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 RubikonTests < Test::Unit::TestCase
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
@@ -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
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # Copyright (c) 2009, Sebastian Staudt
5
5
 
6
- class RubikonTestApp < Rubikon::Application
6
+ class RubikonTestApp < Rubikon::Application::Base
7
7
 
8
8
  set :autorun, false
9
9
  set :name, 'Rubikon test application'
@@ -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.1.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-10-28 00:00:00 +01:00
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
- - test/test.rb
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/test.rb
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
@@ -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