commander 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+
2
+ require 'erb'
3
+
4
+ module Commander
5
+ module HelpFormatter
6
+ class Terminal < Base
7
+ def render
8
+ template(:help).result @runner.get_binding
9
+ end
10
+
11
+ def render_command command
12
+ template(:command_help).result command.get_binding
13
+ end
14
+
15
+ def template name
16
+ ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal', "#{name}.erb")), nil, '-')
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+
2
+ <%= $terminal.color "NAME", :bold %>:
3
+
4
+ <%= @name %>
5
+
6
+ <%= $terminal.color "DESCRIPTION", :bold %>:
7
+
8
+ <%= @description || @summary || 'No description.' -%>
9
+
10
+ <% if @syntax -%>
11
+
12
+ <%= $terminal.color "SYNOPSIS", :bold %>:
13
+
14
+ <%= @syntax -%>
15
+
16
+ <% end -%>
17
+ <% unless @examples.empty? -%>
18
+
19
+ <%= $terminal.color "EXAMPLES", :bold %>:
20
+ <% for description, command in @examples -%>
21
+
22
+ # <%= description %>
23
+ <%= command %>
24
+ <% end -%>
25
+ <% end -%>
26
+ <% unless @options.empty? -%>
27
+
28
+ <%= $terminal.color "OPTIONS", :bold %>:
29
+ <% for option in @options -%>
30
+
31
+ <%= option[:switches].join ', ' %>
32
+ <%= option[:description] %>
33
+ <% end -%>
34
+ <% end -%>
35
+
@@ -0,0 +1,36 @@
1
+ <%= $terminal.color "NAME", :bold %>:
2
+
3
+ <%= program :name %>
4
+
5
+ <%= $terminal.color "DESCRIPTION", :bold %>:
6
+
7
+ <%= program :description %>
8
+
9
+ <%= $terminal.color "COMMANDS", :bold %>:
10
+ <% for name, command in @commands.sort -%>
11
+ <% unless alias? name %>
12
+ <%= "%-20s %s" % [command.name, command.summary || command.description] -%>
13
+ <% end -%>
14
+ <% end %>
15
+ <% unless @aliases.empty? %>
16
+ <%= $terminal.color "ALIASES", :bold %>:
17
+ <% for alias_name, args in @aliases.sort %>
18
+ <%= "%-20s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] -%>
19
+ <% end %>
20
+ <% end %>
21
+ <% unless @options.empty? -%>
22
+ <%= $terminal.color "GLOBAL OPTIONS", :bold %>:
23
+ <% for option in @options -%>
24
+
25
+ <%= option[:switches].join ', ' %>
26
+ <%= option[:description] %>
27
+ <% end -%>
28
+ <% end -%>
29
+ <% if program :help -%>
30
+ <% for title, body in program(:help) %>
31
+ <%= $terminal.color title.to_s.upcase, :bold %>:
32
+
33
+ <%= body %>
34
+ <% end -%>
35
+ <% end -%>
36
+
@@ -0,0 +1,12 @@
1
+
2
+ require 'erb'
3
+
4
+ module Commander
5
+ module HelpFormatter
6
+ class TerminalCompact < Terminal
7
+ def template name
8
+ ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal_compact', "#{name}.erb")), nil, '-')
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,27 @@
1
+
2
+ <%= @name %>
3
+ <% if @description || @summary -%>
4
+
5
+ <%= @description || @summary %>
6
+ <% end -%>
7
+ <% if @syntax -%>
8
+
9
+ Usage: <%= @syntax %>
10
+ <% end -%>
11
+ <% unless @examples.empty? -%>
12
+
13
+ Examples:
14
+ <% for description, command in @examples -%>
15
+
16
+ # <%= description %>
17
+ <%= command %>
18
+ <% end -%>
19
+ <% end -%>
20
+ <% unless @options.empty? -%>
21
+
22
+ Options:
23
+ <% for option in @options -%>
24
+ <%= "%-20s %s" % [option[:switches].join(', '), option[:description]] %>
25
+ <% end -%>
26
+ <% end -%>
27
+
@@ -0,0 +1,29 @@
1
+ <%= program :name %>
2
+
3
+ <%= program :description %>
4
+
5
+ Commands:
6
+ <% for name, command in @commands.sort -%>
7
+ <% unless alias? name -%>
8
+ <%= "%-20s %s" % [command.name, command.summary || command.description] %>
9
+ <% end -%>
10
+ <% end -%>
11
+ <% unless @aliases.empty? %>
12
+ Aliases:
13
+ <% for alias_name, args in @aliases.sort -%>
14
+ <%= "%-20s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] %>
15
+ <% end -%>
16
+ <% end %>
17
+ <% unless @options.empty? -%>
18
+ Global Options:
19
+ <% for option in @options -%>
20
+ <%= "%-20s %s" % [option[:switches].join(', '), option[:description]] -%>
21
+ <% end -%>
22
+ <% end -%>
23
+ <% if program :help -%>
24
+ <% for title, body in program(:help) %>
25
+ <%= title %>:
26
+ <%= body %>
27
+ <% end %>
28
+ <% end -%>
29
+
@@ -0,0 +1,10 @@
1
+
2
+ require 'commander'
3
+ require 'commander/delegates'
4
+
5
+ include Commander::UI
6
+ include Commander::UI::AskForClass
7
+ include Commander::Delegates
8
+
9
+ $terminal.wrap_at = HighLine::SystemExtensions.terminal_size.first - 5 rescue 80 if $stdin.tty?
10
+ at_exit { run! }
@@ -0,0 +1,386 @@
1
+
2
+ require 'optparse'
3
+
4
+ module Commander
5
+ class Runner
6
+
7
+ #--
8
+ # Exceptions
9
+ #++
10
+
11
+ class CommandError < StandardError; end
12
+ class InvalidCommandError < CommandError; end
13
+
14
+ ##
15
+ # Array of commands.
16
+
17
+ attr_reader :commands
18
+
19
+ ##
20
+ # Global options.
21
+
22
+ attr_reader :options
23
+
24
+ ##
25
+ # Hash of help formatter aliases.
26
+
27
+ attr_reader :help_formatter_aliases
28
+
29
+ ##
30
+ # Initialize a new command runner. Optionally
31
+ # supplying _args_ for mocking, or arbitrary usage.
32
+
33
+ def initialize args = ARGV
34
+ @args, @commands, @aliases, @options = args, {}, {}, []
35
+ @help_formatter_aliases = help_formatter_alias_defaults
36
+ @program = program_defaults
37
+ create_default_commands
38
+ end
39
+
40
+ ##
41
+ # Return singleton Runner instance.
42
+
43
+ def self.instance
44
+ @singleton ||= new
45
+ end
46
+
47
+ ##
48
+ # Run command parsing and execution process.
49
+
50
+ def run!
51
+ trace = false
52
+ require_program :version, :description
53
+ trap('INT') { abort program(:int_message) } if program(:int_message)
54
+ global_option('-h', '--help', 'Display help documentation') { command(:help).run *@args[1..-1]; return }
55
+ global_option('-v', '--version', 'Display version information') { say version; return }
56
+ global_option('-t', '--trace', 'Display backtrace when an error occurs') { trace = true }
57
+ parse_global_options
58
+ remove_global_options options, @args
59
+ unless trace
60
+ begin
61
+ run_active_command
62
+ rescue InvalidCommandError => e
63
+ abort "#{e}. Use --help for more information"
64
+ rescue \
65
+ OptionParser::InvalidOption,
66
+ OptionParser::InvalidArgument,
67
+ OptionParser::MissingArgument => e
68
+ abort e
69
+ rescue => e
70
+ abort "error: #{e}. Use --trace to view backtrace"
71
+ end
72
+ else
73
+ run_active_command
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Return program version.
79
+
80
+ def version
81
+ '%s %s' % [program(:name), program(:version)]
82
+ end
83
+
84
+ ##
85
+ # Assign program information.
86
+ #
87
+ # === Examples
88
+ #
89
+ # # Set data
90
+ # program :name, 'Commander'
91
+ # program :version, Commander::VERSION
92
+ # program :description, 'Commander utility program.'
93
+ # program :help, 'Copyright', '2008 TJ Holowaychuk'
94
+ # program :help, 'Anything', 'You want'
95
+ # program :int_message 'Bye bye!'
96
+ # program :help_formatter, :compact
97
+ # program :help_formatter, Commander::HelpFormatter::TerminalCompact
98
+ #
99
+ # # Get data
100
+ # program :name # => 'Commander'
101
+ #
102
+ # === Keys
103
+ #
104
+ # :version (required) Program version triple, ex: '0.0.1'
105
+ # :description (required) Program description
106
+ # :name Program name, defaults to basename of executable
107
+ # :help_formatter Defaults to Commander::HelpFormatter::Terminal
108
+ # :help Allows addition of arbitrary global help blocks
109
+ # :int_message Message to display when interrupted (CTRL + C)
110
+ #
111
+
112
+ def program key, *args
113
+ if key == :help and !args.empty?
114
+ @program[:help] ||= {}
115
+ @program[:help][args.first] = args.at(1)
116
+ elsif key == :help_formatter && !args.empty?
117
+ @program[key] = (@help_formatter_aliases[args.first] || args.first)
118
+ else
119
+ @program[key] = *args unless args.empty?
120
+ @program[key]
121
+ end
122
+ end
123
+
124
+ ##
125
+ # Creates and yields a command instance when a block is passed.
126
+ # Otherwise attempts to return the command, raising InvalidCommandError when
127
+ # it does not exist.
128
+ #
129
+ # === Examples
130
+ #
131
+ # command :my_command do |c|
132
+ # c.when_called do |args|
133
+ # # Code
134
+ # end
135
+ # end
136
+ #
137
+
138
+ def command name, &block
139
+ yield add_command(Commander::Command.new(name)) if block
140
+ @commands[name.to_s]
141
+ end
142
+
143
+ ##
144
+ # Add a global option; follows the same syntax as Command#option
145
+ # This would be used for switches such as --version, --trace, etc.
146
+
147
+ def global_option *args, &block
148
+ switches, description = Runner.separate_switches_from_description *args
149
+ @options << {
150
+ :args => args,
151
+ :proc => block,
152
+ :switches => switches,
153
+ :description => description,
154
+ }
155
+ end
156
+
157
+ ##
158
+ # Alias command _name_ with _alias_name_. Optionally _args_ may be passed
159
+ # as if they were being passed straight to the original command via the command-line.
160
+
161
+ def alias_command alias_name, name, *args
162
+ @commands[alias_name.to_s] = command name
163
+ @aliases[alias_name.to_s] = args
164
+ end
165
+
166
+ ##
167
+ # Default command _name_ to be used when no other
168
+ # command is found in the arguments.
169
+
170
+ def default_command name
171
+ @default_command = name
172
+ end
173
+
174
+ ##
175
+ # Add a command object to this runner.
176
+
177
+ def add_command command
178
+ @commands[command.name] = command
179
+ end
180
+
181
+ ##
182
+ # Check if command _name_ is an alias.
183
+
184
+ def alias? name
185
+ @aliases.include? name.to_s
186
+ end
187
+
188
+ ##
189
+ # Check if a command _name_ exists.
190
+
191
+ def command_exists? name
192
+ @commands[name.to_s]
193
+ end
194
+
195
+ #:stopdoc:
196
+
197
+ ##
198
+ # Get active command within arguments passed to this runner.
199
+
200
+ def active_command
201
+ @__active_command ||= command(command_name_from_args)
202
+ end
203
+
204
+ ##
205
+ # Attempts to locate a command name from within the arguments.
206
+ # Supports multi-word commands, using the largest possible match.
207
+
208
+ def command_name_from_args
209
+ @__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command)
210
+ end
211
+
212
+ ##
213
+ # Returns array of valid command names found within _args_.
214
+
215
+ def valid_command_names_from *args
216
+ arg_string = args.delete_if { |value| value =~ /^-/ }.join ' '
217
+ commands.keys.find_all { |name| name if /^#{name}/.match arg_string }
218
+ end
219
+
220
+ ##
221
+ # Help formatter instance.
222
+
223
+ def help_formatter
224
+ @__help_formatter ||= program(:help_formatter).new self
225
+ end
226
+
227
+ ##
228
+ # Return arguments without the command name.
229
+
230
+ def args_without_command_name
231
+ removed = []
232
+ parts = command_name_from_args.split rescue []
233
+ @args.dup.delete_if do |arg|
234
+ removed << arg if parts.include?(arg) and not removed.include?(arg)
235
+ end
236
+ end
237
+
238
+ ##
239
+ # Returns hash of help formatter alias defaults.
240
+
241
+ def help_formatter_alias_defaults
242
+ return :compact => HelpFormatter::TerminalCompact
243
+ end
244
+
245
+ ##
246
+ # Returns hash of program defaults.
247
+
248
+ def program_defaults
249
+ return :help_formatter => HelpFormatter::Terminal,
250
+ :name => File.basename($0)
251
+ end
252
+
253
+ ##
254
+ # Creates default commands such as 'help' which is
255
+ # essentially the same as using the --help switch.
256
+
257
+ def create_default_commands
258
+ command :help do |c|
259
+ c.syntax = 'commander help [command]'
260
+ c.description = 'Display global or [command] help documentation.'
261
+ c.example 'Display global help', 'command help'
262
+ c.example "Display help for 'foo'", 'command help foo'
263
+ c.when_called do |args, options|
264
+ enable_paging
265
+ if args.empty?
266
+ say help_formatter.render
267
+ else
268
+ command = command args.join(' ')
269
+ require_valid_command command
270
+ say help_formatter.render_command(command)
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ ##
277
+ # Raises InvalidCommandError when a _command_ is not found.
278
+
279
+ def require_valid_command command = active_command
280
+ raise InvalidCommandError, 'invalid command', caller if command.nil?
281
+ end
282
+
283
+ ##
284
+ # Removes global _options_ from _args_. This prevents an invalid
285
+ # option error from occurring when options are parsed
286
+ # again for the command.
287
+
288
+ def remove_global_options options, args
289
+ # TODO: refactor with flipflop, please TJ ! have time to refactor me !
290
+ options.each do |option|
291
+ switches = option[:switches]
292
+ past_switch, arg_removed = false, false
293
+ args.delete_if do |arg|
294
+ # TODO: clean this up, no rescuing ;)
295
+ if switches.any? { |switch| switch.match(/^#{arg}/) rescue false }
296
+ past_switch, arg_removed = true, false
297
+ true
298
+ elsif past_switch && !arg_removed && arg !~ /^-/
299
+ arg_removed = true
300
+ else
301
+ arg_removed = true
302
+ false
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ ##
309
+ # Parse global command options.
310
+
311
+ def parse_global_options
312
+ options.inject OptionParser.new do |options, option|
313
+ options.on *option[:args], &global_option_proc(option[:switches], &option[:proc])
314
+ end.parse! @args.dup
315
+ rescue OptionParser::InvalidOption
316
+ # Ignore invalid options since options will be further
317
+ # parsed by our sub commands.
318
+ end
319
+
320
+ ##
321
+ # Returns a proc allowing for commands to inherit global options.
322
+ # This functionality works whether a block is present for the global
323
+ # option or not, so simple switches such as --verbose can be used
324
+ # without a block, and used throughout all commands.
325
+
326
+ def global_option_proc switches, &block
327
+ lambda do |value|
328
+ unless active_command.nil?
329
+ active_command.proxy_options << [Runner.switch_to_sym(switches.last), value]
330
+ end
331
+ yield value if block and !value.nil?
332
+ end
333
+ end
334
+
335
+ ##
336
+ # Raises a CommandError when the program any of the _keys_ are not present, or empty.
337
+
338
+ def require_program *keys
339
+ keys.each do |key|
340
+ raise CommandError, "program #{key} required" if program(key).nil? or program(key).empty?
341
+ end
342
+ end
343
+
344
+ ##
345
+ # Return switches and description separated from the _args_ passed.
346
+
347
+ def self.separate_switches_from_description *args
348
+ switches = args.find_all { |arg| arg.to_s =~ /^-/ }
349
+ description = args.last unless !args.last.is_a? String or args.last.match(/^-/)
350
+ return switches, description
351
+ end
352
+
353
+ ##
354
+ # Attempts to generate a method name symbol from +switch+.
355
+ # For example:
356
+ #
357
+ # -h # => :h
358
+ # --trace # => :trace
359
+ # --some-switch # => :some_switch
360
+ # --[no-]feature # => :feature
361
+ # --file FILE # => :file
362
+ # --list of,things # => :list
363
+ #
364
+
365
+ def self.switch_to_sym switch
366
+ switch.scan(/[\-\]](\w+)/).join('_').to_sym rescue nil
367
+ end
368
+
369
+ ##
370
+ # Run the active command.
371
+
372
+ def run_active_command
373
+ require_valid_command
374
+ if alias? command_name_from_args
375
+ active_command.run *(@aliases[command_name_from_args.to_s] + args_without_command_name)
376
+ else
377
+ active_command.run *args_without_command_name
378
+ end
379
+ end
380
+
381
+ def say *args #:nodoc:
382
+ $terminal.say *args
383
+ end
384
+
385
+ end
386
+ end