commander 3.3.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.
@@ -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