rubikon 0.3.0 → 0.4.0

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