argparser 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.8.7"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - "2.1" # latest 2.1.x
7
+ - "2.2" # latest 2.2.x
8
+ - "jruby-18mode"
9
+ - "jruby-19mode"
10
+ script:
11
+ CODECLIMATE_REPO_TOKEN=3a1b48c656a57cdd02824699a1352f8a0648e9b41f3df18323b8c48bab22b5ce bundle exec rake test
12
+ branches:
13
+ - "master"
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+ gem 'codeclimate-test-reporter', :group => :development,
4
+ :require => nil,
5
+ :platforms => [:ruby_20]
data/HISTORY.md ADDED
@@ -0,0 +1,10 @@
1
+ # 1.0.1
2
+ 2015-04-14
3
+
4
+ * Some minor fixes and refactorings
5
+ * MRI-1.8, jruby-1.8-mode support
6
+
7
+ # 1.0.0
8
+ 2015-04-03
9
+
10
+ * First public release
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # argparser
2
- Command line argument parser library, trying to follow POSIX and GNU guidelines.
2
+ Yet another ruby command line argument parser library.
3
+
4
+ [![Build Status](https://travis-ci.org/sinm/argparser.svg?branch=master)](https://travis-ci.org/sinm/argparser) [![Gem Version](https://badge.fury.io/rb/argparser.svg)](http://badge.fury.io/rb/argparser) [![Dependency Status](https://gemnasium.com/sinm/argparser.svg)](https://gemnasium.com/sinm/argparser) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/sinm/argparser/master/LICENSE.txt) [![Code Climate](https://codeclimate.com/github/sinm/argparser/badges/gpa.svg)](https://codeclimate.com/github/sinm/argparser) [![Test Coverage](https://codeclimate.com/github/sinm/argparser/badges/coverage.svg)](https://codeclimate.com/github/sinm/argparser) [![Inline docs](http://inch-ci.org/github/sinm/argparser.svg?branch=master)](http://inch-ci.org/github/sinm/argparser)
3
5
 
4
6
  ## Installation
5
- `gem install argparser`, as usual.
7
+ `gem install argparser` as usual.
6
8
 
7
9
  ## Usage by example
8
10
  Suppose there's a file named `example.rb` like this:
@@ -41,6 +43,8 @@ puts args['mode'].value.inspect # So we could use our options...
41
43
  puts args['file'].value # Prints contents of a file
42
44
  ````
43
45
 
46
+ This file located here: `lib/argparser/examples/example.rb`.
47
+
44
48
  Now, let's look at the output of example given in various cases.
45
49
 
46
50
  `$ ruby example.rb` is unsufficient:
@@ -82,12 +86,12 @@ Unknown option: a
82
86
  ````
83
87
 
84
88
  ## Consider more rules
85
- * `--help` and `--version` options provided for free unless specified.
86
- * printed synopsys provided for free unless specified
89
+ * `--help` and `--version` options provided for free unless specified
90
+ * printed synopsis provided for free unless specified
87
91
  * `:default` used if option has :argument and no value given, lowest priority
88
92
  * `:env => 'ENV_VAR'` to pick default value from ENV, high priority
89
93
  * `:eval => 'ruby expr'` to pick default from eval(...), useful for read defaults from config files, so it has low priority
90
- * `--`-argument honored
94
+ * `--` argument honored
91
95
 
92
96
  ## Documentation
93
97
  This README is all i could say in a rush. No other documentation provided at this moment, see the sources.
@@ -97,6 +101,3 @@ Don't hesistate to leave a report.
97
101
 
98
102
  ## License
99
103
  MIT for now.
100
-
101
- ## TODO
102
- * Go steal milk for the hazards applied.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env rake
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new :test do |t|
5
+ # t.test_files = FileList['spec/*_spec.rb']
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ t.libs.push 'spec'
8
+ end
9
+
10
+ task :build do
11
+ system('gem build argparser.gemspec')
12
+ # gem push argparser-1.0.0.gem
13
+ # gem uninstall argparser
14
+ # gem install argparser-1.0.0.gem
15
+ end
16
+
17
+ task :default => :test
data/argparser.gemspec CHANGED
@@ -1,19 +1,22 @@
1
1
  # coding: utf-8
2
- Gem::Specification.new do |spec|
3
- spec.name = 'argparser'
4
- spec.version = '1.0.0'
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'
2
+ Gem::Specification.new do |s|
3
+ s.name = 'argparser'
4
+ s.version = '1.0.1'
5
+ s.authors = ['sinm']
6
+ s.email = 'sinm.sinm@gmail.com'
7
+ s.summary = 'Yet another ruby command line argument parser library'
8
+ s.description = '== Yet another ruby command line argument parser library'
9
+ s.homepage = 'https://github.com/sinm/argparser'
10
+ s.license = 'MIT'
11
+ s.files = `git ls-files -z`.split("\x0")
12
+ s.test_files = `git ls-files -z spec/`.split("\0")
13
+ s.require_paths = ['lib']
14
+ # s.extra_rdoc_files = 'README.md'
15
+ # s.rdoc_options << '--title' << 'argparser' <<
16
+ # '--main' << 'README' <<
17
+ # '--markup' << 'markdown' <<
18
+ # '--line-numbers'
19
+ s.add_development_dependency 'bundler', '~> 1'
20
+ s.add_development_dependency 'rake', '~> 10'
21
+ s.add_development_dependency 'minitest', '~> 4'
19
22
  end
@@ -0,0 +1,27 @@
1
+ {
2
+ "folders":
3
+ [
4
+ {
5
+ "path": "."
6
+ }
7
+ ],
8
+ "build_systems":
9
+ [
10
+ {
11
+ "name": "Run tests",
12
+ "cmd": ["bundle", "exec", "rake", "test"],
13
+ "selector": "source.rb"
14
+ }
15
+ ],
16
+ "settings":
17
+ {
18
+ "ensure_newline_at_eof_on_save": true,
19
+ "rulers":
20
+ [
21
+ 80
22
+ ],
23
+ "tab_size": 2,
24
+ "translate_tabs_to_spaces": true,
25
+ "trim_trailing_white_space_on_save": true
26
+ }
27
+ }
data/lib/argparser.rb CHANGED
@@ -1,28 +1,16 @@
1
- #__END__
2
- module Tulz
3
- def hash2vars!(hash)
4
- hash.each do |k, v|
5
- next unless self.respond_to?(k)
6
- instance_variable_set("@#{k}", v)
7
- end
8
- end
1
+ # coding: utf-8
2
+ require 'stringio'
9
3
 
10
- def to_hash
11
- instance_variables.reduce({}) { |hash, var|
12
- hash[var[1..-1]] = instance_variable_get(var)
13
- hash }
14
- end
15
-
16
- def safe_return(str)
17
- eval(str)
18
- rescue Exception
19
- # intentionally left blank
20
- end
21
- end
4
+ require 'argparser/tools'
5
+ require 'argparser/option'
6
+ require 'argparser/default_parser'
22
7
 
23
8
  class ArgParser
24
- include Tulz
25
- OUT_VERSION = '%s %s %s'
9
+ include Tools
10
+ include DefaultParser
11
+
12
+ # Output messages used in the #terminate method
13
+ OUT_VERSION = '%s%s %s'
26
14
  OUT_COPYRIGHT = 'Copyright (C) %s'
27
15
  OUT_LICENSE = 'License: %s'
28
16
  OUT_HOMEPAGE = '%s home page: %s'
@@ -40,7 +28,7 @@ class ArgParser
40
28
  OUT_OPTION_EXPECTED = 'Expected option: %s'
41
29
  OUT_ARGUMENT_EXPECTED = 'Expected argument: %s'
42
30
  OUT_UNIQUE_NAME = 'Option name should be unique: %s'
43
- INVALID_OPTION = 'Invalid option: %s'
31
+ OUT_INVALID_OPTION = 'Invalid value for option: %s'
44
32
  OPT_ENOUGH = '--'
45
33
 
46
34
  # These options don't display their synopsis and given for free unless
@@ -49,76 +37,6 @@ class ArgParser
49
37
  OPT_VERSION = 'version'
50
38
  OPTS_RESERVED = [OPT_HELP, OPT_VERSION]
51
39
 
52
- class Option
53
- include Tulz
54
- attr_reader :names # Names of an option (short, long, etc.)
55
- attr_reader :argument # Name of an argument, if present
56
- attr_reader :help # Help string for an option
57
- attr_reader :validate # Lambda(option, parser) to validate an option
58
- attr_reader :default # Default value for an option
59
- attr_reader :input # Option is an input argument
60
- attr_reader :required # Option required
61
- attr_reader :multiple # Option may occure multiple times
62
- attr_reader :count # Option occucences
63
- attr_reader :env # Default option set by this ENV VAR, if any
64
- attr_reader :eval # Default option set by this eval,
65
- # superseded by :env, if any
66
- # So, in order: value - env - eval - default
67
- attr_accessor :value # Values of an option, Array if multiple
68
-
69
- def name
70
- names.first
71
- end
72
-
73
- def initialize(o_manifest)
74
- hash2vars!(o_manifest)
75
- @names = Array(names).map(&:to_s).sort{|n1, n2| n1.size <=> n2.size}
76
- @value = [] if multiple
77
- @count = 0
78
- end
79
-
80
- def set_value(v)
81
- @count += 1
82
- multiple ? (@value << v).flatten! : @value = v
83
- end
84
-
85
- def value?
86
- multiple ? !value.compact.empty? : !!value
87
- end
88
-
89
- def to_s
90
- str = ''
91
- str += (count ? count.to_s : ' ')
92
- str += (argument ? 'A' : ' ')
93
- str += (validate ? 'V' : ' ')
94
- str += (default ? 'D' : ' ')
95
- str += (input ? 'I' : ' ')
96
- str += (required ? 'R' : ' ')
97
- str += (multiple ? 'M' : ' ')
98
- str += " #{names.inspect}"
99
- str += " #{value.inspect}" if value
100
- end
101
-
102
- def synopsis
103
- if input
104
- s = name.dup
105
- s << '...' if multiple
106
- s = "[#{s}]" if !required
107
- return s
108
- else
109
- s = names.map{|n| n.size == 1 ? "-#{n}" : "--#{n}"}.join(', ')
110
- s << " #{argument}" if argument
111
- s = "[#{s}]" if !required
112
- s << '...' if multiple
113
- return s
114
- end
115
- end
116
-
117
- def validate!(parser)
118
- !validate || validate.call(self, parser)
119
- end
120
- end
121
-
122
40
  attr_reader :program # Program name, REQUIRED
123
41
  attr_reader :package # Set to nil if there's no package
124
42
  attr_reader :version # Follow semantic versioning rules, REQUIRED
@@ -129,9 +47,10 @@ class ArgParser
129
47
  attr_reader :homepage # Package or, if absent, program's home page
130
48
  attr_reader :synopsis # Print this if present or construct from options
131
49
  attr_reader :help # Print this if present or construct from options
132
- attr_reader :options # Options in a hash,
133
- # see ArgParser::Option class' attr_reader
50
+ attr_reader :options # Array of options,
51
+ # see ArgParser::Option class' attr_readers
134
52
 
53
+ # Returns option by any of its names given
135
54
  def [](name)
136
55
  options.find{|o| o.names.include?(name)}
137
56
  end
@@ -142,13 +61,10 @@ class ArgParser
142
61
  end
143
62
 
144
63
  def initialize(manifest)
145
- manifest = {
146
- :options => []
147
- }.merge(safe_return('$config.manifest') || {}).merge(manifest)
64
+ manifest = (safe_return('$config.manifest') || {}).merge(manifest)
148
65
  hash2vars!(manifest)
149
- @options = (manifest[:options] || manifest['options'] || {}).map do |o|
150
- Option.new(o)
151
- end
66
+ @options = (manifest[:options] || manifest['options'] || []).
67
+ map {|o| o.kind_of?(Option) ? o : Option.new(o)}
152
68
  if !self['help']
153
69
  options << Option.new(:names => OPT_HELP,
154
70
  :help => 'Print this help and exit.',
@@ -165,190 +81,50 @@ class ArgParser
165
81
  end
166
82
  end
167
83
 
168
- def parse!(arguments = ARGV)
169
- {:program => @program, :version => @version}.each do |k, v|
170
- if !v || v.to_s.strip.empty?
171
- terminate(2, OUT_MANIFEST_EXPECTED % k)
172
- end
173
- end
174
-
175
- is = inputs
176
- is.each_with_index do |i, index|
177
- if index < is.length-1 and i.multiple
178
- terminate(2, OUT_MULTIPLE_INPUTS % i.name)
179
- end
180
- if i.names.size > 1
181
- terminate(2, OUT_MULTIPLE_NAMES % i.name)
182
- end
183
- end
184
- first_optional = is.index{|i| !i.required} || is.size
185
- last_required = is.rindex{|i| i.required} || 0
186
- if last_required > first_optional
187
- terminate(2, OUT_REQUIRED % is[last_required].name)
188
- end
189
-
190
- names = {}
191
- options.each do |option|
192
- option.names.each do |name|
193
- if name.strip.empty?
194
- terminate(2, OUT_OPTION_NULL)
195
- end
196
- if names.has_key?(name)
197
- terminate(2, OUT_UNIQUE_NAME % name)
198
- end
199
- names[name] = option
200
- end
201
- if option.required && option.default
202
- terminate(2, OUT_REQUIRED_DEFAULT % option.name)
203
- end
204
- end
205
-
206
- OPTS_RESERVED.each { |o|
207
- next unless arguments.include?("--#{o}")
208
- o = self[o]
209
- o.set_value(nil)
210
- o.validate!(self)
211
- }
212
-
213
- args = arguments.dup
214
- enough = false
215
- while (a = args.shift)
216
- if a == OPT_ENOUGH
217
- enough = true
218
- elsif enough || (a =~ /^[^-]/) || (a == '-') # input argument
219
- if (input = inputs.find{|i| !i.value || i.multiple})
220
- input.set_value(a)
221
- else
222
- terminate(2, OUT_UNEXPECTED_ARGUMENT % a)
223
- end
224
- elsif a =~ /^--(.+)/ # long option
225
- if $1.size > 1 && option = self[$1]
226
- if option.argument
227
- if args.empty?
228
- terminate(2, $stderr.puts(OUT_OPTION_ARGUMENT_EXPECTED % a))
229
- end
230
- option.set_value(args.shift)
231
- else
232
- option.set_value(nil)
233
- end
234
- else
235
- terminate(2, OUT_UNKNOWN_OPTION % $1)
236
- end
237
- elsif a =~ /^-([^-].*)/ # short option, may combine and has an arg at end
238
- (opts = $1).chars.to_a.each_with_index do |char, index|
239
- if (option = self[char])
240
- if option.argument
241
- if opts.size-1 == index
242
- if args.empty?
243
- terminate(2, OUT_OPTION_ARGUMENT_EXPECTED % a)
244
- else
245
- option.set_value(args.shift)
246
- end
247
- else
248
- option.set_value(opts[index+1..-1])
249
- break
250
- end
251
- else
252
- option.set_value(nil)
253
- end
254
- else
255
- terminate(2, OUT_UNKNOWN_OPTION % char)
256
- end
257
- end
258
- else
259
- terminate(2, OUT_UNKNOWN_OPTION % a)
260
- end
261
- end
262
-
263
- options.each do |option|
264
- if !option.value? && (option.argument || option.input) &&
265
- ((option.env && (e = ENV[option.env])) ||
266
- (option.eval && (e = safe_return(option.eval))) ||
267
- (!!option.default && (e = option.default)))
268
- option.set_value(e)
269
- end
270
- if !option.multiple && option.count > 1
271
- terminate(2, OUT_SINGLE_OPTION % option.name)
272
- elsif option.required && option.count < 1
273
- if option.input
274
- terminate(2, OUT_ARGUMENT_EXPECTED % option.name)
275
- else
276
- terminate(2, OUT_OPTION_EXPECTED % option.name)
277
- end
278
- end
279
- end
280
-
281
- options.each { |o|
282
- next if o.validate!(self)
283
- terminate(2, INVALID_OPTION % o.name)
284
- }
285
- self
84
+ def terminate(code, str)
85
+ s = StringIO.new
86
+ s.puts(printed_synopsis) if code != 0
87
+ s.puts(str[-1] == "\n" ? str.chop : str)
88
+ on_exit(code, s.string)
286
89
  end
287
90
 
288
- def terminate(code, str = nil)
289
- stream = code == 0 ? $stdout : $stderr
290
- stream.puts(printed_synopsis) if code != 0
291
- if str
292
- stream.print(str)
293
- stream.puts() unless str[-1] == "\n"
294
- end
295
- exit!(code)
91
+ def on_exit(code, message)
92
+ (code == 0 ? $stdout : $stderr).print(message)
93
+ exit(code)
296
94
  end
297
95
 
298
96
  def printed_version
299
- pk = (pk = @package) ? "(#{pk})" : ''
300
- str = ''
301
- str << (OUT_VERSION % [@program, pk, @version]) + "\n"
302
- str << (OUT_COPYRIGHT % @copyright) + "\n"
303
- str << (OUT_LICENSE % @license) + "\n"
97
+ str = StringIO.new
98
+ pk = (pk = @package) ? " (#{pk})" : ''
99
+ str.puts(OUT_VERSION % [program, pk, version])
100
+ str.puts(OUT_COPYRIGHT % copyright) if copyright
101
+ str.puts(OUT_LICENSE % license) if license
102
+ str.string
304
103
  end
305
104
 
306
105
  def printed_help
307
- str = printed_synopsis + "\n"
308
- str << info + "\n\n" if info
106
+ str = StringIO.new
107
+ str.puts(printed_synopsis)
108
+ str.puts(info) if info
309
109
  if help
310
- str << help + "\n"
110
+ str.puts(help)
311
111
  else
312
- opts = []
313
- options.select{|o| !o.input}.each do |o|
314
- next unless h = o.help
315
- h << "\n\tDefaults to: #{o.default}" if o.default
316
- opts << [o.synopsis, h]
317
- end
318
- inputs.each do |i|
319
- next unless i.help
320
- help = i.help
321
- help << "\n\tDefaults to: #{i.default}" if i.default
322
- opts << [i.synopsis, help]
323
- end
324
- opts.each do |o|
325
- str << "%s\n\t%s\n" % [o.first, o.last]
112
+ (options.select{|o| !o.input} + inputs).each do |o|
113
+ str.puts(o.printed_help)
326
114
  end
327
115
  # term_width = ENV['COLUMNS'] || `tput columns` || 80
328
- # name_width = opts.reduce(0){|max, o| (sz = o.first.size) > max ? sz : max}
329
- # help_width = term_width - (name_width += 1)
116
+ # width = opts.reduce(0){|max, o| (sz = o.first.size) > max ? sz : max}
117
+ # help_width = term_width - (width += 1)
330
118
  # if help_width < 32...
331
119
  end
332
- str << (OUT_BUGS % @bugs) + "\n" if bugs
333
- what = @package || @program
334
- str << (OUT_HOMEPAGE % [what, @homepage]) + "\n" if homepage
335
- str
120
+ str.puts(OUT_BUGS % bugs) if bugs
121
+ str.puts(OUT_HOMEPAGE % [(package||program), homepage]) if homepage
122
+ str.string
336
123
  end
337
124
 
338
125
  def printed_synopsis
339
126
  s = synopsis ||
340
- (options.select{|o| !o.input} + inputs).map{|o|
341
- OPTS_RESERVED.include?(o.name) ? nil : o.synopsis}.compact.join(' ')
342
- "#{program} #{s}"
343
- end
344
-
345
- if __FILE__ == $0 # Some selftests... while hakin in an editor
346
- $stdout.sync = true
347
- $stderr.sync = true
348
- ARGV = %w[--abcm first]
349
- example = File.read('argparser/examples/example.rb')
350
- example = example.split("\n")[1..-1].join("\n") # Cut 'require' string
351
- eval(example) # Last line
127
+ (options.select{|o| !o.input} + inputs).map{|o| o.synopsis}.join(' ')
128
+ "Usage: #{program} #{s}"
352
129
  end
353
-
354
- end # the very end
130
+ end