commandline2 0.7.2

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