commandline2 0.7.2
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.
- data/lib/commandline.rb +16 -0
- data/lib/commandline/application.rb +424 -0
- data/lib/commandline/kernel.rb +17 -0
- data/lib/commandline/optionparser.rb +16 -0
- data/lib/commandline/optionparser/option.rb +185 -0
- data/lib/commandline/optionparser/optiondata.rb +90 -0
- data/lib/commandline/optionparser/optionparser.rb +518 -0
- data/lib/commandline/utils.rb +12 -0
- data/lib/open4.rb +79 -0
- data/lib/test/unit/systemtest.rb +77 -0
- metadata +74 -0
data/lib/commandline.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# $Id: commandline.rb,v 1.2 2005/09/19 19:53:36 jdf Exp $
|
2
|
+
# $Source: /Users/jdf/projects/CVSROOT/devel/ruby/commandline/lib/commandline.rb,v $
|
3
|
+
#
|
4
|
+
# Author: Jim Freeze
|
5
|
+
# Copyright (c) 2005
|
6
|
+
#
|
7
|
+
# =DESCRIPTION
|
8
|
+
# Loader
|
9
|
+
#
|
10
|
+
# =Revision History
|
11
|
+
# Jim.Freeze 2005/06/17 Birthday
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'commandline/kernel'
|
15
|
+
require 'commandline/utils'
|
16
|
+
require 'commandline/application'
|
@@ -0,0 +1,424 @@
|
|
1
|
+
# $Id: application.rb,v 1.4 2005/09/20 19:11:17 jdf Exp $
|
2
|
+
# $Source: /Users/jdf/projects/CVSROOT/devel/ruby/commandline/lib/commandline/application.rb,v $
|
3
|
+
#
|
4
|
+
# Author: Jim Freeze
|
5
|
+
# Copyright (c) 2005
|
6
|
+
#
|
7
|
+
# =DESCRIPTION
|
8
|
+
# Framework for commandline applications
|
9
|
+
#
|
10
|
+
# =Revision History
|
11
|
+
# Jim.Freeze 06/02/2005 Birthday - kinda
|
12
|
+
# Jim.Freeze 09/19/2005 fixed @arg_arity bug and copyright print bug
|
13
|
+
|
14
|
+
require 'commandline/utils'
|
15
|
+
require 'commandline/optionparser'
|
16
|
+
|
17
|
+
module CommandLine
|
18
|
+
class Application
|
19
|
+
class ApplicationError < StandardError; end
|
20
|
+
class OptionError < ApplicationError; end
|
21
|
+
class MissingMainError < ApplicationError; end
|
22
|
+
class InvalidArgumentArityError < ApplicationError; end
|
23
|
+
class ArgumentError < ApplicationError; end
|
24
|
+
class UnknownOptionError < ApplicationError; end
|
25
|
+
|
26
|
+
param_accessor :version, :author, :copyright, :synopsis,
|
27
|
+
:short_description, :long_description,
|
28
|
+
:option_parser
|
29
|
+
|
30
|
+
attr_reader :args, :argv
|
31
|
+
|
32
|
+
@no_auto_run = false
|
33
|
+
|
34
|
+
#
|
35
|
+
# TODO: Consolidate these with OptionParser - put in command line
|
36
|
+
#
|
37
|
+
DEFAULT_CONSOLE_WIDTH = 70
|
38
|
+
MIN_CONSOLE_WIDTH = 10
|
39
|
+
DEFAULT_BODY_INDENT = 4
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@synopsis = ""
|
43
|
+
@arg_arity = [0,0]
|
44
|
+
@options = []
|
45
|
+
@arg_names = []
|
46
|
+
@args = []
|
47
|
+
@replay = false
|
48
|
+
@replay_file = ".replay"
|
49
|
+
|
50
|
+
__initialize_text_formatting
|
51
|
+
|
52
|
+
# Call the child usurped initialize
|
53
|
+
__child_initialize if
|
54
|
+
self.class.private_instance_methods(false).include?("__child_initialize")
|
55
|
+
|
56
|
+
@option_parser ||= CommandLine::OptionParser.new(@options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def options(*opts)
|
60
|
+
opts.each { |opt| option(*[opt].flatten) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def option(*args)
|
64
|
+
@options ||= []
|
65
|
+
new_list = []
|
66
|
+
args.each { |arg|
|
67
|
+
new_list <<
|
68
|
+
case arg
|
69
|
+
when :help then __help
|
70
|
+
when :debug then __debug
|
71
|
+
when :verbose then __verbose
|
72
|
+
when :version then __version
|
73
|
+
else arg
|
74
|
+
end
|
75
|
+
}
|
76
|
+
#p new_list
|
77
|
+
@options << CommandLine::Option.new(*new_list)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Alternative for @option_data["<--opt>"], but with symbols
|
81
|
+
def opt
|
82
|
+
@option_data
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# expected_args tells the application how many arguments (not belonging
|
87
|
+
# any option) are expected to be seen on the command line
|
88
|
+
# The names of the args are used for describing the synopsis (or usage).
|
89
|
+
# If there is an indeterminant amount of arguments, they are not
|
90
|
+
# named, but returned in an array.
|
91
|
+
# expected_args takes either a list of argument names
|
92
|
+
#
|
93
|
+
# =Usage
|
94
|
+
# expected_args :sym1
|
95
|
+
# expected_args :sym1, :sym2, ...
|
96
|
+
# expected_args n #=> arg_arity => [n,n]
|
97
|
+
# expected_args arity
|
98
|
+
#
|
99
|
+
# =Examples
|
100
|
+
#
|
101
|
+
# Many forms are valid. Some examples follow:
|
102
|
+
# expected_args 0 #=> @args = []; same as not calling expected_args
|
103
|
+
# synopsis: Usage: app
|
104
|
+
|
105
|
+
# expected_args 1 #=> @args is array
|
106
|
+
# synopsis: Usage: app arg
|
107
|
+
|
108
|
+
# expected_args 2 #=> @args is array
|
109
|
+
# synopsis: Usage: app arg1 arg2
|
110
|
+
|
111
|
+
# expected_args 10 #=> @args is array
|
112
|
+
# synopsis: Usage: app arg1 ... arg10
|
113
|
+
|
114
|
+
# expected_args :file #=> @file = <arg>
|
115
|
+
# synopsis: Usage: app file
|
116
|
+
|
117
|
+
# expected_args :file1, :file2 #=> @file1 = <arg1>, @file2 = <arg2>
|
118
|
+
# synopsis: Usage: app file1 file2
|
119
|
+
|
120
|
+
# expected_args [0,1] #=> @args is array
|
121
|
+
# synopsis: Usage: app [arg1 [arg2]]
|
122
|
+
|
123
|
+
# expected_args [2,3] #=> @args is array
|
124
|
+
# synopsis: Usage: app arg1 arg2 [arg3]
|
125
|
+
|
126
|
+
# expected_args [0,-1] #=> @args is array
|
127
|
+
# synopsis: Usage: app [arg1 [arg...]]
|
128
|
+
#
|
129
|
+
|
130
|
+
# expected_args :cmd
|
131
|
+
# Now, what to do if command line has more args than expected
|
132
|
+
# app --app-option cmd --cmd-option arg-for-cmd
|
133
|
+
#
|
134
|
+
def expected_args(*exp_args)
|
135
|
+
@arg_names = []
|
136
|
+
case exp_args.size
|
137
|
+
when 0 then @arg_arity = [0,0]
|
138
|
+
when 1
|
139
|
+
case exp_args[0]
|
140
|
+
when Fixnum
|
141
|
+
v = exp_args[0]
|
142
|
+
@arg_arity = [v,v]
|
143
|
+
when Symbol
|
144
|
+
@arg_names = exp_args
|
145
|
+
@arg_arity = [1,1]
|
146
|
+
when Array
|
147
|
+
v = exp_args[0]
|
148
|
+
__validate_arg_arity(v)
|
149
|
+
@arg_arity = v
|
150
|
+
else
|
151
|
+
raise(InvalidArgumentArityError,
|
152
|
+
"Args must be a Fixnum or Array: #{exp_args[0].inspect}.")
|
153
|
+
end
|
154
|
+
else
|
155
|
+
@arg_names = exp_args
|
156
|
+
size = exp_args.size
|
157
|
+
@arg_arity = [size, size]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def use_replay(attribs = {})
|
162
|
+
@replay = true
|
163
|
+
@replay_file = attribs[:replay_file] || @replay_file
|
164
|
+
end
|
165
|
+
|
166
|
+
def usage
|
167
|
+
" Usage: #{name} #{synopsis}"
|
168
|
+
end
|
169
|
+
|
170
|
+
def man
|
171
|
+
require 'text/format'
|
172
|
+
f = Text::Format.new
|
173
|
+
f = Text::Format.new
|
174
|
+
f.columns = @columns
|
175
|
+
f.first_indent = 4
|
176
|
+
f.body_indent = @body_indent
|
177
|
+
f.tag_paragraph = false
|
178
|
+
|
179
|
+
s = []
|
180
|
+
s << ["NAME\n"]
|
181
|
+
|
182
|
+
nm = "#{short_description}".empty? ? name : "#{name} - #{short_description}"
|
183
|
+
s << f.format(nm)
|
184
|
+
|
185
|
+
sn = "#{synopsis}".empty? ? "" : "#{name} #{synopsis}"
|
186
|
+
unless sn.empty?
|
187
|
+
s << "SYNOPSIS\n"
|
188
|
+
s << f.format(sn)
|
189
|
+
end
|
190
|
+
|
191
|
+
dc = "#{long_description}"
|
192
|
+
unless dc.empty?
|
193
|
+
s << "DESCRIPTION\n"
|
194
|
+
s << f.format(dc)
|
195
|
+
end
|
196
|
+
|
197
|
+
op = option_parser.to_s
|
198
|
+
unless op.empty?
|
199
|
+
s << option_parser.to_s
|
200
|
+
end
|
201
|
+
|
202
|
+
ar = "#{author}"
|
203
|
+
unless ar.empty?
|
204
|
+
s << "AUTHOR: #{ar}"
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
ct = "COPYRIGHT (c) #{copyright}"
|
209
|
+
unless "#{copyright}".empty?
|
210
|
+
s << ct
|
211
|
+
end
|
212
|
+
|
213
|
+
s.join("\n")
|
214
|
+
end
|
215
|
+
alias :help :man
|
216
|
+
|
217
|
+
def name
|
218
|
+
File.basename(pathname)
|
219
|
+
end
|
220
|
+
|
221
|
+
def pathname
|
222
|
+
@@appname
|
223
|
+
end
|
224
|
+
|
225
|
+
def get_arg
|
226
|
+
CommandLine::OptionParser::GET_ARGS
|
227
|
+
end
|
228
|
+
alias :get_args :get_arg
|
229
|
+
|
230
|
+
def append_arg
|
231
|
+
CommandLine::OptionParser::GET_ARG_ARRAY
|
232
|
+
end
|
233
|
+
|
234
|
+
def required
|
235
|
+
CommandLine::OptionParser::OPT_NOT_FOUND_BUT_REQUIRED
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.run(argv=ARGV)
|
239
|
+
# Usurp an existing initialize so ours can be called first.
|
240
|
+
# We rename it __child_initialize and call it from initialize.
|
241
|
+
if self.private_instance_methods(false).include?("initialize")
|
242
|
+
$VERBOSE, verbose = nil, $VERBOSE
|
243
|
+
self.class_eval {
|
244
|
+
alias :__child_initialize :initialize
|
245
|
+
remove_method :initialize
|
246
|
+
}
|
247
|
+
$VERBOSE = verbose
|
248
|
+
end
|
249
|
+
obj = self.new
|
250
|
+
obj.__parse_command_line(argv)
|
251
|
+
obj.main
|
252
|
+
|
253
|
+
#alias :user_init :initialize
|
254
|
+
#@@child_class.new.main if ($0 == @@appname)
|
255
|
+
obj
|
256
|
+
rescue => err
|
257
|
+
puts "ERROR: #{err}"
|
258
|
+
exit(-1)
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.inherited(child_class)
|
262
|
+
@@appname = caller[0][/.*:/][0..-2]
|
263
|
+
@@child_class = child_class
|
264
|
+
if @@appname == $0
|
265
|
+
__set_auto_run
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def self.__set_auto_run
|
270
|
+
at_exit { @@child_class.run }
|
271
|
+
end
|
272
|
+
|
273
|
+
def main
|
274
|
+
#raise(MissingMainError, "Method #main must be defined in class #{@@child_class}.")
|
275
|
+
@@child_class.class_eval %{ def main; end }
|
276
|
+
#self.class_eval %{ def main; end }
|
277
|
+
end
|
278
|
+
|
279
|
+
def __save_argv
|
280
|
+
return unless @replay
|
281
|
+
|
282
|
+
line = 0
|
283
|
+
File.open(@replay_file, "w") { |f|
|
284
|
+
@argv.each { |arg|
|
285
|
+
f.puts "\n" if arg[0] == ?- && line != 0
|
286
|
+
f.print " #{arg}"
|
287
|
+
line += 1
|
288
|
+
}
|
289
|
+
}
|
290
|
+
end
|
291
|
+
|
292
|
+
def __restore_argv
|
293
|
+
@argv = File.read(@replay_file).gsub(/\n/, "").split
|
294
|
+
raise "Bad @argv" unless @argv.kind_of?(Array)
|
295
|
+
end
|
296
|
+
|
297
|
+
def __parse_command_line(argv)
|
298
|
+
@argv = argv
|
299
|
+
if @replay && File.exist?(@replay_file) && !@argv.grep("-r").empty?
|
300
|
+
__restore_argv
|
301
|
+
elsif @argv.empty? && @arg_arity[0] != 0
|
302
|
+
puts usage
|
303
|
+
exit(0)
|
304
|
+
end
|
305
|
+
|
306
|
+
begin
|
307
|
+
@option_data = @option_parser.parse(@argv)
|
308
|
+
@args = @option_data.args
|
309
|
+
rescue => err
|
310
|
+
puts err
|
311
|
+
puts
|
312
|
+
puts usage
|
313
|
+
exit(-1)
|
314
|
+
end
|
315
|
+
|
316
|
+
__validate_args(@option_data.args)
|
317
|
+
@arg_names.each_with_index { |name, idx|
|
318
|
+
instance_variable_set("@#{name}", @option_data.args[idx])
|
319
|
+
}
|
320
|
+
|
321
|
+
__save_argv
|
322
|
+
end
|
323
|
+
|
324
|
+
def __validate_arg_arity(arity)
|
325
|
+
min, max = *arity
|
326
|
+
raise(InvalidArgumentArityError, "Minimum argument arity '#{min}' must be "+
|
327
|
+
"greater than or equal to 0.") unless min >= 0
|
328
|
+
raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
|
329
|
+
"greater than or equal to -1.") if max < -1
|
330
|
+
raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
|
331
|
+
"greater than minimum arg_arity '#{min}'.") if max < min && max != -1
|
332
|
+
end
|
333
|
+
|
334
|
+
def __initialize_text_formatting
|
335
|
+
#
|
336
|
+
# Formatting defaults
|
337
|
+
#
|
338
|
+
console_width = ENV["COLUMNS"]
|
339
|
+
@columns =
|
340
|
+
if console_width.nil?
|
341
|
+
DEFAULT_CONSOLE_WIDTH
|
342
|
+
elsif console_width < MIN_CONSOLE_WIDTH
|
343
|
+
console_width
|
344
|
+
else
|
345
|
+
console_width - DEFAULT_BODY_INDENT
|
346
|
+
end
|
347
|
+
@body_indent = DEFAULT_BODY_INDENT
|
348
|
+
@tag_paragraph = false
|
349
|
+
@order = :index # | :alpha
|
350
|
+
end
|
351
|
+
|
352
|
+
def __validate_args(od_args)
|
353
|
+
size = od_args.size
|
354
|
+
min, max = @arg_arity
|
355
|
+
max = 1.0/0.0 if -1 == max
|
356
|
+
raise(ArgumentError,
|
357
|
+
"Missing expected arguments. Found #{size} but expected #{min}. "+
|
358
|
+
"#{od_args.inspect}\n"+
|
359
|
+
"#{usage}") if size < min
|
360
|
+
raise(ArgumentError, "Too many arguments. Found #{size} but "+
|
361
|
+
"expected #{max}.\n#{usage}") if size > max
|
362
|
+
end
|
363
|
+
|
364
|
+
def __help
|
365
|
+
{
|
366
|
+
:names => %w(--help -h),
|
367
|
+
:arity => [0,0],
|
368
|
+
:opt_description => "Displays help page.",
|
369
|
+
:arg_description => "",
|
370
|
+
:opt_found => lambda { puts man; exit },
|
371
|
+
:opt_not_found => false
|
372
|
+
}
|
373
|
+
end
|
374
|
+
|
375
|
+
def __verbose
|
376
|
+
{
|
377
|
+
:names => %w(--verbose -v),
|
378
|
+
:arity => [0,0],
|
379
|
+
:opt_description => "Sets verbosity level. Subsequent "+
|
380
|
+
"flags increase verbosity level",
|
381
|
+
:arg_description => "",
|
382
|
+
:opt_found => lambda { @verbose ||= -1; @verbose += 1 },
|
383
|
+
:opt_not_found => nil
|
384
|
+
}
|
385
|
+
end
|
386
|
+
|
387
|
+
def __version
|
388
|
+
{
|
389
|
+
:names => %w(--version -V),
|
390
|
+
:arity => [0,0],
|
391
|
+
:opt_description => "Displays application version.",
|
392
|
+
:arg_description => "",
|
393
|
+
:opt_found => lambda {
|
394
|
+
begin
|
395
|
+
puts "#{name} - Version: #{version}"
|
396
|
+
rescue
|
397
|
+
puts "No version specified"
|
398
|
+
end;
|
399
|
+
exit
|
400
|
+
},
|
401
|
+
:opt_not_found => nil
|
402
|
+
}
|
403
|
+
end
|
404
|
+
|
405
|
+
def __debug
|
406
|
+
{
|
407
|
+
:names => %w(--debug -d),
|
408
|
+
:arity => [0,0],
|
409
|
+
:opt_description => "Sets debug to true.",
|
410
|
+
:arg_description => "",
|
411
|
+
:opt_found => lambda { $DEBUG = true }
|
412
|
+
}
|
413
|
+
end
|
414
|
+
end#class Application
|
415
|
+
|
416
|
+
Application_wo_AutoRun = Class.new(Application)
|
417
|
+
class Application_wo_AutoRun
|
418
|
+
def self.inherited(child_class)
|
419
|
+
@@appname = caller[0][/.*:/][0..-2]
|
420
|
+
@@child_class = child_class
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
end#module CommandLine
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file contains additions to the Kernel module.
|
2
|
+
# Essentially, any functions that need global access go here.
|
3
|
+
|
4
|
+
module Kernel
|
5
|
+
|
6
|
+
# This is a simple debug that takes either a description and an argument
|
7
|
+
# or just an argument.
|
8
|
+
# We may want to add more debug statements, maybe some that use pp or inspect.
|
9
|
+
def debug(desc, *arg)
|
10
|
+
return unless $DEBUG
|
11
|
+
if arg.empty?
|
12
|
+
puts "==> #{desc}"
|
13
|
+
else
|
14
|
+
puts "==> #{desc}: #{arg.join(", ")}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|