cmdparse 2.0.6 → 3.0.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.
- 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
|