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 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: