rubikon 0.3.0 → 0.4.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/README.md CHANGED
@@ -28,7 +28,7 @@ Creating a Rubikon application is as simple as creating a Ruby class:
28
28
  require 'rubygems'
29
29
  require 'rubikon'
30
30
 
31
- class MyApplication < Rubikon::Application
31
+ class MyApplication < Rubikon::Application::Base
32
32
  end
33
33
 
34
34
  If you save this code in a file called `myapp.rb` you can run it using
@@ -39,7 +39,7 @@ even more easily by typing `./myapp.rb`.
39
39
  Now go on and define what your application should do when the user runs it.
40
40
  This is done using `default`:
41
41
 
42
- class MyApplication < Rubikon::Application
42
+ class MyApplication < Rubikon::Application::Base
43
43
 
44
44
  default do
45
45
  puts 'Hello World!'
@@ -49,9 +49,9 @@ This is done using `default`:
49
49
 
50
50
  If you run this application it will just print `Hello World!`.
51
51
 
52
- You can also add command-line options to your appication using `command`:
52
+ You can also add command-line options to your application using `command`:
53
53
 
54
- class MyApplication < Rubikon::Application
54
+ class MyApplication < Rubikon::Application::Base
55
55
 
56
56
  command :hello do
57
57
  puts 'Hello World!'
@@ -74,19 +74,18 @@ Flags and options are easily added to your application's commands using
74
74
  Rubikon's DSL:
75
75
 
76
76
  flag :more
77
- option :name, 2
77
+ option :name, [:who]
78
78
  command :hello do
79
- ...
79
+ puts "Hello #{who}"
80
80
  end
81
81
 
82
-
83
82
  Please see the `samples` directory for more in detail sample applications.
84
83
 
85
84
  **Warning**:
86
85
 
87
86
  Rubikon is still in an early development stage. If you want to use it be aware
88
- that you will probably run into problems and or restrictions. See the
89
- Contribute section if you want to help making Rubikon better.
87
+ that you will might run into problems and or restrictions. See the Contribute
88
+ section if you want to help to make Rubikon better.
90
89
 
91
90
  ## Features
92
91
 
@@ -132,8 +131,8 @@ You may also see Rubikon as a portmanteau word consisting of *"Ruby"* and
132
131
  ## License
133
132
 
134
133
  This code is free software; you can redistribute it and/or modify it under the
135
- terms of the new BSD License. A copy of this license can be found in the LICENSE
136
- file.
134
+ terms of the new BSD License. A copy of this license can be found in the
135
+ LICENSE file.
137
136
 
138
137
  ## Credits
139
138
 
@@ -146,6 +145,8 @@ file.
146
145
  * [GitHub project page][1]
147
146
  * [GitHub issue tracker][2]
148
147
 
148
+ Follow Rubikon on Twitter [@rubikonrb](http://twitter.com/rubikonrb).
149
+
149
150
  [1]: http://github.com/koraktor/rubikon
150
151
  [2]: http://github.com/koraktor/rubikon/issues
151
152
  [3]: http://koraktor.github.com/rubikon
data/Rakefile CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  require 'rake/testtask'
7
7
 
8
+ samples_files = Dir.glob(File.join('samples', '**', '*.rb'))
8
9
  src_files = Dir.glob(File.join('lib', '**', '*.rb'))
9
10
  test_files = Dir.glob(File.join('test', '**', '*.rb'))
10
11
 
@@ -13,7 +14,7 @@ task :default => :test
13
14
  # Test task
14
15
  Rake::TestTask.new do |t|
15
16
  t.libs << 'lib' << 'test'
16
- t.pattern = 'test/**/*_tests.rb'
17
+ t.pattern = 'test/**/test_*.rb'
17
18
  t.verbose = true
18
19
  end
19
20
 
@@ -31,13 +32,14 @@ begin
31
32
  gem.email = 'koraktor@gmail.com'
32
33
  gem.description = 'A simple to use, yet powerful Ruby framework for building console-based applications.'
33
34
  gem.date = Time.now
34
- gem.files = %w(README.md Rakefile LICENSE) + src_files + test_files
35
+ gem.files = %w(README.md Rakefile LICENSE) + samples_files + src_files + test_files
35
36
  gem.has_rdoc = false
36
37
  gem.homepage = 'http://koraktor.github.com/rubikon'
37
38
  gem.name = gem.rubyforge_project = 'rubikon'
38
39
  gem.summary = 'Rubikon - A Ruby console app framework'
39
40
 
40
41
  gem.add_development_dependency('jeweler')
42
+ gem.add_development_dependency('shoulda')
41
43
  gem.add_development_dependency('yard')
42
44
  end
43
45
  rescue LoadError
@@ -0,0 +1,43 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2010, Sebastian Staudt
5
+
6
+ unless Object.method_defined?(:respond_to_missing?)
7
+
8
+ # Extends Ruby's own Object class with method #start_with? for Ruby < 1.9.2
9
+ #
10
+ # @author Sebastian Staudt
11
+ # @since 0.4.0
12
+ class Object
13
+
14
+ # Returns +true+ if _obj_ responds to the given method. Private methods
15
+ # are included in the search only if the optional second parameter
16
+ # evaluates to +true+.
17
+ #
18
+ # If the method is not implemented, as Process.fork on Windows,
19
+ # File.lchmod on GNU/Linux, etc., +false+ is returned.
20
+ #
21
+ # If the method is not defined, respond_to_missing? method is called and
22
+ # the result is returned.
23
+ #
24
+ # @see #respond_to_missing?
25
+ def respond_to?(symbol, include_private = false)
26
+ super || respond_to_missing?(symbol, include_private)
27
+ end
28
+
29
+ # Hook method to return whether the _obj_ can respond to _id_ method or
30
+ # not.
31
+ #
32
+ # @param [Symbol] symbol The id of the method to check
33
+ # @return [Boolean] +true+ if this object responds to this method via
34
+ # via method_missing
35
+ # @see #method_missing
36
+ # @see #respond_to?
37
+ def respond_to_missing?(symbol, include_private = false)
38
+ false
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -5,8 +5,16 @@
5
5
 
6
6
  unless String.method_defined?(:start_with?)
7
7
 
8
+ # Extends Ruby's own String class with method #start_with? for Ruby < 1.8.7
9
+ #
10
+ # @author Sebastian Staudt
11
+ # @since 0.3.0
8
12
  class String
9
13
 
14
+ # Returns true if this string starts with the given substring
15
+ #
16
+ # @param [String] start The substring to check
17
+ # @return [Boolean] +true+ if this String starts with the given substring
10
18
  def start_with?(start)
11
19
  !/^#{start}/.match(self).nil?
12
20
  end
@@ -3,15 +3,17 @@
3
3
  #
4
4
  # Copyright (c) 2009-2010, Sebastian Staudt
5
5
 
6
- require 'singleton'
7
- require 'yaml'
8
-
9
6
  require 'rubikon/application/class_methods'
10
7
  require 'rubikon/application/dsl_methods'
11
8
  require 'rubikon/application/instance_methods'
12
9
 
13
10
  module Rubikon
14
11
 
12
+ # The Application module contains all basic functionality of a Rubikon
13
+ # application
14
+ #
15
+ # @author Sebastian Staudt
16
+ # @since 0.2.0
15
17
  module Application
16
18
 
17
19
  # The main class of Rubikon. Let your own application class inherit from
@@ -21,13 +23,10 @@ module Rubikon
21
23
  # @since 0.2.0
22
24
  class Base
23
25
 
24
- class << self
25
- include Rubikon::Application::ClassMethods
26
- end
26
+ extend ClassMethods
27
27
 
28
- include Rubikon::Application::DSLMethods
29
- include Rubikon::Application::InstanceMethods
30
- include Singleton
28
+ include DSLMethods
29
+ include InstanceMethods
31
30
 
32
31
  end
33
32
 
@@ -3,6 +3,8 @@
3
3
  #
4
4
  # Copyright (c) 2009-2010, Sebastian Staudt
5
5
 
6
+ require 'singleton'
7
+
6
8
  module Rubikon
7
9
 
8
10
  module Application
@@ -30,8 +32,8 @@ module Rubikon
30
32
  # @param [Class] subclass The subclass inheriting from Application::Base.
31
33
  # This is the user's application.
32
34
  def inherited(subclass)
33
- super
34
- Singleton.__init__(subclass)
35
+ subclass.class_eval { include Singleton }
36
+ subclass.send(:base_file=, File.expand_path(caller.first.split(':').first))
35
37
  at_exit { subclass.run if subclass.send(:autorun?) }
36
38
  end
37
39
 
@@ -16,25 +16,13 @@ module Rubikon
16
16
  # @since 0.3.0
17
17
  module DSLMethods
18
18
 
19
- private
19
+ # @return [String] The (first) definition file of the application
20
+ attr_reader :base_file
20
21
 
21
- # Returns the arguments for the currently executed Command
22
- #
23
- # @return [Array]
24
- # @since 0.2.0
25
- #
26
- # @example
27
- # command :something do
28
- # puts arguments[0]
29
- # end
30
- def args
31
- unless @current_command.nil?
32
- @current_command.arguments
33
- else
34
- @current_global_option.arguments
35
- end
36
- end
37
- alias_method :arguments, :args
22
+ # @return [String] The absolute path of the application
23
+ attr_reader :path
24
+
25
+ private
38
26
 
39
27
  # Define a new application Command or an alias to an existing one
40
28
  #
@@ -50,7 +38,7 @@ module Rubikon
50
38
  #
51
39
  # @return [Command]
52
40
  # @since 0.2.0
53
- def command(name, description = nil, &block)
41
+ def command(name, arg_count = nil, description = nil, &block)
54
42
  if name.is_a? Hash
55
43
  name.each do |alias_name, command_name|
56
44
  command = @commands[command_name]
@@ -62,7 +50,7 @@ module Rubikon
62
50
  end
63
51
  end
64
52
  else
65
- command = Command.new(self, name, &block)
53
+ command = Command.new(self, name, arg_count, &block)
66
54
  command.description = description unless description.nil?
67
55
  @commands.each do |command_alias, command_name|
68
56
  if command_name == command.name
@@ -75,7 +63,7 @@ module Rubikon
75
63
 
76
64
  unless command.nil? || @parameters.empty?
77
65
  @parameters.each do |parameter|
78
- command << parameter
66
+ command.add_param(parameter)
79
67
  end
80
68
  @parameters.clear
81
69
  end
@@ -131,7 +119,7 @@ module Rubikon
131
119
  if name.is_a? Hash
132
120
  @parameters << name
133
121
  else
134
- @parameters << Flag.new(name, &block)
122
+ @parameters << Flag.new(self, name, &block)
135
123
  end
136
124
  end
137
125
 
@@ -144,15 +132,16 @@ module Rubikon
144
132
  # @example
145
133
  # flag :status
146
134
  # command :something do
147
- # print_status if given? :status
135
+ # print_status if active? :status
148
136
  # end
149
- def given?(name)
137
+ def active?(name)
150
138
  name = name.to_sym
151
139
  parameter = @global_parameters[name]
152
140
  parameter = @current_command.parameters[name] if parameter.nil?
153
141
  return false if parameter.nil?
154
142
  parameter.active?
155
143
  end
144
+ alias_method :given?, :active?
156
145
 
157
146
  # Create a new flag with the given name to be used globally
158
147
  #
@@ -184,7 +173,7 @@ module Rubikon
184
173
  end
185
174
  end
186
175
  else
187
- flag = Flag.new(name, &block)
176
+ flag = Flag.new(self, name, &block)
188
177
  @global_parameters.each do |flag_alias, flag_name|
189
178
  if flag_name == flag.name
190
179
  @global_parameters[flag_alias] = flag
@@ -225,7 +214,7 @@ module Rubikon
225
214
  end
226
215
  end
227
216
  else
228
- option = Option.new(name, arg_count, &block)
217
+ option = Option.new(self, name, arg_count, &block)
229
218
  @global_parameters.each do |option_alias, option_name|
230
219
  if option_name == option.name
231
220
  @global_parameters[option_alias] = option
@@ -243,7 +232,7 @@ module Rubikon
243
232
  # @since 0.2.0
244
233
  #
245
234
  # @example Display a prompt "Please type something: "
246
- # action 'interactive' do
235
+ # command 'interactive' do
247
236
  # user_provided_value = input 'Please type something'
248
237
  #
249
238
  # # Do something with the data
@@ -280,7 +269,7 @@ module Rubikon
280
269
  if name.is_a? Hash
281
270
  @parameters << name
282
271
  else
283
- @parameters << Option.new(name.to_s, arg_count, &block)
272
+ @parameters << Option.new(self, name.to_s, arg_count, &block)
284
273
  end
285
274
  end
286
275
 
@@ -297,46 +286,44 @@ module Rubikon
297
286
  @settings[:ostream]
298
287
  end
299
288
 
300
- # Returns the parameters for the currently executed command
301
- #
302
- # @return [Array] The parameters of the currently executed command
303
- # @see Command
304
- # @since 0.2.0
305
- #
306
- # @example
307
- # option :message, 1
308
- # command :something do
309
- # puts parameters[:message].args[0] if given? :message
310
- # end
311
- def params
312
- @current_command.parameters
289
+ # Defines a block of code used as a hook that should be executed after
290
+ # the command execution has finished
291
+ #
292
+ # @param [Proc] The code block to execute after the command execution has
293
+ # finished
294
+ # @since 0.4.0
295
+ def post_execute(&block)
296
+ @hooks[:post_execute] = block
313
297
  end
314
- alias_method :parameters, :params
315
298
 
316
- # Output text using +IO#<<+ of the output stream
317
- #
318
- # @param [String] text The text to write into the output stream
319
- # @since 0.2.0
320
- def put(text)
321
- @settings[:ostream] << text
322
- @settings[:ostream].flush
299
+ # Defines a block of code used as a hook that should be executed after
300
+ # the application has been initialized
301
+ #
302
+ # @param [Proc] The code block to execute after the application has been
303
+ # initialized
304
+ # @since 0.4.0
305
+ def post_init(&block)
306
+ @hooks[:post_init] = block
323
307
  end
324
308
 
325
- # Output a character using +IO#putc+ of the output stream
326
- #
327
- # @param [String, Numeric] char The character to write into the output
328
- # stream
329
- # @since 0.2.0
330
- def putc(char)
331
- @settings[:ostream].putc char
309
+ # Defines a block of code used as a hook that should be executed before
310
+ # the command has been started
311
+ #
312
+ # @param [Proc] The code block to execute before the command has been
313
+ # started
314
+ # @since 0.4.0
315
+ def pre_execute(&block)
316
+ @hooks[:pre_execute] = block
332
317
  end
333
318
 
334
- # Output a line of text using +IO#puts+ of the output stream
335
- #
336
- # @param [String] text The text to write into the output stream
337
- # @since 0.2.0
338
- def puts(text)
339
- @settings[:ostream].puts text
319
+ # Defines a block of code used as a hook that should be executed before
320
+ # the application has been initialized
321
+ #
322
+ # @param [Proc] The code block to execute before the application has been
323
+ # initialized
324
+ # @since 0.4.0
325
+ def pre_init(&block)
326
+ @hooks[:pre_init] = block
340
327
  end
341
328
 
342
329
  # Displays a progress bar while the given block is executed
@@ -373,6 +360,32 @@ module Rubikon
373
360
  end
374
361
  end
375
362
 
363
+ # Output text using +IO#<<+ of the output stream
364
+ #
365
+ # @param [String] text The text to write into the output stream
366
+ # @since 0.2.0
367
+ def put(text)
368
+ @settings[:ostream] << text
369
+ @settings[:ostream].flush
370
+ end
371
+
372
+ # Output a character using +IO#putc+ of the output stream
373
+ #
374
+ # @param [String, Numeric] char The character to write into the output
375
+ # stream
376
+ # @since 0.2.0
377
+ def putc(char)
378
+ @settings[:ostream].putc char
379
+ end
380
+
381
+ # Output a line of text using +IO#puts+ of the output stream
382
+ #
383
+ # @param [String] text The text to write into the output stream
384
+ # @since 0.2.0
385
+ def puts(text)
386
+ @settings[:ostream].puts text
387
+ end
388
+
376
389
  # Sets an application setting
377
390
  #
378
391
  # @param [Symbol, #to_sym] setting The name of the setting to change
@@ -3,6 +3,9 @@
3
3
  #
4
4
  # Copyright (c) 2009-2010, Sebastian Staudt
5
5
 
6
+ require 'pathname'
7
+
8
+ require 'rubikon/application/sandbox'
6
9
  require 'rubikon/command'
7
10
  require 'rubikon/exceptions'
8
11
  require 'rubikon/flag'
@@ -22,8 +25,11 @@ module Rubikon
22
25
  # @since 0.2.0
23
26
  module InstanceMethods
24
27
 
25
- # @return [String] The absolute path of the application
26
- attr_reader :path
28
+ # @return [Parameter] The parameter that's currently executed
29
+ attr_accessor :current_param
30
+
31
+ # @return [Application::Sandbox] The sandbox this application runs in
32
+ attr_reader :sandbox
27
33
 
28
34
  # Initialize with default settings
29
35
  #
@@ -32,17 +38,18 @@ module Rubikon
32
38
  #
33
39
  # @see #set
34
40
  def initialize
35
- @commands = {}
36
- @current_command = nil
37
- @current_global_option = nil
38
- @global_parameters = {}
39
- @initialized = false
40
- @parameters = []
41
- @path = File.dirname($0)
42
- @settings = {
41
+ @commands = {}
42
+ @current_command = nil
43
+ @current_global_param = nil
44
+ @current_param = nil
45
+ @global_parameters = {}
46
+ @hooks = {}
47
+ @initialized = false
48
+ @parameters = []
49
+ @sandbox = Sandbox.new(self)
50
+ @settings = {
43
51
  :autorun => true,
44
52
  :help_as_default => true,
45
- :help_banner => "Usage: #{$0}",
46
53
  :istream => $stdin,
47
54
  :name => self.class.to_s,
48
55
  :ostream => $stdout,
@@ -61,22 +68,25 @@ module Rubikon
61
68
  # given to the application as options
62
69
  def run(args = ARGV)
63
70
  begin
64
- init unless @initialized
65
-
71
+ init
66
72
  command, parameters, args = parse_arguments(args)
67
73
 
68
74
  parameters.each do |parameter|
75
+ @current_global_param = parameter
69
76
  if parameter.is_a? Option
70
77
  parameter.check_args
71
- @current_global_option = parameter
72
78
  end
73
79
  parameter.active!
74
- @current_global_option = nil
80
+ @current_global_param = nil
75
81
  end
76
82
 
77
83
  @current_command = command
84
+ hook :pre_execute
78
85
  result = command.run(*args)
86
+ hook :post_execute
79
87
  @current_command = nil
88
+
89
+ reset
80
90
  result
81
91
  rescue
82
92
  raise $! if @settings[:raise_errors]
@@ -89,6 +99,22 @@ module Rubikon
89
99
 
90
100
  private
91
101
 
102
+ # Sets the (first) file this application has been defined in.
103
+ #
104
+ # This also sets the path of the application used to load external
105
+ # command code and the default banner for the help screen.
106
+ #
107
+ # @param [String] file The (first) file of the class definition
108
+ # @see DSLMethods#base_file
109
+ # @see DSLMethods#path
110
+ # @since 0.4.0
111
+ def base_file=(file)
112
+ @base_file = file
113
+ @path = File.dirname(file)
114
+
115
+ @settings[:help_banner] ||= "Usage: #{Pathname.new(file).relative_path_from(Pathname.new(Dir.getwd))}"
116
+ end
117
+
92
118
  # Defines a global Flag for enabling debug output
93
119
  #
94
120
  # This will define a Flag <tt>--debug</tt> (with alias <tt>-d</tt>) to
@@ -108,16 +134,20 @@ module Rubikon
108
134
  # This takes any defined commands and it's corresponding options and
109
135
  # descriptions and displays them in a user-friendly manner.
110
136
  def help_command
111
- command :help, 'Display this help screen' do
112
- put @settings[:help_banner]
137
+ commands = @commands
138
+ global_parameters = @global_parameters
139
+ settings = @settings
140
+
141
+ command :help, nil, 'Display this help screen' do
142
+ put settings[:help_banner]
113
143
 
114
144
  help = {}
115
- @commands.each_value do |command|
145
+ commands.each_value do |command|
116
146
  help[command.name.to_s] = command.description
117
147
  end
118
148
 
119
149
  global_params = ''
120
- @global_parameters.values.uniq.sort {|a,b| a.name.to_s <=> b.name.to_s }.each do |param|
150
+ global_parameters.values.uniq.sort {|a,b| a.name.to_s <=> b.name.to_s }.each do |param|
121
151
  global_params << ' ['
122
152
  ([param.name] + param.aliases).each_with_index do |name, index|
123
153
  name = name.to_s
@@ -137,7 +167,7 @@ module Rubikon
137
167
  puts "Without command: #{default_description}\n\n"
138
168
  end
139
169
 
140
- puts "Commands:"
170
+ puts 'Commands:'
141
171
  max_command_length = help.keys.max { |a, b| a.size <=> b.size }.size
142
172
  help.sort_by { |name, description| name }.each do |name, description|
143
173
  puts " #{name.ljust(max_command_length)} #{description}"
@@ -163,11 +193,27 @@ module Rubikon
163
193
  @settings[:ostream] = current_ostream
164
194
  end
165
195
 
196
+ # Executes the hook with the secified name
197
+ #
198
+ # @param [Symbol] name The name of the hook to execute
199
+ # @since 0.4.0
200
+ def hook(name)
201
+ @sandbox.instance_eval &@hooks[name] unless @hooks[name].nil?
202
+ end
203
+
166
204
  # This method is called once for each application and is used to
167
205
  # initialize anything that needs to be ready before the application is
168
206
  # run, but <em>after</em> the application is setup, i.e. after the user
169
207
  # has defined the application class.
170
208
  def init
209
+ return if @initialized
210
+
211
+ @current_command = nil
212
+ @current_param = nil
213
+ @current_global_param = nil
214
+
215
+ hook :pre_init
216
+
171
217
  debug_flag
172
218
  help_command
173
219
  verbose_flag
@@ -177,6 +223,34 @@ module Rubikon
177
223
  end
178
224
 
179
225
  @initialized = true
226
+
227
+ hook :post_init
228
+ end
229
+
230
+ # This is used to determine the receiver of a method call inside the
231
+ # application code.
232
+ #
233
+ # This is used to have a convenient way to access e.g. paramter
234
+ # arguments.
235
+ #
236
+ # This will delegate a method call to the currently executed parameter
237
+ # if the receiving object exists and responds to the desired method.
238
+ # Currently executed means the application's execution is inside a
239
+ # parameter's code block at the moment, i.e. a call to a missing method
240
+ # inside a parameter's code block will trigger this behavior.
241
+ #
242
+ # @example Access a command's arguments
243
+ # command :args, [:one, :two] do
244
+ # puts "One: #{one}, Two: #{two}"
245
+ # end
246
+ # @since 0.4.0
247
+ def method_missing(name, *args, &block)
248
+ receiver = @current_param || @current_global_param || @current_command
249
+ if receiver.nil? || !receiver.respond_to?(name)
250
+ super
251
+ else
252
+ receiver.send(name, *args, &block)
253
+ end
180
254
  end
181
255
 
182
256
  # Parses the command-line arguments given to the application by the
@@ -223,6 +297,18 @@ module Rubikon
223
297
  return command, parameters, args
224
298
  end
225
299
 
300
+ # Resets this application to its initial state
301
+ #
302
+ # @see Command#reset
303
+ # @see HasArguments#reset
304
+ # @see Parameter#reset
305
+ # @since 0.4.0
306
+ def reset
307
+ (@commands.values + @global_parameters.values).uniq.each do |param|
308
+ param.reset
309
+ end
310
+ end
311
+
226
312
  # Defines a global Flag for enabling verbose output
227
313
  #
228
314
  # This will define a Flag <tt>--verbose</tt> and <tt>-v</tt> to enable