optconfig 0.4.4

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.
Files changed (2) hide show
  1. data/lib/optconfig.rb +529 -0
  2. metadata +64 -0
data/lib/optconfig.rb ADDED
@@ -0,0 +1,529 @@
1
+ # $Id$
2
+ # Copyright (C) 2004-2009 TOMITA Masahiro
3
+ # mailto:tommy@tmtm.org
4
+ #
5
+ # = OptConfig
6
+ # Author:: TOMITA Masahiro <tommy@tmtm.org>
7
+ # License:: Ruby's. see http://www.ruby-lang.org/en/LICENSE.txt
8
+ #
9
+ # * OptConfig はコマンドラインオプションのパーサです。
10
+ # * ファイルからオプションを読み込むこともできます。
11
+ # * -s 形式と --long-option 形式の両方を扱うことができます。
12
+ # * 長い形式のオプションは曖昧でなければ補完されます。
13
+ # * usage に使用する文字列を自動的に生成します。
14
+ # * オプションの引数の形式を指定できます。
15
+ #
16
+ # == Download
17
+ # * http://rubyforge.org/frs/?group_id=4777
18
+ # * http://tmtm.org/downloads/ruby/optconfig/
19
+ #
20
+ # == Required
21
+ # * StringValidator http://stringvalidator.rubyforge.org/
22
+ #
23
+ # == Install
24
+ # $ make
25
+ # # make install
26
+ #
27
+ # == Usage
28
+ # require "optconfig"
29
+ # opt = OptConfig.new
30
+ # opt.option "p", "port=num", :format=>1..65535, :default=>110, :description=>"port number (%s)"
31
+ # opt.option "h", "hostname=name", :format=>true, :default=>"localhost", :description=>"hostname (%s)"
32
+ # opt.option "hogehoge", :description=>"enable hogehoge flag"
33
+ #
34
+ # argv = %w[-p 12345 -h 192.168.1.1 arg1 arg2]
35
+ # opt.parse argv # => ["arg1", "arg2"]
36
+ # opt["p"] # => 12345
37
+ # opt["port"] # => 12345 (same as opt["p"])
38
+ # opt["h"] # => "192.168.1.1"
39
+ # opt["hogehoge"] # => nil (means not set)
40
+ # opt["x"] # raise OptConfig::UnknownOption
41
+ # argv # => ["-p", "12345", "-h", "192.168.1.1", "arg1", "arg2"]
42
+ # opt.parse! argv # => ["arg1", "arg2"]
43
+ # argv # => ["arg1", "arg2"]
44
+ #
45
+ # opt.usage は次の文字列を生成します。
46
+ # -p, --port=num port number (12345)
47
+ # -h, --hostname=name hostname (192.168.1.1)
48
+ # --hogehoge enable hogehoge flag
49
+ #
50
+ # === オプション定義
51
+ # OptConfig#option でオプションを定義します。
52
+ # 引数は、オプション名のリストと、オプションの属性を表す Hash です。
53
+ # [オプション名]
54
+ # 1文字の英数字、または2文字以上の英数字と「-」「_」。
55
+ # 同じオプションに複数の名前をつける場合は複数指定します。
56
+ # "long-name=val" の形式で指定すると :argument 属性が true になります。
57
+ # "long-name[=val]" の形式で指定すると :argument 属性が :optional になります。
58
+ #
59
+ # オプション属性(Hash)のキーは以下の通りです。
60
+ # [:argument]
61
+ # オプションが引数を取るかどうか。
62
+ # nil :: format が真の場合は引数必須、偽の場合は引数不要 (デフォルト)。
63
+ # true :: 引数必須。
64
+ # false :: 引数不要。
65
+ # :optional :: 引数を省略可能。省略時は OptConfig#[] は true になる。
66
+ # [:format]
67
+ # オプション引数の形式。
68
+ # true :: 任意。
69
+ # false/nil :: オプションが引数を必要としない (デフォルト)。
70
+ # :boolean :: オプション引数が "1", "true", "enable", "yes", "y", "on" で true、"0", "false", "disable", "no", "n", "off" で false を返す。
71
+ # その他 :: StringValidator の rule オブジェクトとみなして引数の形式をチェックする。
72
+ # [:default]
73
+ # オプションが指定されなかった場合のデフォルト値。
74
+ # [:description]
75
+ # オプションの説明文字列。説明は usage() で出力されます。
76
+ # nil の場合は usage() でオプションについて出力されません。
77
+ # 文字列中の %s はオプションの値に置換されます。
78
+ # [:multiple]
79
+ # 複数指定された場合の振る舞い。
80
+ # true :: 複数指定可能。OptConfig#[] は配列を返す。
81
+ # false/nil :: 複数指定された場合はエラー。
82
+ # :last :: 最後に指定されたものが有効(デフォルト)。
83
+ # [:completion]
84
+ # 長いオプションを補完するかどうか (デフォルト: true)。
85
+ # [:underscore_is_hyphen]
86
+ # アンダースコアをハイフンとみなすかどうか (デフォルト: nil)
87
+ # [:in_config]
88
+ # オプションファイル内に記述可能かどうか (デフォルト: true)。
89
+ # [:proc]
90
+ # parse() 時にオプションを見つける度に実行される Proc オブジェクト。
91
+ # オプション引数の正当性を確認した後に実行されます。
92
+ # ブロック引数は、オプション名, Option オブジェクト, オプション引数です。
93
+ # ブロックの評価結果は OptConfig#[] の戻り値として使用されます。
94
+ # [:pre_proc]
95
+ # :proc と同じですが、オプション引数の正当性の確認前に実行されます。
96
+ # ブロック引数は、オプション名, Option オブジェクト, オプション引数です。
97
+ # ブロックの評価結果はオプション引数として使用されます。
98
+ #
99
+ # === オプションファイル
100
+ # parse よりも前に file= でファイル名を指定するか、OptConfig.new 時に
101
+ # :file 属性を指定すると、そのファイルからオプションを読み込みます。ファ
102
+ # イルで指定されたオプションよりも、parse の引数で指定されたオプション
103
+ # の方が優先度が高いです。
104
+ #
105
+ # オプションファイルの形式は次の通りです:
106
+ # option-name = value
107
+ #
108
+ # option-name には1文字のオプション名は指定できません。
109
+ #
110
+ # 「=」の前後の空白はあってもなくても構いません。「=」は省略可能です。
111
+ # その場合は、オプション名と値との間に一つ以上の空白が必要です。
112
+ #
113
+ # 「#」で始まる行はコメントとみなされます。空行も無視されます。
114
+ # 指定できるオプションは長い形式のオプションだけです。
115
+ #
116
+ # ファイル中に「[_section_name_]」という行を置くと、その行以降がセクショ
117
+ # ンとして扱われます。OptConfig#section= でセクション名を指定すると、指
118
+ # 定したセクションのオプションのみが読み込まれます。OptConfig#section=
119
+ # に配列を設定すると、複数のセクションから読み込みます。
120
+ # OptConfig#section を設定しない場合は、すべてのセクションからオプショ
121
+ # ンを読み込みます。
122
+
123
+ require "stringvalidator"
124
+
125
+ class OptConfig
126
+ class Error < StandardError; end
127
+ # 未知のオプション
128
+ class UnknownOption < Error; end
129
+ # オプションに引数が必要
130
+ class ArgumentRequired < Error; end
131
+ # オプションの引数が不正
132
+ class InvalidArgument < Error; end
133
+ # オプション名が曖昧
134
+ class AmbiguousOption < Error; end
135
+ # オプションに不要な引数が指定された
136
+ class UnnecessaryArgument < Error; end
137
+ # 同じオプションが複数指定された
138
+ class DuplicatedOption < Error; end
139
+
140
+ # == 初期化
141
+ # default_attr には各オプション属性のデフォルト値を Hash で指定可能。
142
+ # オプション属性以外にも以下のものを指定できる。これらは OptConfig オブジェクト自身に影響する。
143
+ # :file :: オプションファイル名 (String)。デフォルト: なし。
144
+ # :section :: オプションファイル名のセクション名 (String または String の配列)。デフォルト: なし。
145
+ # :ignore_unknown_file_option :: オプションファイル内に未知のオプションがあっtた時に無視するか(true)エラーにするか(false)。デフォルト: true。
146
+ # :stop_at_non_option_argument :: オプションでない引数でオプションの解釈をやめるか(true)、それ以降もオプションの解釈を続けるか(false)。デフォルト: false。
147
+ def initialize(default_attr={})
148
+ @default_attr = default_attr
149
+ @option_seq = []
150
+ @options = {}
151
+ @file = default_attr[:file]
152
+ @section = default_attr[:section]
153
+ @stop_at_non_option_argument = default_attr[:stop_at_non_option_argument]
154
+ @ignore_unknown_file_option = default_attr.key?(:ignore_unknown_file_option) ? default_attr[:ignore_unknown_file_option] : true
155
+ @obsolete_behavior = false
156
+ @specified = {} # オプションが指定されたかどうかを保持
157
+ end
158
+ attr_accessor :file, :section, :ignore_unknown_file_option
159
+
160
+ alias idlist section
161
+ alias idlist= section=
162
+
163
+ # == オプション定義
164
+ # args:: オプション名(String) のリスト、オプションの属性(Hash)
165
+ # === 戻り値
166
+ # Option オブジェクト
167
+ # === 例外
168
+ # RuntimeError:: オプションが既に定義されている
169
+ def option(*args)
170
+ args = args.dup
171
+ if args.last.is_a? Hash
172
+ attr = args.pop
173
+ attr = @default_attr.merge attr
174
+ else
175
+ attr = @default_attr
176
+ end
177
+ args.push attr
178
+ opt = Option.new *args
179
+ opt.name.each do |n|
180
+ raise "option #{n} is already defined" if @options.key? n
181
+ @options[n] = opt
182
+ end
183
+ @option_seq << opt
184
+ end
185
+
186
+ # == オプション定義(古いインタフェース)
187
+ # option:: オプション定義(ハッシュ)
188
+ def options=(option)
189
+ @options.clear
190
+ @option_seq.clear
191
+ option.each do |k,v|
192
+ v = [v] unless v.is_a? Array
193
+ arg = k.to_a
194
+ arg.push({:format=>v[0], :default=>v[1]})
195
+ opt = Option.new *arg
196
+ opt.name.each do |n|
197
+ raise "option #{n} is already defined" if @options.key? n
198
+ @options[n] = opt
199
+ end
200
+ @option_seq << opt
201
+ end
202
+ @obsolete_behavior = true
203
+ option
204
+ end
205
+
206
+ # == argv のオプションを解析し、オプションを取り除いたものに置き換える
207
+ # argv:: 配列
208
+ # === 戻り値
209
+ # argv:: 残りの引数
210
+ def parse!(argv=[])
211
+ orig_argv_size = argv.size
212
+ ret = []
213
+ @specified.clear
214
+ @option_seq.each do |opt|
215
+ opt.value = opt.ovalue = opt.default
216
+ begin
217
+ opt.value = check_option opt.name.first, opt.default if opt.default.is_a? String
218
+ rescue OptConfig::Error
219
+ # 無視
220
+ end
221
+ end
222
+ parse_file @file if @file
223
+ @specified.clear
224
+
225
+ until argv.empty?
226
+ arg = argv.shift
227
+ case arg
228
+ when "--"
229
+ ret.concat argv
230
+ break
231
+ when /\A--[a-zA-Z0-9_]/
232
+ parse_long_opt arg.sub(/\A--/, ""), argv
233
+ when /\A-[a-zA-Z0-9]/
234
+ parse_short_opt arg.sub(/\A-/, ""), argv
235
+ else
236
+ ret.push arg
237
+ if @stop_at_non_option_argument
238
+ ret.concat argv
239
+ break
240
+ end
241
+ end
242
+ end
243
+
244
+ if @obsolete_behavior
245
+ n = orig_argv_size - ret.size
246
+ argv.replace ret
247
+ return n
248
+ end
249
+ argv.replace ret
250
+ return argv
251
+ end
252
+
253
+ # == argv のオプションを解析する
254
+ # argv:: 文字列の配列
255
+ # === 戻り値
256
+ # argv からオプションを取り除いたもの
257
+ def parse(argv=[])
258
+ parse!(argv.dup)
259
+ end
260
+
261
+ # == ファイルからオプションを読み込む
262
+ # filename:: ファイル名
263
+ # === 例外
264
+ # UnknownOption
265
+ def parse_file(filename)
266
+ cur_sect = nil
267
+ File.open filename do |f|
268
+ f.each_line do |line|
269
+ line.chomp!
270
+ next if line =~ /\A#|\A\s*\z/
271
+ if line =~ /\A\[(.*)\]\z/
272
+ cur_sect = $1
273
+ next
274
+ end
275
+ if @section.nil? or @section.empty? or @section.to_a.include? cur_sect
276
+ name, value = line.split(/\s*=\s*|\s+/, 2)
277
+ begin
278
+ name, = long_option name, false
279
+ raise UnknownOption, "unknown option: #{name}" unless @options[name].in_config
280
+ if value
281
+ parse_long_opt "#{name}=#{value}", [], false
282
+ else
283
+ parse_long_opt name, [], false
284
+ end
285
+ rescue UnknownOption
286
+ raise unless @ignore_unknown_file_option
287
+ end
288
+ end
289
+ end
290
+ end
291
+ end
292
+
293
+ # == オプションの値を返す
294
+ # name:: オプション名
295
+ # === 例外
296
+ # UnknownOption
297
+ def [](name)
298
+ raise UnknownOption, "unknown option: #{name}" unless @options.key? name
299
+ @options[name].value
300
+ end
301
+
302
+ # == オプションの説明文字列を返す
303
+ def usage
304
+ ret = ""
305
+ @option_seq.each do |opt|
306
+ next unless opt.description
307
+ short = []
308
+ long = []
309
+ opt.usage_name.each do |n|
310
+ if n.size == 1
311
+ short << "-#{n}"
312
+ else
313
+ long << "--#{n}"
314
+ end
315
+ end
316
+ line = " "+(short+long).join(", ")
317
+ if opt.description.empty?
318
+ ret << line+"\n"
319
+ next
320
+ end
321
+ if line.length >= 25
322
+ line << "\n "
323
+ else
324
+ line << " "*(26-line.length)
325
+ end
326
+ desc = opt.description.gsub(/%s/, opt.ovalue.to_s).split "\n"
327
+ line << desc.shift+"\n"
328
+ desc.each do |d|
329
+ line << " #{d}\n"
330
+ end
331
+ ret << line
332
+ end
333
+ ret
334
+ end
335
+
336
+ private
337
+
338
+ # == オプションに値を設定
339
+ # name:: オプション名
340
+ # value:: 値(String,true,false)
341
+ # === 例外
342
+ # UnknownOption, DuplicatedOption
343
+ def set_option(name, value)
344
+ raise UnknownOption, "unknown option: #{name}" unless @options.key? name
345
+ opt = @options[name]
346
+ ovalue = value
347
+ value = opt.pre_proc.call name, opt, value if opt.pre_proc
348
+ raise DuplicatedOption, "duplicated option: #{name}" if !opt.multiple and @specified[@options[name]]
349
+ value = check_option name, value unless value == true or value == false
350
+ value = opt.proc.call name, opt, value if opt.proc
351
+ if opt.multiple == :last or !opt.multiple
352
+ opt.value = value
353
+ opt.ovalue = ovalue
354
+ else
355
+ unless @specified[@options[name]]
356
+ opt.value = []
357
+ opt.ovalue = []
358
+ end
359
+ opt.value << value
360
+ opt.ovalue << ovalue
361
+ end
362
+ @specified[@options[name]] = true
363
+ end
364
+
365
+ # == オプション名と値の正当性を確認
366
+ # name:: オプション名
367
+ # value:: 値 (String)
368
+ # === 戻り値
369
+ # _value_ の評価結果
370
+ # === 例外
371
+ # UnnecessaryArgument, ArgumentRequired, InvalidArgument
372
+ def check_option(name, value)
373
+ a = @options[name].argument
374
+ f = @options[name].format
375
+ raise UnnecessaryArgument, "option argument is unnecessary: #{name}" if a == false or (a.nil? and !f)
376
+ return true if a == :optional and value.nil?
377
+ raise ArgumentRequired, "argument required: #{name}" if value.nil?
378
+ return value if f == true
379
+ if f == :boolean
380
+ return true if ["1","true","enable","yes","y","on"].include? value.downcase
381
+ return false if ["0","false","disable","no","n","off"].include? value.downcase
382
+ raise InvalidArgument, "invalid boolean value: #{name}"
383
+ end
384
+ begin
385
+ return StringValidator.validate(f, value)
386
+ rescue StringValidator::Error => e
387
+ raise InvalidArgument, "invalid argument for option `#{name}': #{e.message}: #{value}"
388
+ end
389
+ end
390
+
391
+ # == 長い名前のオプションの解釈
392
+ # name:: オプション名
393
+ # argv:: それ以降の文字列配列
394
+ # completion:: 補完の有無
395
+ # === 戻り値
396
+ # Option オブジェクト
397
+ def parse_long_opt(name, argv, completion=true)
398
+ if name.include? "="
399
+ n, v = name.split "=", 2
400
+ n, invert = long_option n, completion
401
+ set_option n, v
402
+ @options[n].value = !@options[n].value if invert
403
+ return @options[n]
404
+ end
405
+ n, invert = long_option name, completion
406
+ opt = @options[n]
407
+ if opt.argument == false or opt.argument == :optional or
408
+ (opt.argument.nil? and !opt.format)
409
+ set_option n, !invert
410
+ return @options[n]
411
+ end
412
+ set_option n, argv.shift
413
+ @options[n].value = !@options[n].value if invert
414
+ return @options[n]
415
+ end
416
+
417
+ # == 短い名前のオプションの解釈
418
+ # name:: オプション名
419
+ # argv:: それ以降の文字列配列
420
+ # === 戻り値
421
+ # Option オブジェクト
422
+ def parse_short_opt(name, argv)
423
+ arg = name.dup
424
+ until arg.empty?
425
+ n = arg.slice!(0, 1)
426
+ raise UnknownOption, "unknown option: #{n}" unless @options.key? n
427
+ opt = @options[n]
428
+ if opt.argument == false or (opt.argument.nil? and !opt.format)
429
+ set_option n, true
430
+ next
431
+ end
432
+ unless arg.empty?
433
+ set_option n, arg
434
+ return @options[n]
435
+ end
436
+ if opt.argument == :optional
437
+ set_option n, true
438
+ return @options[n]
439
+ end
440
+ set_option n, argv.shift
441
+ return @options[n]
442
+ end
443
+ end
444
+
445
+ # == 長いオプション名の確認
446
+ # name:: オプション名
447
+ # completion:: 補完の有無
448
+ # === 戻り値
449
+ # String :: 補完後オプション名
450
+ # true/false :: "no-" prefix 指定?
451
+ # === 例外
452
+ # UnknownOption
453
+ def long_option(name, completion)
454
+ n = completion ? long_option_completion(name) : name.size > 1 && @options.key?(name) ? name : nil
455
+ return n, false if n
456
+ if name =~ /\Ano-([a-zA-Z0-9][a-zA-Z0-9_-]+)\z/
457
+ n = completion ? long_option_completion($1) : name.size > 1 && @options.key?(name) ? name : nil
458
+ return n, true if n and (@options[n].may_not_take_argument? or @options[n].format == :boolean)
459
+ end
460
+ name2 = name.gsub(/_/, "-")
461
+ n = completion ? long_option_completion(name2) : name.size > 1 && @options.key?(name2) ? name2 : nil
462
+ return n, false if n and @options[n].underscore_is_hyphen
463
+ if name2 =~ /\Ano-([a-zA-Z0-9][a-zA-Z0-9_-]+)\z/
464
+ n = completion ? long_option_completion($1) : name.size > 1 && @options.key?(name2) ? name2 : nil
465
+ return n, true if n and @options[n].underscore_is_hyphen and (@options[n].may_not_take_argument? or @options[n].format == :boolean)
466
+ end
467
+ raise UnknownOption, "unknown option: #{name}"
468
+ end
469
+
470
+ # == 長いオプション名の補完
471
+ # opt:: オプション名
472
+ # === 戻り値
473
+ # オプション名 / nil(未知のオプション)
474
+ # === 例外
475
+ # AmbiguousOption
476
+ def long_option_completion(opt)
477
+ return opt if @options.key? opt
478
+ candidate = @options.keys.sort.select{|k| opt == k[0, opt.size]}.select{|k| @options[k].completion}
479
+ raise AmbiguousOption, "ambiguous option: #{opt} (candidate are #{candidate.join(", ")})" if candidate.size > 1
480
+ return nil if candidate.empty? or not @options[candidate.first].completion
481
+ return candidate.first
482
+ end
483
+
484
+ # = オプション定義
485
+ class Option
486
+ # == 初期化
487
+ # args:: オプション名(String) のリスト、オプションの属性(Hash)
488
+ def initialize(*args)
489
+ name = args.dup
490
+ if name.last.is_a? Hash
491
+ attr = name.pop
492
+ else
493
+ attr = {}
494
+ end
495
+ raise "no option name: #{args.inspect}" if name.empty?
496
+ argument = nil
497
+ @usage_name = name
498
+ @name = name.to_a.map do |n|
499
+ unless n =~ /\A([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9_-]+)(\[?=.*)?\z/
500
+ raise "invalid option name: #{n.inspect}"
501
+ end
502
+ n = $1
503
+ argument = $2.nil? ? nil : $2 =~ /\A\[/ ? :optional : true
504
+ n
505
+ end
506
+ @argument = attr.key?(:argument) ? attr[:argument] : argument
507
+ @format = attr.key?(:format) ? attr[:format] : @argument ? true : nil
508
+ @default = attr[:default]
509
+ @description = attr[:description]
510
+ @multiple = attr.key?(:multiple) ? attr[:multiple] : :last
511
+ @completion = attr.key?(:completion) ? attr[:completion] : true
512
+ @underscore_is_hyphen = attr[:underscore_is_hyphen]
513
+ @in_config = attr.key?(:in_config) ? attr[:in_config] : true
514
+ @proc = attr[:proc]
515
+ @pre_proc = attr[:pre_proc]
516
+ @value = @default
517
+ @ovalue = @default
518
+ end
519
+ attr_reader :name, :argument, :format, :default, :description, :multiple
520
+ attr_reader :completion, :underscore_is_hyphen, :in_config, :proc, :pre_proc
521
+ attr_reader :usage_name
522
+ attr_accessor :value, :ovalue
523
+
524
+ def may_not_take_argument?
525
+ argument == false or argument == :optional or (argument.nil? and !format)
526
+ end
527
+ end
528
+
529
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: optconfig
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.4
5
+ platform: ruby
6
+ authors:
7
+ - Tomita Masahiro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-28 00:00:00 +09:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: stringvalidator
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: OptConfig is a parser of command line option
26
+ email: tommy@tmtm.org
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/optconfig.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/tmtm/optconfig
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=utf-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project: optconfig
59
+ rubygems_version: 1.3.4
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: OptConfig is a parser of command line option
63
+ test_files: []
64
+