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.
- data/lib/optconfig.rb +529 -0
- 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
|
+
|