argparser 1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,19 @@
1
+ *~
2
+ ~*
3
+ .DS_Store
4
+ ._.DS_Store
5
+ thumbs.db
6
+ *.log
7
+ *.pid
8
+ *.dump
9
+ *.prof
10
+ *.prof.gif
11
+ *.prof.symbols
12
+ .svn/
13
+ .hg/
14
+ *.sublime-workspace
15
+ .env
16
+ .rbenv-version
17
+ .ruby-version
18
+ Gemfile.lock
19
+ core
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: