cmdparse 2.0.6 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/COPYING +21 -674
- data/README.md +29 -26
- data/Rakefile +58 -94
- data/VERSION +1 -1
- data/doc/api.api +5 -0
- data/doc/api.template +24 -0
- data/doc/default.scss +1987 -0
- data/doc/default.template +47 -26
- data/doc/images/bg01.png +0 -0
- data/doc/images/bg02.png +0 -0
- data/doc/index.page +81 -74
- data/doc/{download.page → installation.page} +13 -14
- data/doc/metainfo +14 -0
- data/doc/news.page +113 -0
- data/doc/tutorial.page +211 -115
- data/doc/virtual +2 -5
- data/example/net.rb +85 -0
- data/lib/cmdparse.rb +675 -284
- data/webgen.config +22 -0
- metadata +39 -24
- data/COPYING.LESSER +0 -165
- data/doc/about.page +0 -24
- data/doc/default.css +0 -152
- data/doc/logo.png +0 -0
- data/lib/cmdparse/wrappers/optparse.rb +0 -63
- data/net.rb +0 -88
- data/test/tc_commandhash.rb +0 -96
data/lib/cmdparse.rb
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
#
|
2
2
|
#--
|
3
3
|
# cmdparse: advanced command line parser supporting commands
|
4
|
-
# Copyright (C) 2004-
|
5
|
-
#
|
6
|
-
# This file is part of cmdparse.
|
7
|
-
#
|
8
|
-
# cmdparse is free software: you can redistribute it and/or modify it under the terms of the GNU
|
9
|
-
# Lesser General Public License as published by the Free Software Foundation, either version 3 of
|
10
|
-
# the License, or (at your option) any later version.
|
11
|
-
#
|
12
|
-
# cmdparse is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
13
|
-
# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
14
|
-
# General Public License for more details.
|
15
|
-
#
|
16
|
-
# You should have received a copy of the GNU Lesser General Public License along with cmdparse. If
|
17
|
-
# not, see <http://www.gnu.org/licenses/>.
|
4
|
+
# Copyright (C) 2004-2015 Thomas Leitner
|
18
5
|
#
|
6
|
+
# This file is part of cmdparse which is licensed under the MIT.
|
19
7
|
#++
|
20
8
|
#
|
21
9
|
|
22
|
-
|
10
|
+
require 'optparse'
|
11
|
+
|
12
|
+
OptionParser::Officious.delete('version')
|
13
|
+
OptionParser::Officious.delete('help')
|
14
|
+
|
15
|
+
# Namespace module for cmdparse.
|
16
|
+
#
|
17
|
+
# See CmdParse::CommandParser and CmdParse::Command for the two important classes.
|
23
18
|
module CmdParse
|
24
19
|
|
25
20
|
# The version of this cmdparse implemention
|
26
|
-
VERSION =
|
21
|
+
VERSION = '3.0.0'
|
27
22
|
|
28
23
|
|
29
24
|
# Base class for all cmdparse errors.
|
30
|
-
class ParseError <
|
25
|
+
class ParseError < StandardError
|
26
|
+
|
27
|
+
# Sets the error reason for the subclass.
|
28
|
+
def self.reason(reason)
|
29
|
+
@reason = reason
|
30
|
+
end
|
31
31
|
|
32
|
-
#
|
33
|
-
def self.
|
34
|
-
|
32
|
+
# Returns the error reason or 'CmdParse error' if it has not been set.
|
33
|
+
def self.get_reason
|
34
|
+
@reason ||= 'CmdParse error'
|
35
35
|
end
|
36
36
|
|
37
|
-
#
|
37
|
+
# Returns the reason plus the original message.
|
38
38
|
def message
|
39
|
-
|
40
|
-
|
39
|
+
str = super
|
40
|
+
self.class.get_reason + (str.empty? ? "" : ": #{str}")
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -59,418 +59,809 @@ module CmdParse
|
|
59
59
|
|
60
60
|
# This error is thrown when no command was given and no default command was specified.
|
61
61
|
class NoCommandGivenError < ParseError
|
62
|
-
reason 'No command given'
|
62
|
+
reason 'No command given'
|
63
|
+
|
64
|
+
def initialize #:nodoc:
|
65
|
+
super('')
|
66
|
+
end
|
63
67
|
end
|
64
68
|
|
65
69
|
# This error is thrown when a command is added to another command which does not support commands.
|
66
70
|
class TakesNoCommandError < ParseError
|
67
|
-
reason 'This command takes no other commands'
|
71
|
+
reason 'This command takes no other commands'
|
68
72
|
end
|
69
73
|
|
74
|
+
# This error is thrown when not enough arguments are provided for the command.
|
75
|
+
class NotEnoughArgumentsError < ParseError
|
76
|
+
reason 'Not enough arguments provided, minimum is'
|
77
|
+
end
|
78
|
+
|
79
|
+
# Command Hash - will return partial key matches as well if there is a single non-ambigous
|
80
|
+
# matching key
|
81
|
+
class CommandHash < Hash #:nodoc:
|
82
|
+
|
83
|
+
def key?(name) #:nodoc:
|
84
|
+
!self[name].nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
def [](cmd_name) #:nodoc:
|
88
|
+
super or begin
|
89
|
+
possible = keys.select {|key| key[0, cmd_name.length] == cmd_name }
|
90
|
+
fetch(possible[0]) if possible.size == 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
70
95
|
|
71
|
-
#
|
72
|
-
|
96
|
+
# Container for multiple OptionParser::List objects.
|
97
|
+
#
|
98
|
+
# This is needed for providing what's equivalent to stacked OptionParser instances and the global
|
99
|
+
# options implementation.
|
100
|
+
class MultiList #:nodoc:
|
73
101
|
|
74
|
-
|
75
|
-
|
76
|
-
yield @instance if block_given?
|
77
|
-
@instance
|
102
|
+
def initialize(list) #:nodoc:
|
103
|
+
@list = list
|
78
104
|
end
|
79
105
|
|
80
|
-
|
81
|
-
|
82
|
-
def order(args)
|
83
|
-
raise InvalidOptionError.new(args[0]) if args[0] =~ /^-/
|
84
|
-
args
|
106
|
+
def summarize(*args, &block) #:nodoc:
|
107
|
+
# We don't want summary information of the global options to automatically appear.
|
85
108
|
end
|
86
109
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
110
|
+
[:accept, :reject, :prepend, :append].each do |mname|
|
111
|
+
module_eval <<-EOF
|
112
|
+
def #{mname}(*args, &block)
|
113
|
+
@list[-1].#{mname}(*args, &block)
|
114
|
+
end
|
115
|
+
EOF
|
92
116
|
end
|
93
117
|
|
94
|
-
|
95
|
-
|
96
|
-
|
118
|
+
[:search, :complete, :each_option, :add_banner, :compsys].each do |mname|
|
119
|
+
module_eval <<-EOF
|
120
|
+
def #{mname}(*args, &block) #:nodoc:
|
121
|
+
@list.reverse_each {|list| list.#{mname}(*args, &block)}
|
122
|
+
end
|
123
|
+
EOF
|
97
124
|
end
|
98
125
|
|
99
126
|
end
|
100
127
|
|
101
|
-
#
|
102
|
-
|
128
|
+
# Extension for OptionParser objects to allow access to some internals.
|
129
|
+
class ::OptionParser #:nodoc:
|
103
130
|
|
104
|
-
|
105
|
-
|
106
|
-
class CommandHash < Hash
|
131
|
+
# Access the option list stack.
|
132
|
+
attr_reader :stack
|
107
133
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
134
|
+
# Returns +true+ if at least one local option is defined.
|
135
|
+
#
|
136
|
+
# The zeroth stack element is not respected when doing the query because it contains either the
|
137
|
+
# OptionParser::DefaultList or a CmdParse::MultiList with the global options of the
|
138
|
+
# CmdParse::CommandParser.
|
139
|
+
def options_defined?
|
140
|
+
stack[1..-1].each do |list|
|
141
|
+
list.each_option do |switch|
|
142
|
+
return true if ::OptionParser::Switch === switch && (switch.short || switch.long)
|
143
|
+
end
|
112
144
|
end
|
145
|
+
false
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns +true+ if a banner has been set.
|
149
|
+
def banner?
|
150
|
+
!@banner.nil?
|
113
151
|
end
|
114
152
|
|
115
153
|
end
|
116
154
|
|
117
|
-
# Base class for
|
118
|
-
#
|
155
|
+
# === Base class for commands
|
156
|
+
#
|
157
|
+
# This class implements all needed methods so that it can be used by the CommandParser class.
|
158
|
+
#
|
159
|
+
# Commands can either be created by sub-classing or on the fly when using the #add_command method.
|
160
|
+
# The latter allows for a more terse specification of a command while the sub-class approach
|
161
|
+
# allows to customize all aspects of a command by overriding methods.
|
162
|
+
#
|
163
|
+
# Basic example for sub-classing:
|
164
|
+
#
|
165
|
+
# class TestCommand < CmdParse::Command
|
166
|
+
# def initialize
|
167
|
+
# super('test', takes_commands: false)
|
168
|
+
# options.on('-m', '--my-opt', 'My option') { 'Do something' }
|
169
|
+
# end
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# parser = CmdParse::CommandParser.new
|
173
|
+
# parser.add_command(TestCommand.new)
|
174
|
+
# parser.parse
|
175
|
+
#
|
176
|
+
# Basic example for on the fly creation:
|
177
|
+
#
|
178
|
+
# parser = CmdParse::CommandParser.new
|
179
|
+
# parser.add_command('test') do |cmd|
|
180
|
+
# takes_commands(false)
|
181
|
+
# options.on('-m', '--my-opt', 'My option') { 'Do something' }
|
182
|
+
# end
|
183
|
+
# parser.parse
|
184
|
+
#
|
185
|
+
# === Basic Properties
|
186
|
+
#
|
187
|
+
# The only thing that is mandatory to set for a Command is its #name. If the command does not take
|
188
|
+
# any sub-commands, then additionally an #action block needs to be specified or the #execute
|
189
|
+
# method overridden.
|
190
|
+
#
|
191
|
+
# However, there are several other methods that can be used to configure the behavior of a
|
192
|
+
# command:
|
193
|
+
#
|
194
|
+
# #takes_commands:: For specifying whether sub-commands are allowed.
|
195
|
+
# #options:: For specifying command specific options.
|
196
|
+
# #add_command:: For specifying sub-commands if the command takes them.
|
197
|
+
#
|
198
|
+
# === Help Related Methods
|
199
|
+
#
|
200
|
+
# Many of this class' methods are related to providing useful help output. While the most common
|
201
|
+
# methods can directly be invoked to set or retrieve information, many other methods compute the
|
202
|
+
# needed information dynamically and therefore need to be overridden to customize their return
|
203
|
+
# value.
|
204
|
+
#
|
205
|
+
# #short_desc::
|
206
|
+
# For a short description of the command (getter/setter).
|
207
|
+
# #long_desc::
|
208
|
+
# For a detailed description of the command (getter/setter).
|
209
|
+
# #argument_desc::
|
210
|
+
# For describing command arguments (setter).
|
211
|
+
# #help, #help_banner, #help_short_desc, #help_long_desc, #help_commands, #help_arguments, #help_options::
|
212
|
+
# For outputting the general command help or individual sections of the command help (getter).
|
213
|
+
# #usage, #usage_options, #usage_arguments, #usage_commands::
|
214
|
+
# For outputting the usage line or individual parts of it (getter).
|
215
|
+
#
|
216
|
+
# === Built-in Commands
|
217
|
+
#
|
218
|
+
# cmdparse ships with two built-in commands:
|
219
|
+
# * HelpCommand (for showing help messages) and
|
220
|
+
# * VersionCommand (for showing version information).
|
119
221
|
class Command
|
120
222
|
|
121
|
-
# The name of the command
|
223
|
+
# The name of the command.
|
122
224
|
attr_reader :name
|
123
225
|
|
124
|
-
#
|
125
|
-
attr_accessor :short_desc
|
126
|
-
|
127
|
-
# A detailed description of the command. Maybe a single string or an array of strings for
|
128
|
-
# multiline description. Each string should ideally be smaller than 76 characters.
|
129
|
-
attr_accessor :description
|
130
|
-
|
131
|
-
# The wrapper for parsing the command line options.
|
132
|
-
attr_accessor :options
|
133
|
-
|
134
|
-
# Returns the name of the default command.
|
226
|
+
# Returns the name of the default sub-command or +nil+ if there isn't any.
|
135
227
|
attr_reader :default_command
|
136
228
|
|
137
|
-
# Sets or returns the super
|
138
|
-
# instance for normal commands or a
|
229
|
+
# Sets or returns the super-command of this command. The super-command is either a Command
|
230
|
+
# instance for normal commands or a CommandParser instance for the main command (ie.
|
231
|
+
# CommandParser#main_command).
|
139
232
|
attr_accessor :super_command
|
140
233
|
|
141
|
-
# Returns the
|
234
|
+
# Returns the mapping of command name to command for all sub-commands of this command.
|
142
235
|
attr_reader :commands
|
143
236
|
|
144
|
-
#
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
|
154
|
-
|
155
|
-
@
|
156
|
-
@
|
157
|
-
@has_commands = has_commands
|
158
|
-
@has_args = has_args
|
159
|
-
@commands = Hash.new
|
237
|
+
# A data store (initially an empty Hash) that can be used for storing anything. For example, it
|
238
|
+
# can be used to store option values. cmdparse itself doesn't do anything with it.
|
239
|
+
attr_accessor :data
|
240
|
+
|
241
|
+
# Initializes the command called +name+.
|
242
|
+
#
|
243
|
+
# Options:
|
244
|
+
#
|
245
|
+
# takes_commands:: Specifies whether this command can take sub-commands.
|
246
|
+
def initialize(name, takes_commands: true)
|
247
|
+
@name = name.freeze
|
248
|
+
@options = OptionParser.new
|
249
|
+
@commands = CommandHash.new
|
160
250
|
@default_command = nil
|
161
|
-
|
251
|
+
@action = nil
|
252
|
+
@argument_desc ||= {}
|
253
|
+
@data = {}
|
254
|
+
takes_commands(takes_commands)
|
162
255
|
end
|
163
256
|
|
164
|
-
#
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
257
|
+
# Sets whether this command can take sub-command.
|
258
|
+
#
|
259
|
+
# The argument +val+ needs to be +true+ or +false+.
|
260
|
+
def takes_commands(val)
|
261
|
+
if !val && commands.size > 0
|
262
|
+
raise Error, "Can't change value of takes_commands to false because there are already sub-commands"
|
263
|
+
else
|
264
|
+
@takes_commands = val
|
265
|
+
end
|
169
266
|
end
|
267
|
+
alias takes_commands= takes_commands
|
170
268
|
|
171
|
-
# Return +true+ if this command
|
172
|
-
def
|
173
|
-
@
|
269
|
+
# Return +true+ if this command can take sub-commands.
|
270
|
+
def takes_commands?
|
271
|
+
@takes_commands
|
174
272
|
end
|
175
273
|
|
176
|
-
#
|
177
|
-
|
178
|
-
|
274
|
+
# :call-seq:
|
275
|
+
# command.options {|opts| ...} -> opts
|
276
|
+
# command.options -> opts
|
277
|
+
#
|
278
|
+
# Yields the OptionParser instance that is used for parsing the options of this command (if a
|
279
|
+
# block is given) and returns it.
|
280
|
+
def options #:yields: options
|
281
|
+
yield(@options) if block_given?
|
282
|
+
@options
|
179
283
|
end
|
180
284
|
|
181
|
-
#
|
285
|
+
# :call-seq:
|
286
|
+
# command.add_command(other_command, default: false) {|cmd| ... } -> command
|
287
|
+
# command.add_command('other', default: false) {|cmd| ...} -> command
|
288
|
+
#
|
289
|
+
# Adds a command to the command list.
|
290
|
+
#
|
291
|
+
# The argument +command+ can either be a Command object or a String in which case a new Command
|
292
|
+
# object is created. In both cases the Command object is yielded.
|
182
293
|
#
|
183
|
-
# If the optional
|
184
|
-
# specified on the command line.
|
185
|
-
|
186
|
-
|
294
|
+
# If the optional argument +default+ is +true+, then the command is used when no other
|
295
|
+
# sub-command is specified on the command line.
|
296
|
+
#
|
297
|
+
# If this command takes no other commands, an error is raised.
|
298
|
+
def add_command(command, default: false) # :yields: command_object
|
299
|
+
raise TakesNoCommandError.new(name) unless takes_commands?
|
300
|
+
|
301
|
+
command = Command.new(command) if command.kind_of?(String)
|
302
|
+
command.super_command = self
|
187
303
|
@commands[command.name] = command
|
188
304
|
@default_command = command.name if default
|
189
|
-
command.
|
190
|
-
command
|
191
|
-
end
|
192
|
-
|
193
|
-
# For sorting commands by name.
|
194
|
-
def <=>(other)
|
195
|
-
@name <=> other.name
|
196
|
-
end
|
305
|
+
command.fire_hook_after_add
|
306
|
+
yield(command) if block_given?
|
197
307
|
|
198
|
-
|
199
|
-
# to a +CommandParser+ instance.
|
200
|
-
def commandparser
|
201
|
-
cmd = super_command
|
202
|
-
cmd = cmd.super_command while !cmd.nil? && !cmd.kind_of?(CommandParser)
|
203
|
-
cmd
|
308
|
+
self
|
204
309
|
end
|
205
310
|
|
206
|
-
#
|
207
|
-
# [
|
208
|
-
|
311
|
+
# :call-seq:
|
312
|
+
# command.command_chain -> [top_level_command, super_command, ..., command]
|
313
|
+
#
|
314
|
+
# Returns the command chain, i.e. a list containing this command and all of its super-commands,
|
315
|
+
# starting at the top level command.
|
316
|
+
def command_chain
|
209
317
|
cmds = []
|
210
318
|
cmd = self
|
211
319
|
while !cmd.nil? && !cmd.super_command.kind_of?(CommandParser)
|
212
|
-
cmds
|
320
|
+
cmds.unshift(cmd)
|
213
321
|
cmd = cmd.super_command
|
214
322
|
end
|
215
323
|
cmds
|
216
324
|
end
|
217
325
|
|
218
|
-
#
|
219
|
-
|
326
|
+
# Returns the associated CommandParser instance for this command or +nil+ if no command parser
|
327
|
+
# is associated.
|
328
|
+
def command_parser
|
329
|
+
cmd = super_command
|
330
|
+
cmd = cmd.super_command while !cmd.nil? && !cmd.kind_of?(CommandParser)
|
331
|
+
cmd
|
332
|
+
end
|
220
333
|
|
221
|
-
#
|
222
|
-
|
223
|
-
|
334
|
+
# Sets the given +block+ as the action block that is used on when executing this command.
|
335
|
+
#
|
336
|
+
# If a sub-class is created for specifying a command, then the #execute method should be
|
337
|
+
# overridden instead of setting an action block.
|
338
|
+
#
|
339
|
+
# See also: #execute
|
340
|
+
def action(&block)
|
341
|
+
@action = block
|
224
342
|
end
|
225
343
|
|
226
|
-
#
|
344
|
+
# Invokes the action block with the parsed arguments.
|
345
|
+
#
|
346
|
+
# This method is called by the CommandParser instance if this command was specified on the
|
347
|
+
# command line to be executed.
|
227
348
|
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
def execute(args)
|
231
|
-
@
|
349
|
+
# Sub-classes can either specify an action block or directly override this method (the latter is
|
350
|
+
# preferred).
|
351
|
+
def execute(*args)
|
352
|
+
@action.call(*args)
|
232
353
|
end
|
233
354
|
|
234
|
-
#
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
t
|
242
|
-
end.join('')
|
243
|
-
tmp << " COMMAND [options]" if has_commands?
|
244
|
-
tmp << " [ARGS]" if has_args?
|
245
|
-
tmp
|
355
|
+
# Sets the short description of the command if an argument is given. Always returns the short
|
356
|
+
# description.
|
357
|
+
#
|
358
|
+
# The short description is ideally shorter than 60 characters.
|
359
|
+
def short_desc(*val)
|
360
|
+
@short_desc = val[0] unless val.empty?
|
361
|
+
@short_desc
|
246
362
|
end
|
363
|
+
alias short_desc= short_desc
|
247
364
|
|
248
|
-
#
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
365
|
+
# Sets the detailed description of the command if an argument is given. Always returns the
|
366
|
+
# detailed description.
|
367
|
+
#
|
368
|
+
# This may be a single string or an array of strings for multiline description. Each string
|
369
|
+
# is ideally shorter than 76 characters.
|
370
|
+
def long_desc(*val)
|
371
|
+
@long_desc = val.flatten unless val.empty?
|
372
|
+
@long_desc
|
373
|
+
end
|
374
|
+
alias long_desc= long_desc
|
375
|
+
|
376
|
+
# :call-seq:
|
377
|
+
# cmd.argument_desc(name => desc, ...)
|
378
|
+
#
|
379
|
+
# Sets the descriptions for one or more arguments using name-description pairs.
|
380
|
+
#
|
381
|
+
# The used names should correspond to the names used in #usage_arguments.
|
382
|
+
def argument_desc(hash)
|
383
|
+
@argument_desc.update(hash)
|
384
|
+
end
|
385
|
+
|
386
|
+
# Returns the number of arguments required for the execution of the command, i.e. the number of
|
387
|
+
# arguments the #action block or the #execute method takes.
|
388
|
+
#
|
389
|
+
# If the returned number is negative, it means that the minimum number of arguments is -n-1.
|
390
|
+
#
|
391
|
+
# See: Method#arity, Proc#arity
|
392
|
+
def arity
|
393
|
+
(@action || method(:execute)).arity
|
394
|
+
end
|
395
|
+
|
396
|
+
# Returns +true+ if the command can take one or more arguments.
|
397
|
+
def takes_arguments?
|
398
|
+
arity.abs > 0
|
399
|
+
end
|
400
|
+
|
401
|
+
# Returns a string containing the help message for the command.
|
402
|
+
def help
|
403
|
+
output = ''
|
404
|
+
output << help_banner
|
405
|
+
output << help_short_desc
|
406
|
+
output << help_long_desc
|
407
|
+
output << help_commands
|
408
|
+
output << help_arguments
|
409
|
+
output << help_options('Options (take precedence over global options)', options)
|
410
|
+
output << help_options('Global Options', command_parser.global_options)
|
411
|
+
end
|
412
|
+
|
413
|
+
# Returns the banner (including the usage line) of the command.
|
414
|
+
#
|
415
|
+
# The usage line is command specific but the rest is the same for all commands and can be set
|
416
|
+
# via +command_parser.main_options.banner+.
|
417
|
+
def help_banner
|
418
|
+
output = ''
|
419
|
+
if command_parser.main_options.banner?
|
420
|
+
output << format(command_parser.main_options.banner, indent: 0) << "\n\n"
|
260
421
|
end
|
261
|
-
|
262
|
-
|
263
|
-
|
422
|
+
output << format(usage, indent: 7) << "\n\n"
|
423
|
+
end
|
424
|
+
|
425
|
+
# Returns the usage line for the command.
|
426
|
+
#
|
427
|
+
# The usage line is automatically generated from the available information. If this is not
|
428
|
+
# suitable, override this method to provide a command specific usage line.
|
429
|
+
#
|
430
|
+
# Typical usage lines looks like the following:
|
431
|
+
#
|
432
|
+
# Usage: program [options] command [options] {sub_command1 | sub_command2}
|
433
|
+
# Usage: program [options] command [options] ARG1 [ARG2] [REST...]
|
434
|
+
#
|
435
|
+
# See: #usage_options, #usage_arguments, #usage_commands
|
436
|
+
def usage
|
437
|
+
tmp = "Usage: #{command_parser.main_options.program_name}"
|
438
|
+
tmp << command_parser.main_command.usage_options
|
439
|
+
tmp << command_chain.map {|cmd| " #{cmd.name}#{cmd.usage_options}"}.join('')
|
440
|
+
if takes_commands?
|
441
|
+
tmp << " #{usage_commands}"
|
442
|
+
elsif takes_arguments?
|
443
|
+
tmp << " #{usage_arguments}"
|
264
444
|
end
|
265
|
-
|
266
|
-
|
267
|
-
|
445
|
+
tmp
|
446
|
+
end
|
447
|
+
|
448
|
+
# Returns a string describing the options of the command for use in the usage line.
|
449
|
+
#
|
450
|
+
# If there are any options, the resulting string also includes a leading space!
|
451
|
+
#
|
452
|
+
# A typical return value would look like the following:
|
453
|
+
#
|
454
|
+
# [options]
|
455
|
+
#
|
456
|
+
# See: #usage
|
457
|
+
def usage_options
|
458
|
+
(options.options_defined? ? ' [options]' : '')
|
459
|
+
end
|
460
|
+
|
461
|
+
# Returns a string describing the arguments for the command for use in the usage line.
|
462
|
+
#
|
463
|
+
# By default the names of the action block or #execute method arguments are used (done via
|
464
|
+
# Ruby's reflection API). If this is not wanted, override this method.
|
465
|
+
#
|
466
|
+
# A typical return value would look like the following:
|
467
|
+
#
|
468
|
+
# ARG1 [ARG2] [REST...]
|
469
|
+
#
|
470
|
+
# See: #usage, #argument_desc
|
471
|
+
def usage_arguments
|
472
|
+
(@action || method(:execute)).parameters.map do |type, name|
|
473
|
+
case type
|
474
|
+
when :req then name.to_s
|
475
|
+
when :opt then "[#{name}]"
|
476
|
+
when :rest then "[#{name}...]"
|
477
|
+
end
|
478
|
+
end.join(" ").upcase
|
479
|
+
end
|
480
|
+
|
481
|
+
# Returns a string describing the sub-commands of the commands for use in the usage line.
|
482
|
+
#
|
483
|
+
# Override this method for providing a command specific specialization.
|
484
|
+
#
|
485
|
+
# A typical return value would look like the following:
|
486
|
+
#
|
487
|
+
# {command | other_command | another_command }
|
488
|
+
def usage_commands
|
489
|
+
(commands.size > 0 ? "{#{commands.keys.join(" | ")}}" : '')
|
490
|
+
end
|
491
|
+
|
492
|
+
# Returns the formatted short description.
|
493
|
+
#
|
494
|
+
# For the output format see #cond_format_help_section
|
495
|
+
def help_short_desc
|
496
|
+
cond_format_help_section("Summary", "#{name} - #{short_desc}",
|
497
|
+
condition: short_desc && !short_desc.empty?)
|
498
|
+
end
|
499
|
+
|
500
|
+
# Returns the formatted detailed description.
|
501
|
+
#
|
502
|
+
# For the output format see #cond_format_help_section
|
503
|
+
def help_long_desc
|
504
|
+
cond_format_help_section("Description", [long_desc].flatten,
|
505
|
+
condition: long_desc && !long_desc.empty?)
|
506
|
+
end
|
507
|
+
|
508
|
+
# Returns the formatted sub-commands of this command.
|
509
|
+
#
|
510
|
+
# For the output format see #cond_format_help_section
|
511
|
+
def help_commands
|
512
|
+
describe_commands = lambda do |command, level = 0|
|
513
|
+
command.commands.sort.collect do |name, cmd|
|
514
|
+
str = " "*level << name << (name == command.default_command ? " (*)" : '')
|
515
|
+
str = str.ljust(command_parser.help_desc_indent) << cmd.short_desc.to_s
|
516
|
+
str = format(str, width: command_parser.help_line_width - command_parser.help_indent,
|
517
|
+
indent: command_parser.help_desc_indent)
|
518
|
+
str << "\n" << (cmd.takes_commands? ? describe_commands.call(cmd, level + 1) : "")
|
519
|
+
end.join('')
|
268
520
|
end
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
521
|
+
cond_format_help_section("Available commands", describe_commands.call(self),
|
522
|
+
condition: takes_commands?)
|
523
|
+
end
|
524
|
+
|
525
|
+
# Returns the formatted arguments of this command.
|
526
|
+
#
|
527
|
+
# For the output format see #cond_format_help_section
|
528
|
+
def help_arguments
|
529
|
+
desc = @argument_desc.map {|k, v| k.to_s.ljust(command_parser.help_desc_indent) << v.to_s}
|
530
|
+
cond_format_help_section('Arguments', desc, condition: @argument_desc.size > 0)
|
531
|
+
end
|
532
|
+
|
533
|
+
# Returns the formatted option descriptions for the given OptionParser instance.
|
534
|
+
#
|
535
|
+
# The section title needs to be specified with the +title+ argument.
|
536
|
+
#
|
537
|
+
# For the output format see #cond_format_help_section
|
538
|
+
def help_options(title, options)
|
539
|
+
summary = ''
|
540
|
+
summary_width = command_parser.main_options.summary_width
|
541
|
+
options.summarize([], summary_width, summary_width - 1, '') do |line|
|
542
|
+
summary << format(line, width: command_parser.help_line_width - command_parser.help_indent,
|
543
|
+
indent: summary_width + 1, indent_first_line: false) << "\n"
|
273
544
|
end
|
545
|
+
cond_format_help_section(title, summary, condition: !summary.empty?)
|
274
546
|
end
|
275
547
|
|
276
|
-
|
277
|
-
|
278
|
-
|
548
|
+
# This hook method is called when the command (or one of its super-commands) is added to another
|
549
|
+
# Command instance that has an associated command parser (#see command_parser).
|
550
|
+
#
|
551
|
+
# It can be used, for example, to add global options.
|
552
|
+
def on_after_add
|
553
|
+
end
|
279
554
|
|
280
|
-
|
281
|
-
|
282
|
-
|
555
|
+
# For sorting commands by name.
|
556
|
+
def <=>(other)
|
557
|
+
self.name <=> other.name
|
283
558
|
end
|
284
559
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
560
|
+
protected
|
561
|
+
|
562
|
+
# Conditionally formats a help section.
|
563
|
+
#
|
564
|
+
# Returns either the formatted help section if the condition is +true+ or an empty string
|
565
|
+
# otherwise.
|
566
|
+
#
|
567
|
+
# The help section starts with a title and the given lines are indented to easily distinguish
|
568
|
+
# different sections.
|
569
|
+
#
|
570
|
+
# A typical help section would look like the following:
|
571
|
+
#
|
572
|
+
# Summary:
|
573
|
+
# help - Provide help for individual commands
|
574
|
+
def cond_format_help_section(title, *lines, condition: true, indent: true)
|
575
|
+
if condition
|
576
|
+
"#{title}:\n" << format(lines.flatten.join("\n"),
|
577
|
+
indent: (indent ? command_parser.help_indent : 0),
|
578
|
+
indent_first_line: true) << "\n\n"
|
579
|
+
else
|
580
|
+
''
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
# Returns the text in +content+ formatted so that no line is longer than +width+ characters.
|
585
|
+
#
|
586
|
+
# Options:
|
587
|
+
#
|
588
|
+
# width:: The maximum width of a line. If not specified, the CommandParser#help_line_width value
|
589
|
+
# is used.
|
590
|
+
#
|
591
|
+
# indent:: This option specifies the amount of spaces prepended to each line. If not specified,
|
592
|
+
# the CommandParser#help_indent value is used.
|
593
|
+
#
|
594
|
+
# indent_first_line:: If this option is +true+, then the first line is also indented.
|
595
|
+
def format(content, width: command_parser.help_line_width,
|
596
|
+
indent: command_parser.help_indent, indent_first_line: false)
|
597
|
+
content = (content || '').dup
|
598
|
+
line_length = width - indent
|
599
|
+
first_line_pattern = other_lines_pattern = /\A.{1,#{line_length}}\z|\A.{1,#{line_length}}[ \n]/
|
600
|
+
(first_line_pattern = /\A.{1,#{width}}\z|\A.{1,#{width}}[ \n]/) unless indent_first_line
|
601
|
+
pattern = first_line_pattern
|
602
|
+
|
603
|
+
content.split(/\n\n/).map do |paragraph|
|
604
|
+
lines = []
|
605
|
+
while paragraph.length > 0
|
606
|
+
unless (str = paragraph.slice!(pattern).sub(/[ \n]\z/, ''))
|
607
|
+
str = paragraph.slice!(0, line_length)
|
608
|
+
end
|
609
|
+
lines << (lines.empty? && !indent_first_line ? '' : ' '*indent) + str.gsub(/\n/, ' ')
|
610
|
+
pattern = other_lines_pattern
|
611
|
+
end
|
612
|
+
lines.join("\n")
|
613
|
+
end.join("\n\n")
|
614
|
+
end
|
615
|
+
|
616
|
+
def fire_hook_after_add #:nodoc:
|
617
|
+
return unless command_parser
|
618
|
+
@options.stack[0] = MultiList.new(command_parser.global_options.stack)
|
619
|
+
on_after_add
|
620
|
+
@commands.each_value {|cmd| cmd.fire_hook_after_add}
|
292
621
|
end
|
293
622
|
|
294
623
|
end
|
295
624
|
|
296
|
-
# The default help
|
297
|
-
#
|
298
|
-
#
|
625
|
+
# The default help Command.
|
626
|
+
#
|
627
|
+
# It adds the options "-h" and "--help" to the CommandParser#global_options.
|
628
|
+
#
|
629
|
+
# When the command is specified on the command line (or one of the above mentioned options), it
|
630
|
+
# shows the main help or individual command help.
|
299
631
|
class HelpCommand < Command
|
300
632
|
|
301
|
-
def initialize
|
302
|
-
super('help', false)
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
633
|
+
def initialize #:nodoc:
|
634
|
+
super('help', takes_commands: false)
|
635
|
+
short_desc('Provide help for individual commands')
|
636
|
+
long_desc('This command prints the program help if no arguments are given. If one or ' <<
|
637
|
+
'more command names are given as arguments, these arguments are interpreted ' <<
|
638
|
+
'as a hierachy of commands and the help for the right most command is show.')
|
639
|
+
argument_desc(COMMAND: 'The name of a command or sub-command')
|
307
640
|
end
|
308
641
|
|
309
|
-
def
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
opt.on_tail("-h", "--help", "Show help") do
|
314
|
-
execute([])
|
315
|
-
end
|
316
|
-
end
|
642
|
+
def on_after_add #:nodoc:
|
643
|
+
command_parser.global_options.on_tail("-h", "--help", "Show help") do
|
644
|
+
execute(*command_parser.current_command.command_chain.map(&:name))
|
645
|
+
exit
|
317
646
|
end
|
318
647
|
end
|
319
648
|
|
320
|
-
def
|
321
|
-
"
|
649
|
+
def usage_arguments #:nodoc:
|
650
|
+
"[COMMAND COMMAND...]"
|
322
651
|
end
|
323
652
|
|
324
|
-
def execute(args)
|
653
|
+
def execute(*args) #:nodoc:
|
325
654
|
if args.length > 0
|
326
|
-
cmd =
|
655
|
+
cmd = command_parser.main_command
|
327
656
|
arg = args.shift
|
328
|
-
while !arg.nil? && cmd.commands
|
657
|
+
while !arg.nil? && cmd.commands.key?(arg)
|
329
658
|
cmd = cmd.commands[arg]
|
330
659
|
arg = args.shift
|
331
660
|
end
|
332
661
|
if arg.nil?
|
333
|
-
cmd.
|
662
|
+
puts cmd.help
|
334
663
|
else
|
335
664
|
raise InvalidArgumentError, args.unshift(arg).join(' ')
|
336
665
|
end
|
337
666
|
else
|
338
|
-
|
667
|
+
puts command_parser.main_command.help
|
339
668
|
end
|
340
|
-
exit
|
341
669
|
end
|
342
670
|
|
343
671
|
end
|
344
672
|
|
345
673
|
|
346
|
-
# The default version command.
|
347
|
-
#
|
348
|
-
#
|
674
|
+
# The default version command.
|
675
|
+
#
|
676
|
+
# It adds the options "-v" and "--version" to the CommandParser#global_options.
|
677
|
+
#
|
678
|
+
# When the command is specified on the command line (or one of the above mentioned options), it
|
679
|
+
# shows the version of the program configured by the settings
|
680
|
+
#
|
681
|
+
# * command_parser.main_options.program_name
|
682
|
+
# * command_parser.main_options.version
|
349
683
|
class VersionCommand < Command
|
350
684
|
|
351
|
-
def initialize
|
352
|
-
super('version',
|
353
|
-
|
685
|
+
def initialize #:nodoc:
|
686
|
+
super('version', takes_commands: false)
|
687
|
+
short_desc("Show the version of the program")
|
354
688
|
end
|
355
689
|
|
356
|
-
def
|
357
|
-
|
358
|
-
|
359
|
-
commandparser.main_command.options.instance do |opt|
|
360
|
-
opt.on_tail("--version", "-v", "Show the version of the program") do
|
361
|
-
execute([])
|
362
|
-
end
|
363
|
-
end
|
690
|
+
def on_after_add #:nodoc:
|
691
|
+
command_parser.main_options.on_tail("--version", "-v", "Show the version of the program") do
|
692
|
+
execute
|
364
693
|
end
|
365
694
|
end
|
366
695
|
|
367
|
-
def
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
version = commandparser.program_version
|
373
|
-
version = version.join('.') if version.instance_of?(Array)
|
374
|
-
puts commandparser.banner + "\n" if commandparser.banner
|
375
|
-
puts "#{commandparser.program_name} #{version}"
|
696
|
+
def execute #:nodoc:
|
697
|
+
version = command_parser.main_options.version
|
698
|
+
version = version.join('.') if version.kind_of?(Array)
|
699
|
+
puts command_parser.main_options.banner + "\n" if command_parser.main_options.banner?
|
700
|
+
puts "#{command_parser.main_options.program_name} #{version}"
|
376
701
|
exit
|
377
702
|
end
|
378
703
|
|
379
704
|
end
|
380
705
|
|
381
706
|
|
382
|
-
#
|
707
|
+
# === Main Class for Creating a Command Based CLI Program
|
708
|
+
#
|
709
|
+
# This class can directly be used (or sub-classed, if need be) to create a command based CLI
|
710
|
+
# program.
|
711
|
+
#
|
712
|
+
# The CLI program itself is represented by the #main_command, a Command instance (as are all
|
713
|
+
# commands and sub-commands). This main command can either hold sub-commands (the normal use case)
|
714
|
+
# which represent the programs top level commands or take no commands in which case it acts
|
715
|
+
# similar to a simple OptionParser based program (albeit with better help functionality).
|
716
|
+
#
|
717
|
+
# Parsing the command line for commands is done by this class, option parsing is delegated to the
|
718
|
+
# battle tested OptionParser of the Ruby standard library.
|
719
|
+
#
|
720
|
+
# === Usage
|
721
|
+
#
|
722
|
+
# After initialization some optional information is expected to be set on the Command#options of
|
723
|
+
# the #main_command:
|
724
|
+
#
|
725
|
+
# banner:: A banner that appears in the help output before anything else.
|
726
|
+
# program_name:: The name of the program. If not set, this value is computed from $0.
|
727
|
+
# version:: The version string of the program.
|
728
|
+
#
|
729
|
+
# In addition to the main command's options instance (which represents the top level options that
|
730
|
+
# need to be specified before any command name), there is also a #global_options instance which
|
731
|
+
# represents options that can be specified anywhere on the command line.
|
732
|
+
#
|
733
|
+
# Top level commands can be added to the main command by using the #add_command method.
|
734
|
+
#
|
735
|
+
# Once everything is set up, the #parse method is used for parsing the command line.
|
383
736
|
class CommandParser
|
384
737
|
|
385
|
-
# A standard banner for help & version screens
|
386
|
-
attr_accessor :banner
|
387
|
-
|
388
738
|
# The top level command representing the program itself.
|
389
739
|
attr_reader :main_command
|
390
740
|
|
391
|
-
# The
|
392
|
-
|
741
|
+
# The command that is being executed. Only available during parsing of the command line
|
742
|
+
# arguments.
|
743
|
+
attr_reader :current_command
|
393
744
|
|
394
|
-
#
|
395
|
-
|
745
|
+
# A data store (initially an empty Hash) that can be used for storing anything. For example, it
|
746
|
+
# can be used to store global option values. cmdparse itself doesn't do anything with it.
|
747
|
+
attr_accessor :data
|
396
748
|
|
397
749
|
# Should exceptions be handled gracefully? I.e. by printing error message and the help screen?
|
750
|
+
#
|
751
|
+
# See ::new for possible values.
|
398
752
|
attr_reader :handle_exceptions
|
399
753
|
|
400
|
-
#
|
401
|
-
|
402
|
-
|
403
|
-
#
|
404
|
-
|
405
|
-
|
406
|
-
#
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
754
|
+
# The maximum width of the help lines.
|
755
|
+
attr_accessor :help_line_width
|
756
|
+
|
757
|
+
# The amount of spaces to indent the content of help sections.
|
758
|
+
attr_accessor :help_indent
|
759
|
+
|
760
|
+
# The indentation used for, among other things, command descriptions.
|
761
|
+
attr_accessor :help_desc_indent
|
762
|
+
|
763
|
+
# Creates a new CommandParser object.
|
764
|
+
#
|
765
|
+
# Options:
|
766
|
+
#
|
767
|
+
# handle_exceptions:: Set to +true+ if exceptions should be handled gracefully by showing the
|
768
|
+
# error and a help message, or to +false+ if exception should not be handled
|
769
|
+
# at all. If this options is set to :no_help, the exception is handled but no
|
770
|
+
# help message is shown.
|
771
|
+
#
|
772
|
+
# takes_commands:: Specifies whether the main program takes any commands.
|
773
|
+
def initialize(handle_exceptions: false, takes_commands: true)
|
774
|
+
@global_options = OptionParser.new
|
775
|
+
@main_command = Command.new('main', takes_commands: takes_commands)
|
411
776
|
@main_command.super_command = self
|
412
|
-
@
|
413
|
-
@
|
414
|
-
@
|
777
|
+
@main_command.options.stack[0] = MultiList.new(@global_options.stack)
|
778
|
+
@handle_exceptions = handle_exceptions
|
779
|
+
@help_line_width = 80
|
780
|
+
@help_indent = 4
|
781
|
+
@help_desc_indent = 18
|
782
|
+
@data = {}
|
415
783
|
end
|
416
784
|
|
417
|
-
#
|
418
|
-
|
785
|
+
# :call-seq:
|
786
|
+
# cmdparse.main_options -> OptionParser instance
|
787
|
+
# cmdparse.main_options {|opts| ...} -> opts (OptionParser instance)
|
788
|
+
#
|
789
|
+
# Yields the main options (that are only available directly after the program name) if a block
|
790
|
+
# is given and returns them.
|
791
|
+
#
|
792
|
+
# The main options are also used for setting the program name, version and banner.
|
793
|
+
def main_options
|
794
|
+
yield(@main_command.options) if block_given?
|
419
795
|
@main_command.options
|
420
796
|
end
|
421
797
|
|
422
|
-
#
|
423
|
-
|
424
|
-
|
798
|
+
# :call-seq:
|
799
|
+
# cmdparse.global_options -> OptionParser instance
|
800
|
+
# cmdparse.gloabl_options {|opts| ...} -> opts (OptionParser instance)
|
801
|
+
#
|
802
|
+
# Yields the global options if a block is given and returns them.
|
803
|
+
#
|
804
|
+
# The global options are those options that can be used on the top level and with any
|
805
|
+
# command.
|
806
|
+
def global_options
|
807
|
+
yield(@global_options) if block_given?
|
808
|
+
@global_options
|
425
809
|
end
|
426
810
|
|
427
|
-
#
|
428
|
-
|
429
|
-
|
811
|
+
# Adds a top level command.
|
812
|
+
#
|
813
|
+
# See Command#add_command for detailed invocation information.
|
814
|
+
def add_command(*args, &block)
|
815
|
+
@main_command.add_command(*args, &block)
|
430
816
|
end
|
431
817
|
|
432
|
-
#
|
818
|
+
# Parses the command line arguments.
|
433
819
|
#
|
434
|
-
# If a block is
|
820
|
+
# If a block is given, the current hierarchy level and the name of the current command is
|
435
821
|
# yielded after the option parsing is done but before a command is executed.
|
436
|
-
def parse(argv = ARGV) # :yields: level,
|
822
|
+
def parse(argv = ARGV) # :yields: level, command_name
|
437
823
|
level = 0
|
438
|
-
|
824
|
+
@current_command = @main_command
|
439
825
|
|
440
|
-
while
|
441
|
-
argv = if
|
442
|
-
|
826
|
+
while true
|
827
|
+
argv = if @current_command.takes_commands? || ENV.include?('POSIXLY_CORRECT')
|
828
|
+
@current_command.options.order(argv)
|
443
829
|
else
|
444
|
-
|
830
|
+
@current_command.options.permute(argv)
|
445
831
|
end
|
446
|
-
yield(level,
|
447
|
-
|
448
|
-
if
|
449
|
-
|
450
|
-
|
451
|
-
if
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
cmdName = command.default_command
|
456
|
-
end
|
457
|
-
else
|
458
|
-
raise InvalidCommandError.new(cmdName) unless command.commands[ cmdName ]
|
832
|
+
yield(level, @current_command.name) if block_given?
|
833
|
+
|
834
|
+
if @current_command.takes_commands?
|
835
|
+
cmd_name = argv.shift || @current_command.default_command
|
836
|
+
|
837
|
+
if cmd_name.nil?
|
838
|
+
raise NoCommandGivenError.new
|
839
|
+
elsif !@current_command.commands.key?(cmd_name)
|
840
|
+
raise InvalidCommandError.new(cmd_name)
|
459
841
|
end
|
460
842
|
|
461
|
-
|
843
|
+
@current_command = @current_command.commands[cmd_name]
|
462
844
|
level += 1
|
463
845
|
else
|
464
|
-
|
465
|
-
|
846
|
+
original_n = @current_command.arity
|
847
|
+
n = (original_n < 0 ? -original_n - 1 : original_n)
|
848
|
+
raise NotEnoughArgumentsError.new(n) if argv.size < n
|
849
|
+
|
850
|
+
argv.slice!(n..-1) unless original_n < 0
|
851
|
+
@current_command.execute(*argv)
|
852
|
+
break
|
466
853
|
end
|
467
854
|
end
|
468
855
|
rescue ParseError, OptionParser::ParseError => e
|
469
|
-
raise
|
856
|
+
raise unless @handle_exceptions
|
470
857
|
puts "Error while parsing command line:\n " + e.message
|
471
|
-
|
472
|
-
|
473
|
-
|
858
|
+
if @handle_exceptions != :no_help && @main_command.commands.key?('help')
|
859
|
+
puts
|
860
|
+
@main_command.commands['help'].execute(*@current_command.command_chain.map(&:name))
|
861
|
+
end
|
862
|
+
exit(64) # FreeBSD standard exit error for "command was used incorrectly"
|
863
|
+
ensure
|
864
|
+
@current_command = nil
|
474
865
|
end
|
475
866
|
|
476
867
|
end
|