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 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