argparser 1.0.rc1
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.
- 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:
|