argparser 1.0.0 → 1.0.1
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 +4 -4
- data/.gitignore +1 -0
- data/.hound.yml +4 -0
- data/.rubocop.yml +1063 -0
- data/.travis.yml +13 -0
- data/Gemfile +5 -0
- data/HISTORY.md +10 -0
- data/README.md +9 -8
- data/Rakefile +17 -0
- data/argparser.gemspec +20 -17
- data/argparser.sublime-project +27 -0
- data/lib/argparser.rb +45 -269
- data/lib/argparser/default_parser.rb +116 -0
- data/lib/argparser/option.rb +104 -0
- data/lib/argparser/tools.rb +56 -0
- data/spec/argparser_spec.rb +201 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/tools_spec.rb +28 -0
- metadata +64 -8
- data/lib/argparser/examples/example.rb +0 -32
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
data/HISTORY.md
ADDED
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# argparser
|
2
|
-
|
2
|
+
Yet another ruby command line argument parser library.
|
3
|
+
|
4
|
+
[](https://travis-ci.org/sinm/argparser) [](http://badge.fury.io/rb/argparser) [](https://gemnasium.com/sinm/argparser) [](https://raw.githubusercontent.com/sinm/argparser/master/LICENSE.txt) [](https://codeclimate.com/github/sinm/argparser) [](https://codeclimate.com/github/sinm/argparser) [](http://inch-ci.org/github/sinm/argparser)
|
3
5
|
|
4
6
|
## Installation
|
5
|
-
`gem install argparser
|
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
|
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
|
-
*
|
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 |
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
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
|
-
#
|
2
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
25
|
-
|
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
|
-
|
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 #
|
133
|
-
# see ArgParser::Option class'
|
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'] ||
|
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
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
289
|
-
|
290
|
-
|
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
|
-
|
300
|
-
|
301
|
-
str
|
302
|
-
str
|
303
|
-
str
|
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 =
|
308
|
-
str
|
106
|
+
str = StringIO.new
|
107
|
+
str.puts(printed_synopsis)
|
108
|
+
str.puts(info) if info
|
309
109
|
if help
|
310
|
-
str
|
110
|
+
str.puts(help)
|
311
111
|
else
|
312
|
-
|
313
|
-
|
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
|
-
#
|
329
|
-
# help_width = term_width - (
|
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
|
333
|
-
|
334
|
-
str
|
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
|
-
|
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
|