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