CommandLine 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ # $Id$
2
+ # $Source$
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/utils'
15
+ require 'commandline/application'
@@ -0,0 +1,320 @@
1
+ # $Id$
2
+ # $Source$
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
+ #
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
+
25
+ param_accessor :version, :author, :copyright, :synopsis,
26
+ :short_description, :long_description,
27
+ :option_parser
28
+
29
+ #
30
+ # TODO: Consolidate these with OptionParser - put in command line
31
+ #
32
+ DEFAULT_CONSOLE_WIDTH = 70
33
+ MIN_CONSOLE_WIDTH = 10
34
+ DEFAULT_BODY_INDENT = 4
35
+
36
+ #def options
37
+ # raise(OptionError,
38
+ # "Options must be over-written with a valid (or empty) options list.")
39
+ #end
40
+
41
+ def initialize
42
+ # Ensure initializations have taken place
43
+ @arg_arity ||= [0,0]
44
+ @options ||= []
45
+ @arg_names ||= []
46
+ @option_parser ||= CommandLine::OptionParser.new(@options)
47
+ _init_format
48
+
49
+ if ARGV.empty? && [0,0] != @arg_arity
50
+ puts usage
51
+ exit(0)
52
+ end
53
+
54
+ @option_data = @option_parser.parse
55
+
56
+ validate_args(@option_data.args)
57
+ @arg_names.each_with_index { |name, idx|
58
+ instance_variable_set("@#{name}", @option_data.args[idx])
59
+ }
60
+ end
61
+
62
+ def _init_format
63
+ #
64
+ # Formatting defaults
65
+ #
66
+ console_width = ENV["COLUMNS"]
67
+ @columns =
68
+ if console_width.nil?
69
+ DEFAULT_CONSOLE_WIDTH
70
+ elsif console_width < MIN_CONSOLE_WIDTH
71
+ console_width
72
+ else
73
+ console_width - DEFAULT_BODY_INDENT
74
+ end
75
+ @body_indent = DEFAULT_BODY_INDENT
76
+ @tag_paragraph = false
77
+ @order = :index # | :alpha
78
+ end
79
+
80
+ def validate_args(od_args)
81
+ size = od_args.size
82
+ min, max = @arg_arity
83
+ max = 1.0/0.0 if -1 == max
84
+ raise(ArgumentError,
85
+ "Missing expected arguments. Found #{size} but expected #{min}.\n"+
86
+ "#{usage}") if size < min
87
+ raise(ArgumentError, "Too many arguments. Found #{size} but "+
88
+ "expected #{max}.\n#{usage}") if size > max
89
+ end
90
+
91
+ def option(*args)
92
+ @options ||= []
93
+ new_list = []
94
+ args.each { |arg|
95
+ new_list <<
96
+ case arg
97
+ when :help then _help
98
+ when :debug then _debug
99
+ when :verbose then _verbose
100
+ when :version then _version
101
+ else arg
102
+ end
103
+ }
104
+ #p new_list
105
+ @options << CommandLine::Option.new(*new_list)
106
+ end
107
+
108
+ #
109
+ # Args tells the application how many arguments (not belonging
110
+ # any option) are expected to be seen on the command line
111
+ # The names of the args are used for describing the synopsis (or usage).
112
+ # If there is an indeterminant amount of arguments, they are not
113
+ # named, but returned in an array.
114
+ # Many forms are valid. Some examples follow:
115
+ # args 0
116
+ # synopsis: Usage: app
117
+
118
+ # args :none
119
+ # synopsis: Usage: app
120
+
121
+ # args 1 #=> args is array
122
+ # synopsis: Usage: app arg
123
+
124
+ # args 2 #=> args is array
125
+ # synopsis: Usage: app arg1 arg2
126
+
127
+ # args 10 #=> args is array
128
+ # synopsis: Usage: app arg1 ... arg10
129
+
130
+ # args :file #=> @file = <arg>
131
+ # synopsis: Usage: app file
132
+
133
+ # args :file1, :file2 #=> @file1 = <arg1>, @file2 = <arg2>
134
+ # synopsis: Usage: app file1 file2
135
+
136
+ # args [0,1] #=> args is array
137
+ # synopsis: Usage: app [arg [arg]]
138
+
139
+ # args [2,3] #=> args is array
140
+ # synopsis: Usage: app arg[2,3]
141
+
142
+ # args [0,-1] #=> args is array
143
+ # synopsis: Usage: app [arg [arg...]]
144
+ #
145
+ def args(*expected_args)
146
+ @arg_names = []
147
+ case expected_args.size
148
+ when 0 then @arg_arity = [0,0]
149
+ when 1
150
+ case expected_args[0]
151
+ when Fixnum
152
+ v = expected_args[0]
153
+ @arg_arity = [v,v]
154
+ when Symbol
155
+ @arg_names = expected_args
156
+ @arg_arity = [1,1]
157
+ when Array
158
+ v = expected_args[0]
159
+ validate_arg_arity(v)
160
+ @arg_arity = v
161
+ else
162
+ raise(InvalidArgumentArityError,
163
+ "Args must be a Fixnum or Array: #{expected_args[0].inspect}.")
164
+ end
165
+ else
166
+ @arg_names = expected_args
167
+ size = expected_args.size
168
+ @arg_arity = [size, size]
169
+ end
170
+ end
171
+
172
+ def validate_arg_arity(arity)
173
+ min, max = *arity
174
+ raise(InvalidArgumentArityError, "Minimum argument arity '#{min}' must be "+
175
+ "greater than or equal to 0.") unless min >= 0
176
+ raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
177
+ "greater than or equal to -1.") if max < -1
178
+ raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
179
+ "greater than minimum arg_arity '#{min}'.") if max < min && max != -1
180
+ end
181
+
182
+ def usage
183
+ " Usage: #{name} #{synopsis}"
184
+ end
185
+
186
+ def man
187
+ require 'commandline/text/format'
188
+ f = Text::Format.new
189
+ f = Text::Format.new
190
+ f.columns = @columns
191
+ f.first_indent = 4
192
+ f.body_indent = @body_indent
193
+ f.tag_paragraph = false
194
+
195
+ s = []
196
+ s << ["NAME\n"]
197
+
198
+ nm = "#{short_description}".empty? ? name : "#{name} - #{short_description}"
199
+ s << f.format(nm)
200
+
201
+ sn = "#{synopsis}"
202
+ unless sn.empty?
203
+ s << "SYNOPSIS\n"
204
+ s << f.format(sn)
205
+ end
206
+
207
+ dc = "#{long_description}"
208
+ unless dc.empty?
209
+ s << "DESCRIPTION\n"
210
+ s << f.format(dc)
211
+ end
212
+
213
+ op = option_parser.to_s
214
+ unless op.empty?
215
+ s << option_parser.to_s
216
+ end
217
+
218
+ ar = "#{author}"
219
+ unless ar.empty?
220
+ s << "AUTHOR: #{ar}"
221
+ end
222
+
223
+
224
+ ct = "#{copyright}"
225
+ unless ct.empty?
226
+ s << ct
227
+ end
228
+
229
+ s.join("\n")
230
+ end
231
+ alias :help :man
232
+
233
+ def pathname
234
+ @@appname
235
+ end
236
+
237
+ def name
238
+ File.basename(pathname)
239
+ end
240
+
241
+ def get_arg
242
+ CommandLine::OptionParser::GET_ARGS
243
+ end
244
+ alias :get_args :get_arg
245
+
246
+ def append_arg
247
+ CommandLine::OptionParser::GET_ARG_ARRAY
248
+ end
249
+
250
+ def self.run
251
+ @@app.new.main if ($0 == @@appname)
252
+ rescue => err
253
+ puts "ERROR: #{err}"
254
+ exit(-1)
255
+ end
256
+
257
+ def self.inherited(klass)
258
+ @@appname = caller[0][/.*:/][0..-2]
259
+ @@app = klass
260
+ at_exit { @@app.run }
261
+ end
262
+
263
+ def main
264
+ #raise(MissingMainError, "Method #main must be defined in class #{@@app}.")
265
+ @@app.class_eval %{ def main; end }
266
+ end
267
+
268
+ def _help
269
+ {
270
+ :names => %w(--help -h),
271
+ :arg_arity => [0,0],
272
+ :opt_description => "Displays help page.",
273
+ :arg_description => "",
274
+ :opt_found => lambda { puts man; exit },
275
+ :opt_not_found => false
276
+ }
277
+ end
278
+
279
+ def _verbose
280
+ {
281
+ :names => %w(--verbose -v),
282
+ :arg_arity => [0,0],
283
+ :opt_description => "Sets verbosity level. Subsequent "+
284
+ "flags increase verbosity level",
285
+ :arg_description => "",
286
+ :opt_found => lambda { @verbose ||= -1; @verbose += 1 },
287
+ :opt_not_found => nil
288
+ }
289
+ end
290
+
291
+ def _version
292
+ {
293
+ :names => %w(--version -V),
294
+ :arg_arity => [0,0],
295
+ :opt_description => "Displays application version.",
296
+ :arg_description => "",
297
+ :opt_found => lambda {
298
+ begin
299
+ version
300
+ rescue
301
+ puts "No version specified"
302
+ end;
303
+ exit
304
+ },
305
+ :opt_not_found => nil
306
+ }
307
+ end
308
+
309
+ def _debug
310
+ {
311
+ :names => %w(--debug -d),
312
+ :arg_arity => [0,0],
313
+ :opt_description => "Sets debug to true.",
314
+ :arg_description => "",
315
+ :opt_found => lambda { $DEBUG = true }
316
+ }
317
+ end
318
+ end#class Application
319
+ end#module CommandLine
320
+
@@ -0,0 +1,16 @@
1
+ # $Id$
2
+ # $Source$
3
+ #
4
+ # Author: Jim Freeze
5
+ # Copyright (c) 2005
6
+ #
7
+ # =DESCRIPTION
8
+ # Loader
9
+ #
10
+ # =Revision History
11
+ # Jim.Freeze 2005/06/14 Birthday
12
+ #
13
+
14
+ require 'commandline/optionparser/option'
15
+ require 'commandline/optionparser/optionparser'
16
+ require 'commandline/optionparser/optiondata'
@@ -0,0 +1,180 @@
1
+ # $Id$
2
+ # $Source$
3
+ #
4
+ # Author: Jim Freeze
5
+ # Copyright (c) 2005 Jim Freeze
6
+ #
7
+ # =DESCRIPTION
8
+ # A very flexible commandline parser
9
+ #
10
+ # =Revision History
11
+ # Jim.Freeze 04/01/2005 Birthday
12
+ #
13
+
14
+ module CommandLine
15
+
16
+ class Option
17
+ class OptionError < StandardError; end
18
+ class InvalidOptionNameError < OptionError; end
19
+ class InvalidArgumentError < OptionError; end
20
+ class MissingOptionNameError < OptionError; end
21
+ class InvalidArgumentArityError < OptionError; end
22
+ class MissingPropertyError < OptionError; end
23
+ class InvalidPropertyError < OptionError; end
24
+ class InvalidConstructionError < OptionError; end
25
+
26
+ attr_accessor :posix
27
+
28
+ #
29
+ GENERAL_OPT_EQ_ARG_RE = /^(-{1,2}[a-zA-Z]+[-_a-zA-Z0-9]*)=(.*)$/ # :nodoc:
30
+ GNU_OPT_EQ_ARG_RE = /^(--[a-zA-Z]+[-_a-zA-Z0-9]*)=(.*)$/
31
+ #OPTION_RE = /^-{1,2}([a-zA-Z]+\w*)(.*)/
32
+ #UNIX_OPT_EQ_ARG_RE = /^(-[a-zA-Z])=(.*)$/
33
+ #UNIX_OPT_EQorSP_ARG_RE = /^(-[a-zA-Z])(=|\s+)(.*)$/
34
+
35
+ POSIX_OPTION_RE = /^-[a-zA-Z]?$/
36
+ # need to change this to support - and --
37
+ NON_POSIX_OPTION_RE = /^(-|-{1,2}[a-zA-Z_]+[-_a-zA-Z0-9]*)/
38
+
39
+ PROPERTIES = [ :arg_arity, :opt_description, :arg_description,
40
+ :opt_found, :opt_not_found, :posix
41
+ ]
42
+
43
+ FLAG_BASE_OPTS = {
44
+ :arg_arity => [0,0],
45
+ # :opt_description => nil,
46
+ :arg_description => "",
47
+ :opt_found => true,
48
+ :opt_not_found => false
49
+ }
50
+
51
+ # You get these without asking for them
52
+ DEFAULT_OPTS = {
53
+ :arg_arity => [1,1],
54
+ :opt_description => "",
55
+ :arg_description => "",
56
+ :opt_found => true,
57
+ :opt_not_found => false
58
+ }
59
+
60
+ #
61
+ # Option.new(:flag, :posix => true, :names => %w(--opt))
62
+ #
63
+ # TODO: Should we test and raise key is not one of :names, opt_description, ...
64
+ # This will prevent typos. Can users add properties to an Option that are their own?
65
+ def initialize(*all)
66
+ @posix = false
67
+
68
+ raise(MissingPropertyError,
69
+ "No properties specified for new #{self.class}.") if all.empty?
70
+
71
+ until Hash === all[0]
72
+ case (prop = all.shift)
73
+ when :flag then @flag = true
74
+ when :posix then @posix = true
75
+ else
76
+ raise(InvalidPropertyError, "Unknown option setting '#{prop}'.")
77
+ end
78
+ end
79
+
80
+ type = @flag.nil? ? :default : :flag
81
+ merge_hash =
82
+ case type
83
+ when :flag then FLAG_BASE_OPTS
84
+ when :default then DEFAULT_OPTS
85
+ else raise(InvalidConstructionError,
86
+ "Invalid arguments to Option.new. Must be a property hash with "+
87
+ "keys [:names, :arg_arity, :opt_description, :arg_description, "+
88
+ ":opt_found, :opt_not_found] or "+
89
+ "an option type [:flag, :default].")
90
+ end
91
+
92
+ @properties = {}.merge(merge_hash)
93
+ all.each { |properties|
94
+ raise(InvalidPropertyError,
95
+ "Don't understand argument of type '#{properties.class}' => "+
96
+ "#{properties.inspect} passed to #{self.class}.new. Looking "+
97
+ "for type Hash.") unless properties.kind_of?(Hash)
98
+
99
+ @properties.merge!(properties)
100
+ }
101
+
102
+ @properties[:names] = [@properties[:names]] unless
103
+ @properties[:names].kind_of?(Array)
104
+
105
+ arg_arity = @properties[:arg_arity]
106
+ @properties[:arg_arity] = [arg_arity, arg_arity] unless
107
+ arg_arity.kind_of?(Array)
108
+
109
+ raise "Invalid value for arg_arity '#{arg_arity}'." unless
110
+ arg_arity.kind_of?(Array) || arg_arity.kind_of?(Fixnum)
111
+
112
+ raise(InvalidArgumentArityError,
113
+ "Conflicting value given to new option: :flag "+
114
+ "and :arg_arity = #{@properties[:arg_arity].inspect}.") if
115
+ :flag == type && [0,0] != @properties[:arg_arity]
116
+
117
+ names = @properties[:names]
118
+ raise(MissingOptionNameError,
119
+ "Attempt to create an Option without :names defined.") if
120
+ names.nil? || names.empty?
121
+
122
+ names.each { |name| check_option_name(name) }
123
+ validate_arity @properties[:arg_arity]
124
+
125
+ create_opt_description if :flag == type
126
+ end
127
+
128
+ def create_opt_description
129
+ return if @properties.has_key?(:opt_description)
130
+ word = @properties[:names].grep(/^--\w.+/)
131
+ if word.empty?
132
+ @properties[:opt_description] = ""
133
+ else
134
+ @properties[:opt_description] = "Sets #{word.first[2..-1]} to true."
135
+ end
136
+ end
137
+
138
+ def check_option_name(name)
139
+ raise(InvalidOptionNameError,
140
+ "Option name '#{name}' contains invalid space.") if /\s+/.match(name)
141
+
142
+ if @posix
143
+ raise(InvalidOptionNameError,
144
+ "Option name '#{name}' is invalid.") unless POSIX_OPTION_RE.match(name)
145
+ else
146
+ raise(InvalidOptionNameError,
147
+ "Option name '#{name}' is invalid.") unless NON_POSIX_OPTION_RE.match(name)
148
+ end
149
+ end
150
+
151
+ def validate_arity(arity)
152
+ raise ":arg_arity is nil" if arity.nil?
153
+ min, max = *arity
154
+
155
+ raise(InvalidArgumentArityError, "Minimum argument arity '#{min}' must be "+
156
+ "greater than or equal to 0.") unless min >= 0
157
+ raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
158
+ "greater than or equal to -1.") if max < -1
159
+ raise(InvalidArgumentArityError, "Maximum argument arity '#{max}' must be "+
160
+ "greater than minimum arg_arity '#{min}'.") if max < min && max != -1
161
+ if @posix
162
+ raise(InvalidArgumentArityError, "Posix options only support :arg_arity "+
163
+ "of [0,0] or [1,1].") unless ([0,0] == arity) || ([1,1] == arity)
164
+ end
165
+ end
166
+
167
+ def method_missing(sym, *args)
168
+ raise "Unknown property '#{sym}' for option
169
+ #{@properties[:names].inspect unless @properties[:names].nil?}." unless
170
+ @properties.has_key?(sym) || PROPERTIES.include?(sym)
171
+ @properties[sym, *args]
172
+ end
173
+
174
+ def to_hash
175
+ Marshal.load(Marshal.dump(@properties))
176
+ end
177
+
178
+ end#class Option
179
+
180
+ end#module CommandLine