rubikon 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,12 @@
1
- # This code is free software; you can redistribute it and/or modify it under the
2
- # terms of the new BSD License.
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2009, Sebastian Staudt
4
+ # Copyright (c) 2009-2010, Sebastian Staudt
5
5
 
6
+ require 'rubikon/command'
7
+ require 'rubikon/exceptions'
8
+ require 'rubikon/flag'
9
+ require 'rubikon/option'
6
10
  require 'rubikon/progress_bar'
7
11
  require 'rubikon/throbber'
8
12
 
@@ -10,183 +14,70 @@ module Rubikon
10
14
 
11
15
  module Application
12
16
 
17
+ # This module contains internal instance methods of +Application::Base+ and
18
+ # its subclasses.
19
+ #
20
+ # @author Sebastian Staudt
21
+ # @see Application::Base
22
+ # @since 0.2.0
13
23
  module InstanceMethods
14
24
 
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
25
+ # @return [String] The absolute path of the application
26
+ attr_reader :path
146
27
 
147
- # Output text using +IO#<<+ of the output stream
28
+ # Initialize with default settings
148
29
  #
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
30
+ # If you really need to override this in your application class, be sure
31
+ # to call +super+
156
32
  #
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
33
+ # @see #set
34
+ 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 = {
43
+ :autorun => true,
44
+ :help_as_default => true,
45
+ :help_banner => "Usage: #{$0}",
46
+ :istream => $stdin,
47
+ :name => self.class.to_s,
48
+ :ostream => $stdout,
49
+ :raise_errors => false
50
+ }
167
51
  end
168
52
 
169
53
  # Run this application
170
54
  #
171
- # +args+:: The command line arguments that should be given to the
172
- # application as options
55
+ # Calling this method explicitly is not required when you want to create
56
+ # a simple application (having one main class inheriting from
57
+ # Rubikon::Application). But it's useful for testing or if you want to
58
+ # have some sort of sub-applications.
173
59
  #
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.
60
+ # @param [Array<String>] args The command line arguments that should be
61
+ # given to the application as options
178
62
  def run(args = ARGV)
179
- init unless @initialized
180
- action_results = []
181
-
182
63
  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)
64
+ init unless @initialized
65
+
66
+ command, parameters, args = parse_arguments(args)
67
+
68
+ parameters.each do |parameter|
69
+ if parameter.is_a? Option
70
+ parameter.check_args
71
+ @current_global_option = parameter
188
72
  end
73
+ parameter.active!
74
+ @current_global_option = nil
189
75
  end
76
+
77
+ @current_command = command
78
+ result = command.run(*args)
79
+ @current_command = nil
80
+ result
190
81
  rescue
191
82
  raise $! if @settings[:raise_errors]
192
83
 
@@ -194,96 +85,62 @@ module Rubikon
194
85
  puts " #{$!.backtrace.join("\n ")}" if $DEBUG
195
86
  exit 1
196
87
  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
88
  end
244
89
 
245
90
  private
246
91
 
247
- # Assigns aliases to the actions that have been defined using action_alias
92
+ # Defines a global Flag for enabling debug output
248
93
  #
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
94
+ # This will define a Flag <tt>--debug</tt> (with alias <tt>-d</tt>) to
95
+ # enable debug output.
96
+ # Using it sets Ruby's global variable <tt>$DEBUG</tt> to +true+.
97
+ #
98
+ # @return [Flag]
99
+ def debug_flag
100
+ global_flag :debug do
101
+ $DEBUG = true
262
102
  end
103
+ global_flag :d => :debug
263
104
  end
264
105
 
265
- # Defines an action for displaying a help screen
106
+ # Defines a command for displaying a help screen
266
107
  #
267
- # This takes any defined action and it's corresponding options and
108
+ # This takes any defined commands and it's corresponding options and
268
109
  # descriptions and displays them in a user-friendly manner.
269
- def help_action
270
- action 'help', { :description => 'Display this help screen' } do
110
+ def help_command
111
+ command :help, 'Display this help screen' do
112
+ put @settings[:help_banner]
113
+
271
114
  help = {}
272
- @actions.each do |option, action|
273
- help[action] = [] if help[action].nil?
274
- help[action] << option.to_s
115
+ @commands.each_value do |command|
116
+ help[command.name.to_s] = command.description
275
117
  end
276
118
 
277
- put @settings[:help_banner]
278
- puts " [options]" unless @default.nil?
279
- puts ''
119
+ global_params = ''
120
+ @global_parameters.values.uniq.sort {|a,b| a.name.to_s <=> b.name.to_s }.each do |param|
121
+ global_params << ' ['
122
+ ([param.name] + param.aliases).each_with_index do |name, index|
123
+ name = name.to_s
124
+ global_params << '|' if index > 0
125
+ global_params << '-' if name.size > 1
126
+ global_params << "-#{name}"
127
+ end
128
+ global_params << ' ...' if param.is_a?(Option)
129
+ global_params << ']'
130
+ end
280
131
 
281
- help.each do |action, options|
282
- help[action] = options.sort.join(', ')
132
+ default_description = help.delete('__default')
133
+ if default_description.nil?
134
+ puts "#{global_params} command [args]\n\n"
135
+ else
136
+ puts "#{global_params} [command] [args]\n\n"
137
+ puts "Without command: #{default_description}\n\n"
283
138
  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
139
+
140
+ puts "Commands:"
141
+ max_command_length = help.keys.max { |a, b| a.size <=> b.size }.size
142
+ help.sort_by { |name, description| name }.each do |name, description|
143
+ puts " #{name.ljust(max_command_length)} #{description}"
287
144
  end
288
145
  end
289
146
  end
@@ -291,7 +148,8 @@ module Rubikon
291
148
  # Hide output inside the given block and print it after the block has
292
149
  # finished
293
150
  #
294
- # +block+:: The block that should not print output while it's running
151
+ # @param [Proc] block The block that should not print output while it's
152
+ # running
295
153
  #
296
154
  # If the block needs to print to the real IO stream, it can access it
297
155
  # using its first parameter.
@@ -310,32 +168,73 @@ module Rubikon
310
168
  # run, but <em>after</em> the application is setup, i.e. after the user
311
169
  # has defined the application class.
312
170
  def init
313
- help_action
314
- assign_aliases
171
+ debug_flag
172
+ help_command
173
+ verbose_flag
174
+
175
+ if @settings[:help_as_default] && !@commands.keys.include?(:__default)
176
+ default :help
177
+ end
178
+
315
179
  @initialized = true
316
180
  end
317
181
 
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)
182
+ # Parses the command-line arguments given to the application by the
183
+ # user. This distinguishes between commands, global flags and command
184
+ # flags
185
+ #
186
+ # @param [Array] args The command-line arguments
187
+ # @return [Command, Array<Symbol>, Array] The command to execute, the
188
+ # parameters of this command that have been supplied and any
189
+ # additional command-line arguments supplied
190
+ def parse_arguments(args)
191
+ command_arg = args.shift
192
+ if command_arg.nil? || command_arg.start_with?('-')
193
+ command = @commands[:__default]
194
+ args.unshift(command_arg) unless command_arg.nil?
195
+ raise NoDefaultCommandError if command.nil?
196
+ else
197
+ command = @commands[command_arg.to_sym]
198
+ raise UnknownCommandError.new(command_arg) if command.nil?
199
+ end
200
+
201
+ parameter = nil
202
+ parameters = []
203
+ args.dup.each do |arg|
204
+ if arg.start_with?('--')
205
+ parameter = @global_parameters[arg[2..-1].to_sym]
206
+ elsif arg.start_with?('-')
207
+ parameter = @global_parameters[arg[1..-1].to_sym]
333
208
  else
334
- actions_to_call[last_action] << option
209
+ if !parameter.nil? && parameter.more_args?
210
+ parameter.args << args.delete(arg)
211
+ else
212
+ parameter = nil
213
+ end
214
+ next
215
+ end
216
+
217
+ unless parameter.nil?
218
+ parameters << parameter
219
+ args.delete(arg)
335
220
  end
336
221
  end
337
222
 
338
- actions_to_call
223
+ return command, parameters, args
224
+ end
225
+
226
+ # Defines a global Flag for enabling verbose output
227
+ #
228
+ # This will define a Flag <tt>--verbose</tt> and <tt>-v</tt> to enable
229
+ # verbose output.
230
+ # Using it sets Ruby's global variable <tt>$VERBOSE</tt> to +true+.
231
+ #
232
+ # @return [Flag] The debug Flag object
233
+ def verbose_flag
234
+ global_flag :verbose do
235
+ $VERBOSE = true
236
+ end
237
+ global_flag :v => :verbose
339
238
  end
340
239
 
341
240
  end
@@ -0,0 +1,135 @@
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
+ require 'rubikon/application/base'
7
+ require 'rubikon/exceptions'
8
+ require 'rubikon/parameter'
9
+
10
+ module Rubikon
11
+
12
+ # Instances of the Command class are used to define the real code that should
13
+ # be executed when running the Application.
14
+ #
15
+ # @author Sebastian Staudt
16
+ # @since 0.3.0
17
+ class Command
18
+
19
+ include Parameter
20
+
21
+ attr_accessor :description
22
+ attr_reader :args, :params
23
+ alias_method :arguments, :args
24
+ alias_method :parameters, :params
25
+
26
+ # Create a new application command with the given name with a reference to
27
+ # the app it belongs to
28
+ #
29
+ # @param [Application::Base] app A reference to the application
30
+ # instance this command belongs to
31
+ # @param [#to_sym] name The name of this command, used in application
32
+ # arguments
33
+ # @param [Proc] block The code block which should be executed by this
34
+ # command
35
+ # @raise [ArgumentError] if the given application object isn't a Rubikon
36
+ # application
37
+ # @raise [BlockMissingError] if no command code block is given and a
38
+ # command file does not exist
39
+ def initialize(app, name, &block)
40
+ raise ArgumentError unless app.is_a? Application::Base
41
+ super(name, nil)
42
+
43
+ @app = app
44
+ @params = {}
45
+
46
+ if block_given?
47
+ @block = block
48
+ else
49
+ @file_name = "#{@app.path}/commands/#{name}.rb"
50
+ raise BlockMissingError unless File.exists?(@file_name)
51
+ code = open(@file_name).read
52
+ @block = Proc.new { instance_eval(code) }
53
+ end
54
+ end
55
+
56
+ # Add a new Parameter for this command
57
+ #
58
+ # @param [Parameter, Hash] parameter The parameter to add to this
59
+ # command. This might also be a Hash where every key will be an
60
+ # alias to the corresponding value, e.g. <tt>{ :alias => :parameter
61
+ # }</tt>.
62
+ # @see Parameter
63
+ def <<(parameter)
64
+ if parameter.is_a? Hash
65
+ parameter.each do |alias_name, name|
66
+ alias_name = alias_name.to_sym
67
+ name = name.to_sym
68
+ parameter = @params[name]
69
+ if parameter.nil?
70
+ @params[alias_name] = name
71
+ else
72
+ parameter.aliases << alias_name
73
+ @params[alias_name] = parameter
74
+ end
75
+ end
76
+ else
77
+ raise ArgumentError unless parameter.is_a? Parameter
78
+ @params.each do |name, param|
79
+ if param == parameter.name
80
+ parameter.aliases << name
81
+ end
82
+ end
83
+ @params[parameter.name] = parameter
84
+ end
85
+ end
86
+
87
+ # Parses the arguments of this command and sets each Parameter as active
88
+ # if it has been supplied by the user on the command-line. Additional
89
+ # arguments are passed to the individual parameters.
90
+ #
91
+ # @param [Array<String>] args The arguments that have been passed to this
92
+ # command
93
+ # @raise [UnknownParameterError] if an undefined parameter is passed to the
94
+ # command
95
+ # @see Flag
96
+ # @see Option
97
+ def parse_arguments(args)
98
+ @args = []
99
+ parameter = nil
100
+ args.each do |arg|
101
+ if arg.start_with?('-')
102
+ parameter_name = arg.start_with?('--') ? arg[2..-1] : arg[1..-1]
103
+ parameter = @params[parameter_name.to_sym]
104
+ raise UnknownParameterError.new(arg) if parameter.nil?
105
+ end
106
+
107
+ unless parameter.nil? || parameter.active?
108
+ parameter.active!
109
+ next
110
+ end
111
+
112
+ if parameter.nil? || !parameter.more_args?
113
+ @args << arg
114
+ else
115
+ parameter << arg
116
+ end
117
+ end
118
+
119
+ @params.values.each do |param|
120
+ param.check_args if param.is_a?(Option) && param.active?
121
+ end
122
+ end
123
+
124
+ # Run this command's code block
125
+ #
126
+ # @param [Array<String>] args The arguments that have been passed to this
127
+ # command
128
+ def run(*args)
129
+ parse_arguments(args)
130
+ @app.instance_eval(&@block)
131
+ end
132
+
133
+ end
134
+
135
+ end