commandline2 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|