argparser 1.0.1 → 2.0.0.RC1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/HISTORY.md +22 -0
- data/README.md +7 -6
- data/TODO.md +13 -0
- data/argparser.gemspec +10 -6
- data/lib/argparser.rb +4 -125
- data/lib/argparser/arg_parser.rb +219 -0
- data/lib/argparser/argument.rb +82 -0
- data/lib/argparser/errors.rb +9 -0
- data/lib/argparser/option.rb +13 -92
- data/lib/argparser/tools.rb +2 -32
- data/lib/argparser/version.rb +5 -0
- data/spec/argparser_spec.rb +59 -43
- data/spec/tools_spec.rb +2 -2
- metadata +15 -11
- data/lib/argparser/default_parser.rb +0 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58593645fe6bb77546efd38e6426d090f297e947
|
4
|
+
data.tar.gz: 09097746fde57e2adfe85f103fa1ae59e3b19cfa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28adb1c9eca795de1ea6ec51a5f8af9ca1b09851e49645ac458744b46db0f2e223d65c1d184e70cd66ee3f8775870613c8a5096ede50fa74aa08f3befab340d2
|
7
|
+
data.tar.gz: 7f6af691f64f2ecace8d8aec79573a615df41a747f29f1a91748b1352ecbfc137569fcdb698fc6fb35befdd04b202bf7e6f32de81e28cb430083cbc1032ad9d9
|
data/HISTORY.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
# 2.0.0.RC2
|
2
|
+
2015-04-28 Holidays
|
3
|
+
|
4
|
+
* All exclamation marks removed from method names
|
5
|
+
* `Option/Argument#set_value` renamed to `#add_value`
|
6
|
+
* `Option/Argument#add_value`, `set_default` and `#reset` now return `self`
|
7
|
+
* `Option/Argument#value` now is read-only
|
8
|
+
* `Option/Argument#value?` now returns true in case of parsed option w/o param
|
9
|
+
* Block(name, value) in `#parse`
|
10
|
+
|
11
|
+
# 2.0.0.RC1
|
12
|
+
2015-04-28 Holidays
|
13
|
+
|
14
|
+
* Major version changed due to extensive renamings and removals
|
15
|
+
* Whitespace in option names no longer stripped
|
16
|
+
* Separate classes for options and arguments
|
17
|
+
* `Option#argument` renamed to `Option#param`
|
18
|
+
* `Option#eval` and `Option#env` settings removed as they aren't secure, use `Option#default`, which now may be a proc/lambda
|
19
|
+
* `ArgParser.manifest=` added, which merges with manifest supplied through `ArgParser.new`
|
20
|
+
* `ArgParser.new` raises `ManifestError` instead of late `exit(2)` in `ArgParser#parse` while checking manifest
|
21
|
+
* `DefaultParser` module removed and `#parse!` method merged back into `ArgParser`
|
22
|
+
|
1
23
|
# 1.0.1
|
2
24
|
2015-04-14
|
3
25
|
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# argparser
|
2
2
|
Yet another ruby command line argument parser library.
|
3
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)
|
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) [![security](https://hakiri.io/github/sinm/argparser/master.svg)](https://hakiri.io/github/sinm/argparser/master)
|
5
5
|
|
6
6
|
## Installation
|
7
7
|
`gem install argparser` as usual.
|
@@ -86,13 +86,14 @@ Unknown option: a
|
|
86
86
|
````
|
87
87
|
|
88
88
|
## Consider more rules
|
89
|
-
* `--help` and `--version` options provided
|
90
|
-
* printed synopsis provided
|
91
|
-
* `:default`
|
92
|
-
* `:env => 'ENV_VAR'` to pick default value from ENV, high priority
|
93
|
-
* `:eval => 'ruby expr'` to pick default from eval(...), useful for read defaults from config files, so it has low priority
|
89
|
+
* `--help` and `--version` options provided unless specified explicitly
|
90
|
+
* printed synopsis provided unless specified explicitly
|
91
|
+
* `:default` setting assigns default option's value if value isn't specified, may be proc/lambda
|
94
92
|
* `--` argument honored
|
95
93
|
|
94
|
+
## Tests
|
95
|
+
`bundle exec rake test`
|
96
|
+
|
96
97
|
## Documentation
|
97
98
|
This README is all i could say in a rush. No other documentation provided at this moment, see the sources.
|
98
99
|
|
data/TODO.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# argparser gem TODO list
|
2
|
+
|
3
|
+
5. Testing bash and rb scripts
|
4
|
+
4. CPU Profiling
|
5
|
+
3. + remove eval
|
6
|
+
2. More sophisticated output of printed_help
|
7
|
+
````ruby
|
8
|
+
term_width = ENV['COLUMNS'] || `tput columns` || 80
|
9
|
+
width = opts.reduce(0){|max, o| (sz = o.first.size) > max ? sz : max}
|
10
|
+
help_width = term_width - (width += 1)
|
11
|
+
if help_width < 32...
|
12
|
+
````
|
13
|
+
1. RDocs
|
data/argparser.gemspec
CHANGED
@@ -1,22 +1,26 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
require File.expand_path('../lib/argparser/version.rb', __FILE__)
|
3
|
+
|
2
4
|
Gem::Specification.new do |s|
|
3
5
|
s.name = 'argparser'
|
4
|
-
s.version =
|
6
|
+
s.version = ArgParser::VERSION
|
5
7
|
s.authors = ['sinm']
|
6
8
|
s.email = 'sinm.sinm@gmail.com'
|
7
9
|
s.summary = 'Yet another ruby command line argument parser library'
|
8
10
|
s.description = '== Yet another ruby command line argument parser library'
|
9
11
|
s.homepage = 'https://github.com/sinm/argparser'
|
10
12
|
s.license = 'MIT'
|
11
|
-
s.files = `git ls-files
|
12
|
-
s.test_files = `git ls-files
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- spec/`.split("\n")
|
13
15
|
s.require_paths = ['lib']
|
16
|
+
s.bindir = 'bin'
|
17
|
+
s.executables = `git ls-files -- bin/`.split("\n").map{|f| File.basename(f)}
|
14
18
|
# s.extra_rdoc_files = 'README.md'
|
15
19
|
# s.rdoc_options << '--title' << 'argparser' <<
|
16
20
|
# '--main' << 'README' <<
|
17
21
|
# '--markup' << 'markdown' <<
|
18
22
|
# '--line-numbers'
|
19
|
-
s.add_development_dependency 'bundler', '~> 1'
|
20
|
-
s.add_development_dependency 'rake', '~> 10'
|
21
|
-
s.add_development_dependency 'minitest', '~> 4'
|
23
|
+
s.add_development_dependency 'bundler', '~> 1.7'
|
24
|
+
s.add_development_dependency 'rake', '~> 10.1'
|
25
|
+
s.add_development_dependency 'minitest', '~> 4.7'
|
22
26
|
end
|
data/lib/argparser.rb
CHANGED
@@ -1,130 +1,9 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require 'stringio'
|
3
3
|
|
4
|
+
require 'argparser/version'
|
4
5
|
require 'argparser/tools'
|
6
|
+
require 'argparser/argument'
|
5
7
|
require 'argparser/option'
|
6
|
-
require 'argparser/
|
7
|
-
|
8
|
-
class ArgParser
|
9
|
-
include Tools
|
10
|
-
include DefaultParser
|
11
|
-
|
12
|
-
# Output messages used in the #terminate method
|
13
|
-
OUT_VERSION = '%s%s %s'
|
14
|
-
OUT_COPYRIGHT = 'Copyright (C) %s'
|
15
|
-
OUT_LICENSE = 'License: %s'
|
16
|
-
OUT_HOMEPAGE = '%s home page: %s'
|
17
|
-
OUT_BUGS = 'Report bugs to: %s'
|
18
|
-
OUT_MANIFEST_EXPECTED = 'Property expected through the manifest: %s'
|
19
|
-
OUT_MULTIPLE_INPUTS = 'Multiple input argument not allowed in the middle: %s'
|
20
|
-
OUT_MULTIPLE_NAMES = 'Multiple names for the input argument: %s'
|
21
|
-
OUT_OPTION_NULL = 'Empty name for option'
|
22
|
-
OUT_REQUIRED = 'Required input argument after optional one: %s'
|
23
|
-
OUT_REQUIRED_DEFAULT = 'Required option has default value: %s'
|
24
|
-
OUT_UNEXPECTED_ARGUMENT = 'Unexpected argument: %s'
|
25
|
-
OUT_UNKNOWN_OPTION = 'Unknown option: %s'
|
26
|
-
OUT_OPTION_ARGUMENT_EXPECTED = 'Expected argument for the option: %s'
|
27
|
-
OUT_SINGLE_OPTION = 'Multiple options not allowed: %s'
|
28
|
-
OUT_OPTION_EXPECTED = 'Expected option: %s'
|
29
|
-
OUT_ARGUMENT_EXPECTED = 'Expected argument: %s'
|
30
|
-
OUT_UNIQUE_NAME = 'Option name should be unique: %s'
|
31
|
-
OUT_INVALID_OPTION = 'Invalid value for option: %s'
|
32
|
-
OPT_ENOUGH = '--'
|
33
|
-
|
34
|
-
# These options don't display their synopsis and given for free unless
|
35
|
-
# explicitly specified in the manifest.
|
36
|
-
OPT_HELP = 'help'
|
37
|
-
OPT_VERSION = 'version'
|
38
|
-
OPTS_RESERVED = [OPT_HELP, OPT_VERSION]
|
39
|
-
|
40
|
-
attr_reader :program # Program name, REQUIRED
|
41
|
-
attr_reader :package # Set to nil if there's no package
|
42
|
-
attr_reader :version # Follow semantic versioning rules, REQUIRED
|
43
|
-
attr_reader :copyright # '2015 Somebody, Inc.',
|
44
|
-
attr_reader :license # Give license headline
|
45
|
-
attr_reader :info # Set additional lines that would follow license
|
46
|
-
attr_reader :bugs # Address to post bug reports
|
47
|
-
attr_reader :homepage # Package or, if absent, program's home page
|
48
|
-
attr_reader :synopsis # Print this if present or construct from options
|
49
|
-
attr_reader :help # Print this if present or construct from options
|
50
|
-
attr_reader :options # Array of options,
|
51
|
-
# see ArgParser::Option class' attr_readers
|
52
|
-
|
53
|
-
# Returns option by any of its names given
|
54
|
-
def [](name)
|
55
|
-
options.find{|o| o.names.include?(name)}
|
56
|
-
end
|
57
|
-
|
58
|
-
# Returns array of input args in order
|
59
|
-
def inputs
|
60
|
-
options.select{|o| o.input}
|
61
|
-
end
|
62
|
-
|
63
|
-
def initialize(manifest)
|
64
|
-
manifest = (safe_return('$config.manifest') || {}).merge(manifest)
|
65
|
-
hash2vars!(manifest)
|
66
|
-
@options = (manifest[:options] || manifest['options'] || []).
|
67
|
-
map {|o| o.kind_of?(Option) ? o : Option.new(o)}
|
68
|
-
if !self['help']
|
69
|
-
options << Option.new(:names => OPT_HELP,
|
70
|
-
:help => 'Print this help and exit.',
|
71
|
-
:validate => (lambda { |o, p|
|
72
|
-
return true if o.count < 1
|
73
|
-
p.terminate(0, p.printed_help) }))
|
74
|
-
end
|
75
|
-
if !self['version']
|
76
|
-
options << Option.new(:names => OPT_VERSION,
|
77
|
-
:help => 'Print version and exit.',
|
78
|
-
:validate => (lambda { |o, p|
|
79
|
-
return true if o.count < 1
|
80
|
-
p.terminate(0, p.printed_version) }))
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
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)
|
89
|
-
end
|
90
|
-
|
91
|
-
def on_exit(code, message)
|
92
|
-
(code == 0 ? $stdout : $stderr).print(message)
|
93
|
-
exit(code)
|
94
|
-
end
|
95
|
-
|
96
|
-
def printed_version
|
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
|
103
|
-
end
|
104
|
-
|
105
|
-
def printed_help
|
106
|
-
str = StringIO.new
|
107
|
-
str.puts(printed_synopsis)
|
108
|
-
str.puts(info) if info
|
109
|
-
if help
|
110
|
-
str.puts(help)
|
111
|
-
else
|
112
|
-
(options.select{|o| !o.input} + inputs).each do |o|
|
113
|
-
str.puts(o.printed_help)
|
114
|
-
end
|
115
|
-
# term_width = ENV['COLUMNS'] || `tput columns` || 80
|
116
|
-
# width = opts.reduce(0){|max, o| (sz = o.first.size) > max ? sz : max}
|
117
|
-
# help_width = term_width - (width += 1)
|
118
|
-
# if help_width < 32...
|
119
|
-
end
|
120
|
-
str.puts(OUT_BUGS % bugs) if bugs
|
121
|
-
str.puts(OUT_HOMEPAGE % [(package||program), homepage]) if homepage
|
122
|
-
str.string
|
123
|
-
end
|
124
|
-
|
125
|
-
def printed_synopsis
|
126
|
-
s = synopsis ||
|
127
|
-
(options.select{|o| !o.input} + inputs).map{|o| o.synopsis}.join(' ')
|
128
|
-
"Usage: #{program} #{s}"
|
129
|
-
end
|
130
|
-
end
|
8
|
+
require 'argparser/errors'
|
9
|
+
require 'argparser/arg_parser'
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
class ArgParser
|
4
|
+
include Tools
|
5
|
+
|
6
|
+
# Output templates used for the #terminate(0) call
|
7
|
+
OUT_VERSION = '%s%s %s'
|
8
|
+
OUT_COPYRIGHT = 'Copyright (C) %s'
|
9
|
+
OUT_LICENSE = 'License: %s'
|
10
|
+
OUT_HOMEPAGE = '%s home page: %s'
|
11
|
+
OUT_BUGS = 'Report bugs to: %s'
|
12
|
+
OUT_OPTIONS = 'OPTIONS:'
|
13
|
+
OUT_ARGUMENTS = 'ARGUMENTS:'
|
14
|
+
|
15
|
+
# Templates used for the terminate(2) call
|
16
|
+
TRM_UNEXPECTED_ARGUMENT = 'Unexpected argument: %s'
|
17
|
+
TRM_UNKNOWN = 'Unknown option: %s'
|
18
|
+
TRM_OPTION_ARGUMENT_EXPECTED = 'Expected parameter for the option: %s'
|
19
|
+
TRM_EXPECTED = 'Expected required argument/option: %s'
|
20
|
+
TRM_INVALID_OPTION = 'Invalid value for the argument/option: %s'
|
21
|
+
|
22
|
+
OPT_ENOUGH = '--'
|
23
|
+
|
24
|
+
# These options don't display their synopsis and given for free unless
|
25
|
+
# explicitly specified in the manifest.
|
26
|
+
OPTS_RESERVED = [{:func => :printed_help,
|
27
|
+
:help => 'Print this help and exit.',
|
28
|
+
:name => 'help'},
|
29
|
+
{:func => :printed_version,
|
30
|
+
:help => 'Print version and exit.',
|
31
|
+
:name => 'version'}]
|
32
|
+
|
33
|
+
attr_reader :program # Program name, REQUIRED
|
34
|
+
attr_reader :package # Set to nil if there's no package
|
35
|
+
attr_reader :version # Follow semantic versioning rules, REQUIRED
|
36
|
+
attr_reader :copyright # Like '2015 Somebody, Inc.',
|
37
|
+
attr_reader :license # Give license headline
|
38
|
+
attr_reader :info # Program info string
|
39
|
+
attr_reader :bugs # Address to post bug reports
|
40
|
+
attr_reader :homepage # Package or, if absent, program's home page
|
41
|
+
attr_reader :synopsis # Build from options if not given
|
42
|
+
attr_reader :help # Build from options if not given
|
43
|
+
attr_reader :arguments # Array of arguments
|
44
|
+
attr_reader :options # Array of options
|
45
|
+
# see ArgParser::Argument/Option classes attr_readers
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def manifest=(manifest)
|
49
|
+
@@manifest = manifest
|
50
|
+
end
|
51
|
+
def manifest
|
52
|
+
@@manifest ||= {}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns option by any of its names given
|
57
|
+
def [](name)
|
58
|
+
get_argument(name) || get_option(name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def all
|
62
|
+
options + arguments
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_argument(name)
|
66
|
+
arguments.find{|a| a.name == name}
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_option(name)
|
70
|
+
options.find{|o| o.names.include?(name)}
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(manifest)
|
74
|
+
hash2vars(manifest = ArgParser.manifest.merge(manifest))
|
75
|
+
@arguments =
|
76
|
+
(@arguments || []).map {|o| o.kind_of?(Argument) ? o : Argument.new(o)}
|
77
|
+
@options =
|
78
|
+
(@options || []).map {|o| o.kind_of?(Option) ? o : Option.new(o)}
|
79
|
+
_check_manifest
|
80
|
+
end
|
81
|
+
|
82
|
+
# Uses ARGV by default, but you may supply your own arguments
|
83
|
+
# It exits if bad arguments given or they aren't validated.
|
84
|
+
def parse(argv = ARGV)
|
85
|
+
all.each(&:reset)
|
86
|
+
_check_manifest
|
87
|
+
|
88
|
+
OPTS_RESERVED.each do |res|
|
89
|
+
name = res[:name]
|
90
|
+
next unless argv.include?("--#{name}") && !get_argument(name)
|
91
|
+
terminate(0, self.send(res[:func]))
|
92
|
+
end
|
93
|
+
|
94
|
+
args = argv.dup
|
95
|
+
enough = false
|
96
|
+
while (a = args.shift)
|
97
|
+
if a == OPT_ENOUGH
|
98
|
+
enough = true
|
99
|
+
elsif enough || (a =~ /^[^-]/) || (a == '-')
|
100
|
+
_set_argument(a)
|
101
|
+
elsif a =~ /^--(.+)/
|
102
|
+
_set_long_option($1, args)
|
103
|
+
elsif a =~ /^-([^-].*)/
|
104
|
+
_set_short_options($1, args)
|
105
|
+
else
|
106
|
+
terminate(2, TRM_UNKNOWN % a)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
all.each { |o|
|
111
|
+
o.set_default
|
112
|
+
terminate(2, (TRM_EXPECTED % o.name)) if o.required && !o.value?
|
113
|
+
}
|
114
|
+
|
115
|
+
all.each { |o|
|
116
|
+
terminate(2, TRM_INVALID_OPTION % o.name) unless o.valid?(self)
|
117
|
+
}
|
118
|
+
|
119
|
+
all.select(&:value?).each {|o| yield(o.name, o.value)} if block_given?
|
120
|
+
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
def terminate(code, str)
|
125
|
+
s = StringIO.new
|
126
|
+
s.puts(printed_synopsis) if code != 0
|
127
|
+
s.puts(str[-1] == "\n" ? str.chop : str)
|
128
|
+
on_exit(code, s.string)
|
129
|
+
end
|
130
|
+
|
131
|
+
def on_exit(code, message)
|
132
|
+
(code == 0 ? $stdout : $stderr).print(message)
|
133
|
+
exit(code)
|
134
|
+
end
|
135
|
+
|
136
|
+
def printed_version
|
137
|
+
str = StringIO.new
|
138
|
+
pk = (pk = @package) ? " (#{pk})" : ''
|
139
|
+
str.puts(OUT_VERSION % [program, pk, version])
|
140
|
+
str.puts(OUT_COPYRIGHT % copyright) if copyright
|
141
|
+
str.puts(OUT_LICENSE % license) if license
|
142
|
+
str.string
|
143
|
+
end
|
144
|
+
|
145
|
+
def printed_help
|
146
|
+
str = StringIO.new
|
147
|
+
str.puts(printed_synopsis)
|
148
|
+
str.puts(info) if info
|
149
|
+
if help
|
150
|
+
str.puts(help)
|
151
|
+
else
|
152
|
+
unless options.empty?
|
153
|
+
str.puts(OUT_OPTIONS)
|
154
|
+
options.each {|o| str.puts(o.printed_help)}
|
155
|
+
end
|
156
|
+
OPTS_RESERVED.each do |res|
|
157
|
+
r = Option.new(res)
|
158
|
+
next if get_argument(r.name)
|
159
|
+
str.puts(r.printed_help)
|
160
|
+
end
|
161
|
+
unless arguments.empty?
|
162
|
+
str.puts(OUT_ARGUMENTS)
|
163
|
+
arguments.each {|a| str.puts(a.printed_help)}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
str.puts(OUT_BUGS % bugs) if bugs
|
167
|
+
str.puts(OUT_HOMEPAGE % [(package||program), homepage]) if homepage
|
168
|
+
str.string
|
169
|
+
end
|
170
|
+
|
171
|
+
def printed_synopsis
|
172
|
+
"Usage: #{program} #{synopsis || all.map{|o| o.synopsis}.join(' ')}"
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def _check_manifest
|
178
|
+
{:program => program, :version => version}.each do |k, v|
|
179
|
+
raise ManifestError, (ERR_MANIFEST_EXPECTED % k) if v.to_s.strip.empty?
|
180
|
+
end
|
181
|
+
|
182
|
+
arguments[0..-2].each do |i|
|
183
|
+
raise ManifestError, (ERR_MULTIPLE_INPUTS % i.name) if i.multiple
|
184
|
+
end
|
185
|
+
opt = arguments.index{|i| !i.required} || arguments.size
|
186
|
+
req = arguments.rindex{|i| i.required} || 0
|
187
|
+
raise ManifestError, (ERR_REQUIRED % arguments[req].name) if req > opt
|
188
|
+
|
189
|
+
names = all.map(&:names).flatten
|
190
|
+
raise ManifestError, ERR_UNIQUE_NAME if names.size != names.uniq.size
|
191
|
+
end
|
192
|
+
|
193
|
+
def _set_argument(a)
|
194
|
+
terminate(2, TRM_UNEXPECTED_ARGUMENT % a) unless
|
195
|
+
(input = arguments.find{|i| !i.value || i.multiple})
|
196
|
+
input.add_value(a)
|
197
|
+
end
|
198
|
+
|
199
|
+
def _set_long_option(a, tail)
|
200
|
+
terminate(2, TRM_UNKNOWN % a) unless a.size > 1 && (o = get_option(a))
|
201
|
+
terminate(2, TRM_OPTION_ARGUMENT_EXPECTED % a) if o.param && tail.empty?
|
202
|
+
o.add_value(o.param ? tail.shift : nil)
|
203
|
+
end
|
204
|
+
|
205
|
+
def _set_short_options(a, tail)
|
206
|
+
a.chars.each_with_index do |char, index|
|
207
|
+
terminate(2, TRM_UNKNOWN % char) unless (option = get_option(char))
|
208
|
+
if !option.param
|
209
|
+
option.add_value(nil)
|
210
|
+
elsif a.size-1 == index
|
211
|
+
terminate(2, TRM_OPTION_ARGUMENT_EXPECTED % char) if tail.empty?
|
212
|
+
option.add_value(tail.shift)
|
213
|
+
else
|
214
|
+
option.add_value(a[index+1..-1])
|
215
|
+
break
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
class ArgParser
|
4
|
+
class Argument
|
5
|
+
include Tools
|
6
|
+
|
7
|
+
# These attrs are from the manifest that is applied through constructor
|
8
|
+
attr_reader :name
|
9
|
+
attr_reader :help # Help string
|
10
|
+
attr_reader :validate # Proc(this, parser) to validate a value
|
11
|
+
attr_reader :default # Value or proc to set default value
|
12
|
+
attr_reader :required # Required
|
13
|
+
attr_reader :multiple # May occur multiple times?
|
14
|
+
|
15
|
+
# These attrs have their meaning after parsing was done
|
16
|
+
attr_reader :count # Occucences
|
17
|
+
attr_reader :value # Value (Array if multiple)
|
18
|
+
|
19
|
+
# Just helper
|
20
|
+
def names
|
21
|
+
[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Constructs from Hash of properties (see attr_readers)
|
25
|
+
def initialize(o_manifest)
|
26
|
+
hash2vars(o_manifest)
|
27
|
+
reset
|
28
|
+
raise ManifestError, ERR_OPTION_NULL if !name or name.strip.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def synopsis
|
32
|
+
s = name.dup
|
33
|
+
s << '...' if multiple
|
34
|
+
s = "[#{s}]" if !required
|
35
|
+
s
|
36
|
+
end
|
37
|
+
|
38
|
+
# Adds value.
|
39
|
+
def add_value(v)
|
40
|
+
@count += 1
|
41
|
+
multiple ? (@value = @value + Array(v)) : @value = v
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Does option contain it's value?
|
46
|
+
def value?
|
47
|
+
@count > 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns value as string
|
51
|
+
def to_s
|
52
|
+
multiple ? value.map(&:to_s).join(', ') : value.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def valid?(parser)
|
56
|
+
!validate || validate.call(self, parser)
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset
|
60
|
+
@value = multiple ? [] : nil
|
61
|
+
@count = 0
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def printed_help
|
66
|
+
s = help || ''
|
67
|
+
s << "\n\tDefaults to: #{get_default}" if default
|
68
|
+
"%s\n\t%s" % [synopsis, s]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set value to default one if no value provided
|
72
|
+
def set_default
|
73
|
+
return self if !default || value?
|
74
|
+
add_value(get_default)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get default value
|
78
|
+
def get_default
|
79
|
+
default.respond_to?(:call) ? default.call : default
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class ArgParser
|
2
|
+
ERR_OPTION_NULL = 'Empty name for an argument'
|
3
|
+
ERR_MANIFEST_EXPECTED = 'Property expected through the manifest: %s'
|
4
|
+
ERR_MULTIPLE_INPUTS = 'Multi-value argument allowed only if last: %s'
|
5
|
+
ERR_REQUIRED = 'Required argument after optional one: %s'
|
6
|
+
ERR_UNIQUE_NAME = 'All option/argument names must be unique'
|
7
|
+
class ManifestError < RuntimeError
|
8
|
+
end
|
9
|
+
end
|
data/lib/argparser/option.rb
CHANGED
@@ -1,104 +1,25 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
3
|
class ArgParser
|
4
|
-
class Option
|
5
|
-
include Tools
|
6
|
-
attr_reader :names # Names of an option (short, long, etc.)
|
7
|
-
attr_reader :argument # Name of an argument, if present
|
8
|
-
attr_reader :help # Help string for an option
|
9
|
-
attr_reader :validate # Lambda(option, parser) to validate an option
|
10
|
-
attr_reader :default # Default value for an option
|
11
|
-
attr_reader :input # Option is an input argument
|
12
|
-
attr_reader :required # Option required
|
13
|
-
attr_reader :multiple # Option may occure multiple times
|
14
|
-
attr_reader :count # Option occucences
|
15
|
-
attr_reader :env # Default option set by this ENV VAR, if any
|
16
|
-
attr_reader :eval # Default option set by this eval,
|
17
|
-
# superseded by :env, if any
|
18
|
-
# So, in order: value - env - eval - default
|
19
|
-
attr_accessor :value # Values of an option, Array if multiple
|
4
|
+
class Option < Argument
|
20
5
|
|
21
|
-
#
|
6
|
+
# These attrs are from the manifest that is applied through constructor
|
7
|
+
attr_reader :param # Parameter name, if any
|
8
|
+
# Returns first name as a 'default' one when several names given
|
22
9
|
def name
|
23
|
-
names.first
|
10
|
+
@name ||= names.first
|
24
11
|
end
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
hash2vars!(o_manifest)
|
29
|
-
@names = Array(names).map{|n| n.to_s.strip}.
|
30
|
-
sort{|n1, n2| n1.size <=> n2.size}
|
31
|
-
reset!
|
32
|
-
end
|
33
|
-
|
34
|
-
# Sets value. Do not use this directly
|
35
|
-
def set_value(v)
|
36
|
-
@count += 1
|
37
|
-
multiple ? (@value << v).flatten! : @value = v
|
38
|
-
end
|
39
|
-
|
40
|
-
# Does option contain it's value?
|
41
|
-
def value?
|
42
|
-
multiple ? !value.compact.empty? : !!value
|
43
|
-
end
|
44
|
-
|
45
|
-
def to_s
|
46
|
-
if multiple
|
47
|
-
value.empty? ? '' : value.map(&:to_s).join(', ')
|
48
|
-
else
|
49
|
-
value.to_s
|
50
|
-
end
|
12
|
+
# Names of an option (short, long, etc.)
|
13
|
+
def names
|
14
|
+
@names ||= [@name]
|
51
15
|
end
|
52
16
|
|
53
17
|
def synopsis
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
else
|
60
|
-
s = names.map{|n| n.size == 1 ? "-#{n}" : "--#{n}"}.join(', ')
|
61
|
-
s << " #{argument}" if argument
|
62
|
-
s = "[#{s}]" if !required
|
63
|
-
s << '...' if multiple
|
64
|
-
s
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def validate!(parser)
|
69
|
-
!validate || validate.call(self, parser)
|
70
|
-
end
|
71
|
-
|
72
|
-
def reset!
|
73
|
-
@value = multiple ? [] : nil
|
74
|
-
@count = 0
|
75
|
-
end
|
76
|
-
|
77
|
-
def printed_help
|
78
|
-
s = help || ''
|
79
|
-
s << "\n\tDefaults to: #{default}" if default
|
80
|
-
"%s\n\t%s" % [synopsis, s]
|
81
|
-
end
|
82
|
-
|
83
|
-
def set_default!
|
84
|
-
return self unless !value? && (argument || input)
|
85
|
-
# rubocop:disable Lint/Eval
|
86
|
-
e = ((env && ENV[env]) || (eval && safe_return(eval)) || default)
|
87
|
-
# rubocop:enable Lint/Eval
|
88
|
-
set_value(e) if e
|
89
|
-
self
|
90
|
-
end
|
91
|
-
|
92
|
-
def on_first_error
|
93
|
-
if !multiple && count > 1
|
94
|
-
yield(OUT_SINGLE_OPTION % name)
|
95
|
-
elsif required && count < 1
|
96
|
-
yield((input ? OUT_ARGUMENT_EXPECTED : OUT_OPTION_EXPECTED) % name)
|
97
|
-
elsif names.find(&:empty?)
|
98
|
-
yield(OUT_OPTION_NULL)
|
99
|
-
elsif required && default
|
100
|
-
yield(OUT_REQUIRED_DEFAULT % name)
|
101
|
-
end
|
18
|
+
s = names.map{|n| n.size == 1 ? "-#{n}" : "--#{n}"}.join(', ')
|
19
|
+
s << " #{param}" if param
|
20
|
+
s = "[#{s}]" if !required
|
21
|
+
s << '...' if multiple
|
22
|
+
s
|
102
23
|
end
|
103
24
|
end
|
104
25
|
end
|
data/lib/argparser/tools.rb
CHANGED
@@ -4,7 +4,7 @@ class ArgParser
|
|
4
4
|
# Aux tools intented to include into a class
|
5
5
|
module Tools
|
6
6
|
# Sets self state from a hash given
|
7
|
-
def hash2vars
|
7
|
+
def hash2vars(hash)
|
8
8
|
if hash.kind_of?(Hash) || (hash.respond_to?(:to_h) && (hash = hash.to_h))
|
9
9
|
hash.each do |k, v|
|
10
10
|
next unless self.respond_to?(k)
|
@@ -16,41 +16,11 @@ class ArgParser
|
|
16
16
|
self
|
17
17
|
end
|
18
18
|
|
19
|
-
# Returns a hash of self state
|
19
|
+
# Returns a hash of self state
|
20
20
|
def to_hash
|
21
21
|
instance_variables.reduce({}) { |hash, var|
|
22
22
|
hash[var[1..-1]] = instance_variable_get(var)
|
23
23
|
hash }
|
24
24
|
end
|
25
|
-
|
26
|
-
# Eval ruby code.
|
27
|
-
# Returns result of Kernel.eval or nil if some errors occure
|
28
|
-
def safe_return(str)
|
29
|
-
# rubocop:disable Lint/Eval
|
30
|
-
eval(str)
|
31
|
-
# rubocop:enable Lint/Eval
|
32
|
-
rescue NameError, NoMethodError
|
33
|
-
nil
|
34
|
-
end
|
35
|
-
|
36
|
-
=begin Deep pack
|
37
|
-
def to_hash(deep = true)
|
38
|
-
instance_variables.reduce({}) { |hash, var|
|
39
|
-
value = instance_variable_get(var)
|
40
|
-
hash[var[1..-1]] = deep ? value_to_hash(value) : value
|
41
|
-
hash }
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
def value_to_hash value
|
46
|
-
if value.respond_to?(:to_hash)
|
47
|
-
(v = value.to_hash).kind_of?(Hash) ? v : {}
|
48
|
-
elsif value.respond_to?(:to_a)
|
49
|
-
(v = value.to_a).kind_of?(Array) ? v.map{|v| value_to_hash(v)} : []
|
50
|
-
else
|
51
|
-
value
|
52
|
-
end
|
53
|
-
end
|
54
|
-
=end
|
55
25
|
end
|
56
26
|
end
|
data/spec/argparser_spec.rb
CHANGED
@@ -2,20 +2,26 @@
|
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
4
|
a_manifest = {
|
5
|
-
:program
|
6
|
-
:version
|
7
|
-
:
|
5
|
+
:program => 'a_example', # Use additional properties like these:
|
6
|
+
:version => '1.0', # :info, :copyright, :license,
|
7
|
+
:copyright=> '2015 sinm',
|
8
|
+
:info => 'A Example',
|
9
|
+
:license => 'MIT',
|
10
|
+
:package => 'ArgParser Spec',
|
11
|
+
:bugs => 'https://github.com/sinm/argparser',
|
12
|
+
:homepage => 'https://github.com/sinm/argparser',
|
13
|
+
:options => [{ # :package, :bugs, :homepage
|
8
14
|
:names => %w[m mode],
|
9
|
-
:
|
15
|
+
:param => 'first|second|third',
|
10
16
|
:default => 'first',
|
11
17
|
:multiple => true,
|
12
18
|
:help => 'Example mode.',
|
13
19
|
:validate => (lambda {|this, _parser| # Validating value in-line
|
14
|
-
possible = this.
|
20
|
+
possible = this.param.split('|')
|
15
21
|
this.value.select{|v| possible.include?(v)}.size == this.value.size })
|
16
|
-
},
|
17
|
-
|
18
|
-
:
|
22
|
+
}],
|
23
|
+
:arguments => [{
|
24
|
+
:name => 'file',
|
19
25
|
:required => false,
|
20
26
|
:default => '-',
|
21
27
|
:help => 'Filename or - for stdin.'
|
@@ -29,29 +35,24 @@ describe 'manifest' do
|
|
29
35
|
o.must_be_instance_of(ArgParser::Option)
|
30
36
|
args[o.name].must_be_same_as(o)
|
31
37
|
}
|
32
|
-
(a = args.parse
|
38
|
+
(a = args.parse(%w[-])).must_be_instance_of(ArgParser)
|
33
39
|
a.must_be_same_as(args)
|
34
40
|
end
|
35
41
|
|
36
42
|
it 'should require program' do
|
37
43
|
b_manifest = a_manifest.merge(:program => '')
|
38
|
-
|
39
|
-
lambda {
|
40
|
-
args.parse!([])
|
41
|
-
}.must_raise(ExitStub).status.must_equal(2)
|
44
|
+
lambda { ArgParser.new(b_manifest) }.must_raise(ArgParser::ManifestError)
|
42
45
|
end
|
43
46
|
|
44
47
|
it 'should require version' do
|
45
48
|
b_manifest = a_manifest.merge(:version => nil)
|
46
|
-
lambda {
|
47
|
-
ArgParser.new(b_manifest).parse!([])
|
48
|
-
}.must_raise(ExitStub).status.must_equal(2)
|
49
|
+
lambda { ArgParser.new(b_manifest) }.must_raise(ArgParser::ManifestError)
|
49
50
|
end
|
50
51
|
|
51
52
|
it 'requires nothing but but program & version' do
|
52
53
|
b_manifest = a_manifest.reduce({}) {|h, (k, v)|
|
53
54
|
h[k] = v if [:program, :version].include?(k); h}
|
54
|
-
ArgParser.new(b_manifest).parse
|
55
|
+
ArgParser.new(b_manifest).parse([]).must_be_instance_of(ArgParser)
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
@@ -59,25 +60,47 @@ describe 'Built-in options' do
|
|
59
60
|
it 'prints out version and terminates' do
|
60
61
|
a = ArgParser.new(a_manifest)
|
61
62
|
e = lambda {
|
62
|
-
a.parse
|
63
|
+
a.parse(%w[any --othelp --version])
|
63
64
|
}.must_raise(ExitStub)
|
64
65
|
e.status.must_equal(0)
|
65
66
|
e.message.must_match(/#{a_manifest[:program]}.*#{a_manifest[:version]}/)
|
67
|
+
e.message.must_equal(
|
68
|
+
"a_example (ArgParser Spec) 1.0
|
69
|
+
Copyright (C) 2015 sinm
|
70
|
+
License: MIT\n")
|
66
71
|
end
|
67
72
|
|
68
73
|
it 'prints out help and terminates' do
|
69
74
|
a = ArgParser.new(a_manifest)
|
70
75
|
e = lambda {
|
71
|
-
a.parse
|
76
|
+
a.parse(%w[any --oth --help])
|
72
77
|
}.must_raise(ExitStub)
|
73
78
|
e.status.must_equal(0)
|
74
79
|
e.message.must_match(/#{a_manifest[:options].last[:help]}/)
|
80
|
+
e.message.must_equal(
|
81
|
+
"Usage: a_example [-m, --mode first|second|third]... [file]
|
82
|
+
A Example
|
83
|
+
OPTIONS:
|
84
|
+
[-m, --mode first|second|third]...
|
85
|
+
\tExample mode.
|
86
|
+
\tDefaults to: first
|
87
|
+
[--help]
|
88
|
+
\tPrint this help and exit.
|
89
|
+
[--version]
|
90
|
+
\tPrint version and exit.
|
91
|
+
ARGUMENTS:
|
92
|
+
[file]
|
93
|
+
\tFilename or - for stdin.
|
94
|
+
\tDefaults to: -
|
95
|
+
Report bugs to: https://github.com/sinm/argparser
|
96
|
+
ArgParser Spec home page: https://github.com/sinm/argparser\n")
|
97
|
+
|
75
98
|
end
|
76
99
|
|
77
100
|
it 'prints synopsys on argument error' do
|
78
101
|
a = ArgParser.new(a_manifest)
|
79
102
|
e = lambda {
|
80
|
-
a.parse
|
103
|
+
a.parse(%w[any --1287])
|
81
104
|
}.must_raise(ExitStub)
|
82
105
|
e.status.must_equal(2)
|
83
106
|
e.message.must_match(/#{a.synopsis}/)
|
@@ -87,7 +110,7 @@ describe 'Built-in options' do
|
|
87
110
|
b_manifest = a_manifest.merge(:help => 'User-defined help')
|
88
111
|
a = ArgParser.new(b_manifest)
|
89
112
|
e = lambda {
|
90
|
-
a.parse
|
113
|
+
a.parse(%w[--help])
|
91
114
|
}.must_raise(ExitStub)
|
92
115
|
e.status.must_equal(0)
|
93
116
|
e.message.must_match(/#{b_manifest[:help]}/)
|
@@ -95,7 +118,7 @@ describe 'Built-in options' do
|
|
95
118
|
|
96
119
|
it 'understands -- argument' do
|
97
120
|
@args = ArgParser.new(a_manifest)
|
98
|
-
mode = @args.parse
|
121
|
+
mode = @args.parse(%w[-- --mode])['mode']
|
99
122
|
mode.count.must_equal(1)
|
100
123
|
mode.value.first.must_equal(mode.default)
|
101
124
|
end
|
@@ -107,19 +130,19 @@ describe 'Multiple argumented options w/default value' do
|
|
107
130
|
end
|
108
131
|
|
109
132
|
it 'reads them and gives out values in order' do
|
110
|
-
@args.parse
|
133
|
+
@args.parse(%w[--mode second -m first -msecond -- -])
|
111
134
|
@args['mode'].value.must_equal(%w[second first second])
|
112
135
|
end
|
113
136
|
|
114
137
|
it 'doesn''t validate unknown value' do
|
115
138
|
lambda {
|
116
|
-
@args.parse
|
139
|
+
@args.parse(%w[--mode second -m foo -msecond -])
|
117
140
|
}.must_raise(ExitStub).status.must_equal(2)
|
118
141
|
end
|
119
142
|
|
120
143
|
it 'doesn''t understand unknown options' do
|
121
144
|
lambda {
|
122
|
-
@args.parse
|
145
|
+
@args.parse(%w[--mode second -abm foo -m first -- -])
|
123
146
|
}.must_raise(ExitStub).status.must_equal(2)
|
124
147
|
end
|
125
148
|
end
|
@@ -137,17 +160,17 @@ describe 'required option' do
|
|
137
160
|
end
|
138
161
|
|
139
162
|
it 'is really not optional' do
|
140
|
-
e = lambda { @args.parse
|
163
|
+
e = lambda { @args.parse(%w[-- file]) }.must_raise(ExitStub)
|
141
164
|
e.status.must_equal(2)
|
142
165
|
end
|
143
166
|
|
144
167
|
it 'is actually multiple too' do
|
145
|
-
@args.parse
|
168
|
+
@args.parse(%w[-rrrrr --legacy-required --required -r -- file])
|
146
169
|
@args['required'].count.must_equal(8)
|
147
170
|
end
|
148
171
|
|
149
172
|
it 'lives with an optional one' do
|
150
|
-
@args.parse
|
173
|
+
@args.parse(%w[--mode first -rmsecond])
|
151
174
|
@args['required'].count.must_equal(1)
|
152
175
|
end
|
153
176
|
end
|
@@ -155,20 +178,19 @@ end
|
|
155
178
|
describe 'input argument' do
|
156
179
|
before do
|
157
180
|
@b_manifest = a_manifest.merge({
|
158
|
-
:
|
159
|
-
:
|
160
|
-
:input => true
|
181
|
+
:arguments => (a_manifest[:arguments] + [{
|
182
|
+
:name => 'file2'
|
161
183
|
}])
|
162
184
|
})
|
163
185
|
@args = ArgParser.new(@b_manifest)
|
164
186
|
end
|
165
187
|
|
166
188
|
it 'terminates if name used as an option' do
|
167
|
-
lambda { @args.parse
|
189
|
+
lambda { @args.parse(%w[--file2 --]) }.must_raise(ExitStub)
|
168
190
|
end
|
169
191
|
|
170
192
|
it 'survives second optional argument' do
|
171
|
-
@args.parse
|
193
|
+
@args.parse(%w[file2])
|
172
194
|
@args['file'].value.must_equal('file2')
|
173
195
|
@args['file2'].value.must_equal(@args['file2'].default)
|
174
196
|
end
|
@@ -178,24 +200,18 @@ describe 'optional tiny features' do
|
|
178
200
|
before do
|
179
201
|
@b_manifest = a_manifest.merge({
|
180
202
|
:options => (a_manifest[:options] + [{
|
181
|
-
:names => ['a',
|
203
|
+
:names => ['a', 'aaaaa'],
|
182
204
|
:multiple => true,
|
183
|
-
:
|
205
|
+
:param => 'arg'
|
184
206
|
}])
|
185
207
|
})
|
186
208
|
@args = ArgParser.new(@b_manifest)
|
187
209
|
end
|
188
210
|
|
189
|
-
it 'ignores whitespace in option names' do
|
190
|
-
"\n aaaaa \t ".strip.must_equal('aaaaa')
|
191
|
-
@args.parse!(%w[--aaaaa foobar])
|
192
|
-
@args['aaaaa'].value.must_equal(['foobar'])
|
193
|
-
end
|
194
|
-
|
195
211
|
it 'allows to get value as string' do
|
196
|
-
@args.parse
|
212
|
+
@args.parse(%w[--aaaaa foo])
|
197
213
|
"#{@args['aaaaa']}".must_equal('foo')
|
198
|
-
@args.parse
|
214
|
+
@args.parse(%w[--aaaaa foo -abar])
|
199
215
|
"#{@args['aaaaa']}".must_equal('foo, bar')
|
200
216
|
end
|
201
217
|
end
|
data/spec/tools_spec.rb
CHANGED
@@ -17,9 +17,9 @@ describe 'hash behaviour' do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'allows to pack/unpack' do
|
20
|
-
obj1 = StubClass.new.hash2vars
|
20
|
+
obj1 = StubClass.new.hash2vars(@hash)
|
21
21
|
hash = obj1.to_hash
|
22
|
-
obj2 = StubClass.new.hash2vars
|
22
|
+
obj2 = StubClass.new.hash2vars(hash)
|
23
23
|
obj1.hash.each{|k, v| obj2.hash[k].must_equal(v)}
|
24
24
|
obj1.array.must_equal(obj2.array)
|
25
25
|
obj1.value.must_equal(obj2.value)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: argparser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0.RC1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sinm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,42 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1'
|
19
|
+
version: '1.7'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1'
|
26
|
+
version: '1.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '10'
|
33
|
+
version: '10.1'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '10'
|
40
|
+
version: '10.1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '4'
|
47
|
+
version: '4.7'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '4'
|
54
|
+
version: '4.7'
|
55
55
|
description: "== Yet another ruby command line argument parser library"
|
56
56
|
email: sinm.sinm@gmail.com
|
57
57
|
executables: []
|
@@ -67,12 +67,16 @@ files:
|
|
67
67
|
- LICENSE.txt
|
68
68
|
- README.md
|
69
69
|
- Rakefile
|
70
|
+
- TODO.md
|
70
71
|
- argparser.gemspec
|
71
72
|
- argparser.sublime-project
|
72
73
|
- lib/argparser.rb
|
73
|
-
- lib/argparser/
|
74
|
+
- lib/argparser/arg_parser.rb
|
75
|
+
- lib/argparser/argument.rb
|
76
|
+
- lib/argparser/errors.rb
|
74
77
|
- lib/argparser/option.rb
|
75
78
|
- lib/argparser/tools.rb
|
79
|
+
- lib/argparser/version.rb
|
76
80
|
- spec/argparser_spec.rb
|
77
81
|
- spec/spec_helper.rb
|
78
82
|
- spec/tools_spec.rb
|
@@ -91,9 +95,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
91
95
|
version: '0'
|
92
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
97
|
requirements:
|
94
|
-
- - "
|
98
|
+
- - ">"
|
95
99
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
100
|
+
version: 1.3.1
|
97
101
|
requirements: []
|
98
102
|
rubyforge_project:
|
99
103
|
rubygems_version: 2.2.2
|
@@ -1,116 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
#
|
3
|
-
|
4
|
-
class ArgParser
|
5
|
-
module DefaultParser
|
6
|
-
# Uses ARGV by default, but you may supply your own arguments
|
7
|
-
# It exits if bad arguments given or they aren't validated.
|
8
|
-
def parse!(arguments = ARGV)
|
9
|
-
options.each(&:reset!)
|
10
|
-
_check_manifest!
|
11
|
-
|
12
|
-
OPTS_RESERVED.each { |o|
|
13
|
-
next unless arguments.include?("--#{o}")
|
14
|
-
self[o].set_value(nil)
|
15
|
-
self[o].validate!(self)
|
16
|
-
self[o].reset! # If it didn't terminate while validating
|
17
|
-
}
|
18
|
-
|
19
|
-
args = arguments.dup
|
20
|
-
enough = false
|
21
|
-
while (a = args.shift)
|
22
|
-
if a == OPT_ENOUGH
|
23
|
-
enough = true
|
24
|
-
elsif enough || (a =~ /^[^-]/) || (a == '-')
|
25
|
-
_set_argument!(a)
|
26
|
-
elsif a =~ /^--(.+)/
|
27
|
-
_set_long_option!(a, args)
|
28
|
-
elsif a =~ /^-([^-].*)/
|
29
|
-
_set_short_options!(a, args)
|
30
|
-
else
|
31
|
-
terminate(2, OUT_UNKNOWN_OPTION % a)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
options.each { |o|
|
36
|
-
o.set_default!
|
37
|
-
o.on_first_error {|msg| terminate(2, msg)}
|
38
|
-
}
|
39
|
-
|
40
|
-
options.each { |o|
|
41
|
-
terminate(2, OUT_INVALID_OPTION % o.name) unless o.validate!(self)
|
42
|
-
}
|
43
|
-
|
44
|
-
self
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def _set_argument!(a)
|
50
|
-
if (input = inputs.find{|i| !i.value || i.multiple})
|
51
|
-
input.set_value(a)
|
52
|
-
else
|
53
|
-
terminate(2, OUT_UNEXPECTED_ARGUMENT % a)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def _set_long_option!(a, tail)
|
58
|
-
a = a[2..-1]
|
59
|
-
if a.size > 1 && (option = self[a]) && !option.input
|
60
|
-
if option.argument
|
61
|
-
terminate(2, OUT_OPTION_ARGUMENT_EXPECTED % a) if tail.empty?
|
62
|
-
option.set_value(tail.shift)
|
63
|
-
else
|
64
|
-
option.set_value(nil)
|
65
|
-
end
|
66
|
-
else
|
67
|
-
terminate(2, OUT_UNKNOWN_OPTION % $1)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def _set_short_options!(a, tail)
|
72
|
-
(a = a[1..-1]).chars.each_with_index do |char, index|
|
73
|
-
unless (option = self[char]) && !option.input
|
74
|
-
terminate(2, OUT_UNKNOWN_OPTION % char)
|
75
|
-
end
|
76
|
-
if option.argument
|
77
|
-
if a.size-1 == index
|
78
|
-
terminate(2, OUT_OPTION_ARGUMENT_EXPECTED % char) if tail.empty?
|
79
|
-
option.set_value(tail.shift)
|
80
|
-
else
|
81
|
-
option.set_value(a[index+1..-1])
|
82
|
-
break
|
83
|
-
end
|
84
|
-
else
|
85
|
-
option.set_value(nil)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def _check_manifest!
|
91
|
-
{:program => program, :version => version}.each do |k, v|
|
92
|
-
terminate(2, OUT_MANIFEST_EXPECTED % k) if !v || v.to_s.strip.empty?
|
93
|
-
end
|
94
|
-
|
95
|
-
is = inputs
|
96
|
-
is.each_with_index do |i, index|
|
97
|
-
if index < is.length-1 && i.multiple
|
98
|
-
terminate(2, OUT_MULTIPLE_INPUTS % i.name)
|
99
|
-
elsif i.names.size > 1
|
100
|
-
terminate(2, OUT_MULTIPLE_NAMES % i.name)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
opt = is.index{|i| !i.required} || is.size
|
104
|
-
req = is.rindex{|i| i.required} || 0
|
105
|
-
terminate(2, OUT_REQUIRED % is[req].name) if req > opt
|
106
|
-
|
107
|
-
names = {}
|
108
|
-
options.each do |option|
|
109
|
-
option.names.each do |name|
|
110
|
-
terminate(2, OUT_UNIQUE_NAME % name) if names.has_key?(name)
|
111
|
-
names[name] = option
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|