argparser 1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/LICENSE.txt +20 -0
- data/README.md +0 -0
- data/argparser.gemspec +19 -0
- data/lib/argparser.rb +366 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3cff1283a52db9d44ad99667cf617ce8ca7c9a3f
|
4
|
+
data.tar.gz: 7d5976fa654b6d36a2a02a3daf2deefe97f86703
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f7c962841f1173cd972ed7dff430f99f67c78822aed82df1af28242299c2961b034548dbd8913c0aafb48a3cdadfb348ec15a9fffdebf238e0639e0f849d498
|
7
|
+
data.tar.gz: 6384e7a3e0be42426ca87b022f6ef8899163c22166c1a47f12e9295bfcafbd436cadd8688b6e467513c7ce2ff159302b65ca6b4735944e2b50a06776dd4cca0c
|
data/.gitignore
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
MIT License, http://opensource.org/licenses/MIT
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
File without changes
|
data/argparser.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
Gem::Specification.new do |spec|
|
3
|
+
spec.name = 'argparser'
|
4
|
+
spec.version = '1.0.rc1'
|
5
|
+
spec.authors = ['sinm']
|
6
|
+
spec.email = 'sinm.sinm@gmail.com'
|
7
|
+
spec.summary = 'Command line argument parser'
|
8
|
+
spec.description = '== Trying to follow POSIX and GNU guidelines'
|
9
|
+
spec.homepage = 'https://github.com/sinm/argparser'
|
10
|
+
spec.license = 'MIT'
|
11
|
+
spec.files = `git ls-files -z`.split("\x0")
|
12
|
+
spec.require_paths = ['lib']
|
13
|
+
|
14
|
+
#spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
|
+
#spec.test_files = spec.files.grep(%r{^test/})
|
16
|
+
#spec.add_development_dependency "bundler", "~> 1.7"
|
17
|
+
#spec.add_development_dependency "rake", "~> 10.0"
|
18
|
+
#spec.add_development_dependency "minitest" if RUBY_VERSION > '1.8'
|
19
|
+
end
|
data/lib/argparser.rb
ADDED
@@ -0,0 +1,366 @@
|
|
1
|
+
|
2
|
+
#__END__
|
3
|
+
module Tulz
|
4
|
+
def hash2vars!(hash)
|
5
|
+
hash.each do |k, v|
|
6
|
+
next unless self.respond_to?(k)
|
7
|
+
instance_variable_set("@#{k}", v)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
instance_variables.reduce({}) { |hash, var|
|
13
|
+
hash[var[1..-1]] = instance_variable_get(var)
|
14
|
+
hash }
|
15
|
+
end
|
16
|
+
|
17
|
+
def safe_return(str)
|
18
|
+
eval(str)
|
19
|
+
rescue Exception
|
20
|
+
# intentionally left blank
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ArgParser
|
25
|
+
include Tulz
|
26
|
+
OUT_VERSION = '%s %s %s'
|
27
|
+
OUT_COPYRIGHT = 'Copyright (C) %s'
|
28
|
+
OUT_LICENSE = 'License: %s'
|
29
|
+
OUT_HOMEPAGE = '%s home page: %s'
|
30
|
+
OUT_BUGS = 'Report bugs to: %s'
|
31
|
+
OUT_MANIFEST_EXPECTED = 'Property expected through the manifest: %s'
|
32
|
+
OUT_MULTIPLE_INPUTS = 'Multiple input argument not allowed in the middle: %s'
|
33
|
+
OUT_MULTIPLE_NAMES = 'Multiple names for the input argument: %s'
|
34
|
+
OUT_OPTION_NULL = 'Empty name for option'
|
35
|
+
OUT_REQUIRED = 'Required input argument after optional one: %s'
|
36
|
+
OUT_REQUIRED_DEFAULT = 'Required option has default value: %s'
|
37
|
+
OUT_UNEXPECTED_ARGUMENT = 'Unexpected argument: %s'
|
38
|
+
OUT_UNKNOWN_OPTION = 'Unknown option: %s'
|
39
|
+
OUT_OPTION_ARGUMENT_EXPECTED = 'Expected argument for the option: %s'
|
40
|
+
OUT_SINGLE_OPTION = 'Multiple options not allowed: %s'
|
41
|
+
OUT_OPTION_EXPECTED = 'Expected option: %s'
|
42
|
+
OUT_ARGUMENT_EXPECTED = 'Expected argument: %s'
|
43
|
+
OUT_UNIQUE_NAME = 'Option name should be unique: %s'
|
44
|
+
INVALID_OPTION = 'Invalid option: %s'
|
45
|
+
OPT_HELP = 'help'
|
46
|
+
OPT_VERSION = 'version'
|
47
|
+
OPT_ENOUGH = '--'
|
48
|
+
|
49
|
+
class Option
|
50
|
+
include Tulz
|
51
|
+
attr_reader :names # Names of an option (short, long, etc.)
|
52
|
+
attr_reader :argument # Name of an argument, if present
|
53
|
+
attr_reader :help # Help string for an option
|
54
|
+
attr_reader :validate # Lambda(option, parser) to validate an option
|
55
|
+
attr_reader :default # Default value for an option
|
56
|
+
attr_reader :input # Option is an input argument
|
57
|
+
attr_reader :required # Option required
|
58
|
+
attr_reader :multiple # Option may occure multiple times
|
59
|
+
attr_reader :count # Option occucences
|
60
|
+
attr_reader :env # Default option set by this ENV VAR, if any
|
61
|
+
attr_reader :eval # Default option set by this eval,
|
62
|
+
# superseded by :env, if any
|
63
|
+
# So, in order: value - env - eval - default
|
64
|
+
attr_accessor :value # Values of an option, Array if multiple
|
65
|
+
|
66
|
+
def name
|
67
|
+
names.first
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize(o_manifest)
|
71
|
+
hash2vars!(o_manifest)
|
72
|
+
@names = Array(names).map(&:to_s).sort{|n1, n2| n1.size <=> n2.size}
|
73
|
+
@value = [] if multiple
|
74
|
+
@count = 0
|
75
|
+
end
|
76
|
+
|
77
|
+
def set_value(v)
|
78
|
+
@count += 1
|
79
|
+
multiple ? (@value << v).flatten! : @value = v
|
80
|
+
end
|
81
|
+
|
82
|
+
def value?
|
83
|
+
multiple ? !value.compact.empty? : !!value
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
str = ''
|
88
|
+
str += (count ? count.to_s : ' ')
|
89
|
+
str += (argument ? 'A' : ' ')
|
90
|
+
str += (validate ? 'V' : ' ')
|
91
|
+
str += (default ? 'D' : ' ')
|
92
|
+
str += (input ? 'I' : ' ')
|
93
|
+
str += (required ? 'R' : ' ')
|
94
|
+
str += (multiple ? 'M' : ' ')
|
95
|
+
str += " #{names.inspect}"
|
96
|
+
str += " #{value.inspect}" if value
|
97
|
+
end
|
98
|
+
|
99
|
+
def synopsis
|
100
|
+
if input
|
101
|
+
s = name.dup
|
102
|
+
s << '...' if multiple
|
103
|
+
s = "[#{s}]" if !required
|
104
|
+
return s
|
105
|
+
else
|
106
|
+
s = names.map{|n| n.size == 1 ? "-#{n}" : "--#{n}"}.join(', ')
|
107
|
+
s << " #{argument}" if argument
|
108
|
+
s = "[#{s}]" if !required
|
109
|
+
s << '...' if multiple
|
110
|
+
return s
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate!(parser)
|
115
|
+
!validate || validate.call(self, parser)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
attr_reader :program # Program name, REQUIRED
|
120
|
+
attr_reader :package # Set to nil if there's no package
|
121
|
+
attr_reader :version # Follow semantic versioning rules, REQUIRED
|
122
|
+
attr_reader :copyright # '2015 Somebody, Inc.',
|
123
|
+
attr_reader :license # Give license headline
|
124
|
+
attr_reader :info # Set additional lines that would follow license
|
125
|
+
attr_reader :bugs # Address to post bug reports
|
126
|
+
attr_reader :homepage # Package or, if absent, program's home page
|
127
|
+
attr_reader :synopsis # Print this if present or construct from options
|
128
|
+
attr_reader :help # Print this if present or construct from options
|
129
|
+
attr_reader :options # Options in a hash,
|
130
|
+
# see ArgParser::Option class' attr_reader
|
131
|
+
|
132
|
+
def [](name)
|
133
|
+
options.find{|o| o.names.include?(name)}
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns array of input args in order
|
137
|
+
def inputs
|
138
|
+
options.select{|o| o.input}
|
139
|
+
end
|
140
|
+
|
141
|
+
def initialize(manifest)
|
142
|
+
manifest = {
|
143
|
+
:options => []
|
144
|
+
}.merge(safe_return('$config.manifest') || {}).merge(manifest)
|
145
|
+
hash2vars!(manifest)
|
146
|
+
@options = (manifest[:options] || manifest['options'] || {}).map do |o|
|
147
|
+
Option.new(o)
|
148
|
+
end
|
149
|
+
if !self['help']
|
150
|
+
options << Option.new(:names => OPT_HELP,
|
151
|
+
:help => 'Print this help and exit.',
|
152
|
+
:validate => (lambda { |o, p|
|
153
|
+
return true if o.count < 1
|
154
|
+
p.terminate(0, p.printed_help) }))
|
155
|
+
end
|
156
|
+
if !self['version']
|
157
|
+
options << Option.new(:names => OPT_VERSION,
|
158
|
+
:help => 'Print version and exit.',
|
159
|
+
:validate => (lambda { |o, p|
|
160
|
+
return true if o.count < 1
|
161
|
+
p.terminate(0, p.printed_version) }))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def parse!(arguments = ARGV)
|
166
|
+
{:program => @program, :version => @version}.each do |k, v|
|
167
|
+
if !v || v.to_s.strip.empty?
|
168
|
+
terminate(2, OUT_MANIFEST_EXPECTED % k)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
is = inputs
|
173
|
+
is.each_with_index do |i, index|
|
174
|
+
if index < is.length-1 and i.multiple
|
175
|
+
terminate(2, OUT_MULTIPLE_INPUTS % i.name)
|
176
|
+
end
|
177
|
+
if i.names.size > 1
|
178
|
+
terminate(2, OUT_MULTIPLE_NAMES % i.name)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
first_optional = is.index{|i| !i.required} || is.size
|
182
|
+
last_required = is.rindex{|i| i.required} || 0
|
183
|
+
if last_required > first_optional
|
184
|
+
terminate(2, OUT_REQUIRED % is[last_required].name)
|
185
|
+
end
|
186
|
+
|
187
|
+
names = {}
|
188
|
+
options.each do |option|
|
189
|
+
option.names.each do |name|
|
190
|
+
if name.strip.empty?
|
191
|
+
terminate(2, OUT_OPTION_NULL)
|
192
|
+
end
|
193
|
+
if names.has_key?(name)
|
194
|
+
terminate(2, OUT_UNIQUE_NAME % name)
|
195
|
+
end
|
196
|
+
names[name] = option
|
197
|
+
end
|
198
|
+
if option.required && option.default
|
199
|
+
terminate(2, OUT_REQUIRED_DEFAULT % option.name)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
[OPT_VERSION, OPT_HELP].each { |o|
|
204
|
+
next unless arguments.include?("--#{o}")
|
205
|
+
o = self[o]
|
206
|
+
o.set_value(nil)
|
207
|
+
o.validate!(self)
|
208
|
+
}
|
209
|
+
|
210
|
+
args = arguments.dup
|
211
|
+
enough = false
|
212
|
+
while (a = args.shift)
|
213
|
+
if a == OPT_ENOUGH
|
214
|
+
enough = true
|
215
|
+
elsif enough || (a =~ /^[^-]/) || (a == '-') # input argument
|
216
|
+
if (input = inputs.find{|i| !i.value || i.multiple})
|
217
|
+
input.set_value(a)
|
218
|
+
else
|
219
|
+
terminate(2, OUT_UNEXPECTED_ARGUMENT % a)
|
220
|
+
end
|
221
|
+
elsif a =~ /^--(.+)/ # long option
|
222
|
+
if $1.size > 1 && option = self[$1]
|
223
|
+
if option.argument
|
224
|
+
if args.empty?
|
225
|
+
terminate(2, $stderr.puts(OUT_OPTION_ARGUMENT_EXPECTED % a))
|
226
|
+
end
|
227
|
+
option.set_value(args.shift)
|
228
|
+
else
|
229
|
+
option.set_value(nil)
|
230
|
+
end
|
231
|
+
else
|
232
|
+
terminate(2, OUT_UNKNOWN_OPTION % a)
|
233
|
+
end
|
234
|
+
elsif a =~ /^-([^-].*)/ # short option, may combine and has an arg at end
|
235
|
+
(opts = $1).chars.to_a.each_with_index do |char, index|
|
236
|
+
if (option = self[char])
|
237
|
+
if option.argument
|
238
|
+
if opts.size-1 == index
|
239
|
+
if args.empty?
|
240
|
+
terminate(2, OUT_OPTION_ARGUMENT_EXPECTED % a)
|
241
|
+
else
|
242
|
+
option.set_value(args.shift)
|
243
|
+
end
|
244
|
+
else
|
245
|
+
option.set_value(opts[index+1..-1])
|
246
|
+
break
|
247
|
+
end
|
248
|
+
else
|
249
|
+
option.set_value(nil)
|
250
|
+
end
|
251
|
+
else
|
252
|
+
terminate(2, OUT_UNKNOWN_OPTION % a)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
else
|
256
|
+
terminate(2, OUT_UNKNOWN_OPTION % a)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
options.each do |option|
|
261
|
+
if !option.value? && (option.argument || option.input) &&
|
262
|
+
((option.env && (e = ENV[option.env])) ||
|
263
|
+
(option.eval && (e = safe_return(option.eval))) ||
|
264
|
+
(!!option.default && (e = option.default)))
|
265
|
+
option.set_value(e)
|
266
|
+
end
|
267
|
+
if !option.multiple && option.count > 1
|
268
|
+
terminate(2, OUT_SINGLE_OPTION % option.name)
|
269
|
+
elsif option.required && option.count < 1
|
270
|
+
if option.input
|
271
|
+
terminate(2, OUT_ARGUMENT_EXPECTED % option.name)
|
272
|
+
else
|
273
|
+
terminate(2, OUT_OPTION_EXPECTED % option.name)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
options.each { |o|
|
279
|
+
next if o.validate!(self)
|
280
|
+
terminate(2, INVALID_OPTION % o.name)
|
281
|
+
}
|
282
|
+
self
|
283
|
+
end
|
284
|
+
|
285
|
+
def terminate(code, str = nil)
|
286
|
+
stream = code == 0 ? $stdout : $stderr
|
287
|
+
stream.puts(printed_synopsis) if code != 0
|
288
|
+
if str
|
289
|
+
stream.print(str)
|
290
|
+
stream.puts() unless str[-1] == "\n"
|
291
|
+
end
|
292
|
+
exit!(code)
|
293
|
+
end
|
294
|
+
|
295
|
+
def printed_version
|
296
|
+
pk = (pk = @package) ? "(#{pk})" : ''
|
297
|
+
str = ''
|
298
|
+
str << (OUT_VERSION % [@program, pk, @version]) + "\n"
|
299
|
+
str << (OUT_COPYRIGHT % @copyright) + "\n"
|
300
|
+
str << (OUT_LICENSE % @license) + "\n"
|
301
|
+
end
|
302
|
+
|
303
|
+
def printed_help
|
304
|
+
str = printed_synopsis + "\n"
|
305
|
+
str << info + "\n\n" if info
|
306
|
+
if help
|
307
|
+
str << help + "\n"
|
308
|
+
else
|
309
|
+
opts = []
|
310
|
+
options.select{|o| !o.input}.each do |o|
|
311
|
+
next unless h = o.help
|
312
|
+
h << "\n\tDefaults to: #{o.default}" if o.default
|
313
|
+
opts << [o.synopsis, h]
|
314
|
+
end
|
315
|
+
inputs.each do |i|
|
316
|
+
next unless i.help
|
317
|
+
help = i.help
|
318
|
+
help << "\n\tDefaults to: #{i.default}" if i.default
|
319
|
+
opts << [i.synopsis, help]
|
320
|
+
end
|
321
|
+
opts.each do |o|
|
322
|
+
str << "%s\n\t%s\n" % [o.first, o.last]
|
323
|
+
end
|
324
|
+
# term_width = ENV['COLUMNS'] || `tput columns` || 80
|
325
|
+
# name_width = opts.reduce(0){|max, o| (sz = o.first.size) > max ? sz : max}
|
326
|
+
# help_width = term_width - (name_width += 1)
|
327
|
+
# if help_width < 32...
|
328
|
+
end
|
329
|
+
str << (OUT_BUGS % @bugs) + "\n" if bugs
|
330
|
+
what = @package || @program
|
331
|
+
str << (OUT_HOMEPAGE % [what, @homepage]) + "\n" if homepage
|
332
|
+
str
|
333
|
+
end
|
334
|
+
|
335
|
+
def printed_synopsis
|
336
|
+
s = synopsis ||
|
337
|
+
(options.select{|o| !o.input} + inputs).map{|o| o.synopsis}.join(' ')
|
338
|
+
"#{program} #{s}"
|
339
|
+
end
|
340
|
+
|
341
|
+
if __FILE__ == $0 # Some selftests
|
342
|
+
$stdout.sync = true
|
343
|
+
$stderr.sync = true
|
344
|
+
args = ArgParser.new(
|
345
|
+
:version => '00',
|
346
|
+
:program => 'exec',
|
347
|
+
:info => 'executes command over a group of servers',
|
348
|
+
:options => [{
|
349
|
+
:names => 'group',
|
350
|
+
:input => true,
|
351
|
+
:required => true,
|
352
|
+
:help => 'Name of a group'
|
353
|
+
},{
|
354
|
+
:names => 'command',
|
355
|
+
:input => true,
|
356
|
+
:required => true,
|
357
|
+
:multiple => true,
|
358
|
+
:help => 'command to execute'
|
359
|
+
}]
|
360
|
+
).parse!(%w[--help])
|
361
|
+
|
362
|
+
puts args.options.map(&:to_s).join("\n")
|
363
|
+
puts 'OK!'
|
364
|
+
end
|
365
|
+
|
366
|
+
end # class
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: argparser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- sinm
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-03 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "== Trying to follow POSIX and GNU guidelines"
|
14
|
+
email: sinm.sinm@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".gitignore"
|
20
|
+
- LICENSE.txt
|
21
|
+
- README.md
|
22
|
+
- argparser.gemspec
|
23
|
+
- lib/argparser.rb
|
24
|
+
homepage: https://github.com/sinm/argparser
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 1.3.1
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.2.2
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Command line argument parser
|
48
|
+
test_files: []
|
49
|
+
has_rdoc:
|