opt 0.1.0
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 +7 -0
- data/LICENSE.txt +165 -0
- data/README.md +85 -0
- data/doc/file.README.html +107 -0
- data/lib/opt.rb +65 -0
- data/lib/opt/command.rb +282 -0
- data/lib/opt/option.rb +198 -0
- data/lib/opt/program.rb +9 -0
- data/lib/opt/switch.rb +119 -0
- data/lib/opt/types/io.rb +4 -0
- data/lib/opt/version.rb +3 -0
- data/opt.gemspec +22 -0
- data/spec/opt/flag_spec.rb +100 -0
- data/spec/opt/option_spec.rb +232 -0
- data/spec/opt/switch_spec.rb +60 -0
- data/spec/opt_spec.rb +124 -0
- data/spec/readme_spec.rb +18 -0
- data/spec/spec_helper.rb +19 -0
- metadata +81 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec::Matchers.define :parse do |*expected|
|
|
4
|
+
match do |option|
|
|
5
|
+
result = {}
|
|
6
|
+
argv = expected.dup
|
|
7
|
+
option.parse!(argv, result)
|
|
8
|
+
|
|
9
|
+
expect(result).to include results if results.any?
|
|
10
|
+
expect(argv).to eq expected[1..-1] if shift?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
chain :emit do |hash|
|
|
14
|
+
results.merge! hash
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
chain :shift_argv do
|
|
18
|
+
@shift = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def shift?
|
|
22
|
+
@shift.nil? ? (@shift = false) : @shift
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def results
|
|
26
|
+
@results ||= {}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def t(type, value)
|
|
31
|
+
Opt::Command::Token.new(type, value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe Opt::Option do
|
|
35
|
+
let(:option) { described_class.new(defin, {name: :test}.merge(opts)) }
|
|
36
|
+
let(:defin) { '' }
|
|
37
|
+
let(:opts) { {} }
|
|
38
|
+
subject { option }
|
|
39
|
+
|
|
40
|
+
describe '.parse_nargs' do
|
|
41
|
+
it 'should parse single fixnum' do
|
|
42
|
+
expect(described_class.parse_nargs(5)).to eq 5..5
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'should parse range' do
|
|
46
|
+
expect(described_class.parse_nargs(2..6)).to eq 2..6
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'should parse fixnum string' do
|
|
50
|
+
expect(described_class.parse_nargs('12')).to eq 12..12
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'should parse special infinity string' do
|
|
54
|
+
expect(described_class.parse_nargs('*')).to eq 0..Float::INFINITY
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'should parse special infinity symbol (I)' do
|
|
58
|
+
expect(described_class.parse_nargs(:inf)).to eq 0..Float::INFINITY
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'should parse special infinity symbol (II)' do
|
|
62
|
+
expect(described_class.parse_nargs(:infinity)).to eq 0..Float::INFINITY
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'should parse array (I)' do
|
|
66
|
+
expect(described_class.parse_nargs([3, :inf])).to eq 3..Float::INFINITY
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'should parse array (II)' do
|
|
70
|
+
expect(described_class.parse_nargs([3, 100])).to eq 3..100
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'should reject invalid string' do
|
|
74
|
+
expect do
|
|
75
|
+
described_class.parse_nargs('a12')
|
|
76
|
+
end.to raise_error(/invalid value for Integer/)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'should flip negative range' do
|
|
80
|
+
expect(described_class.parse_nargs(6..2)).to eq 2..6
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'should reject negative range' do
|
|
84
|
+
expect do
|
|
85
|
+
described_class.parse_nargs(-2..2)
|
|
86
|
+
end.to raise_error(/Argument number must not be less than zero/)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '#initialize' do
|
|
91
|
+
it 'should derive name from switches (I)' do
|
|
92
|
+
expect(described_class.new('-h').name).to eq 'h'
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'should derive name from switches (II)' do
|
|
96
|
+
expect(described_class.new('--help').name).to eq 'help'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'should use name from opts' do
|
|
100
|
+
expect(described_class.new('--help', name: :test).name).to eq 'test'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'should parse switches' do
|
|
104
|
+
option = described_class.new('-h, --help')
|
|
105
|
+
expect(option.switches.size).to eq 2
|
|
106
|
+
expect(option.switches.each.map(&:name)).to match_array %w(h help)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'should parse free-text arg' do
|
|
110
|
+
option = described_class.new('file', nargs: 0..Float::INFINITY)
|
|
111
|
+
expect(option.switches.size).to eq 0
|
|
112
|
+
expect(option).to be_text
|
|
113
|
+
expect(option.name).to eq 'file'
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
describe '#parse!' do
|
|
118
|
+
context 'with shirt code' do
|
|
119
|
+
let(:defin) { '-h, -2, -め' }
|
|
120
|
+
|
|
121
|
+
it 'should match short token' do
|
|
122
|
+
option.parse! argv = [t(:short, 'h')], result = {}
|
|
123
|
+
|
|
124
|
+
expect(argv).to be_empty
|
|
125
|
+
expect(result).to eq 'test' => true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'should match grouped short token' do
|
|
129
|
+
option.parse! argv = [t(:short, '2b4')], result = {}
|
|
130
|
+
|
|
131
|
+
expect(argv.size).to eq 1
|
|
132
|
+
expect(argv.first.value).to eq 'b4'
|
|
133
|
+
expect(result).to eq 'test' => true
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'should match short unicode token' do
|
|
137
|
+
option.parse! argv = [t(:short, 'め')], result = {}
|
|
138
|
+
|
|
139
|
+
expect(argv).to be_empty
|
|
140
|
+
expect(result).to eq 'test' => true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'should not match long token' do
|
|
144
|
+
option.parse! argv = [t(:long, 'め')], result = {}
|
|
145
|
+
|
|
146
|
+
expect(argv.size).to eq 1
|
|
147
|
+
expect(result).to be_empty
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'should not match different short token' do
|
|
151
|
+
option.parse! argv = [t(:short, '1')], result = {}
|
|
152
|
+
|
|
153
|
+
expect(argv.size).to eq 1
|
|
154
|
+
expect(result).to be_empty
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
context 'with long code' do
|
|
159
|
+
let(:defin) { '--help, --2, --めこそ' }
|
|
160
|
+
|
|
161
|
+
it 'should match long argv (I)' do
|
|
162
|
+
option.parse! argv = [t(:long, 'help')], result = {}
|
|
163
|
+
|
|
164
|
+
expect(argv).to be_empty
|
|
165
|
+
expect(result).to eq 'test' => true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'should match long argv (II)' do
|
|
169
|
+
option.parse! argv = [t(:long, '2')], result = {}
|
|
170
|
+
|
|
171
|
+
expect(argv).to be_empty
|
|
172
|
+
expect(result).to eq 'test' => true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'should match long argv (III)' do
|
|
176
|
+
option.parse! argv = [t(:long, 'めこそ')], result = {}
|
|
177
|
+
|
|
178
|
+
expect(argv).to be_empty
|
|
179
|
+
expect(result).to eq 'test' => true
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
context 'with free-text' do
|
|
184
|
+
let(:defin) { 'file' }
|
|
185
|
+
let(:opts) { {nargs: 1} }
|
|
186
|
+
|
|
187
|
+
it 'should match text' do
|
|
188
|
+
option.parse! argv = [t(:text, 'help')], result = {}
|
|
189
|
+
|
|
190
|
+
expect(argv).to be_empty
|
|
191
|
+
expect(result).to eq 'test' => 'help'
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context 'with lower bound' do
|
|
195
|
+
let(:opts) { {nargs: 2..Float::INFINITY} }
|
|
196
|
+
|
|
197
|
+
it 'should reject to less arguments' do
|
|
198
|
+
expect do
|
|
199
|
+
option.parse! [t(:text, 'a')], {}
|
|
200
|
+
end.to raise_error(/wrong number of arguments/)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'should accept enough arguments' do
|
|
204
|
+
option.parse! argv = [t(:text, 'a'), t(:text, 'b')], result = {}
|
|
205
|
+
|
|
206
|
+
expect(argv).to be_empty
|
|
207
|
+
expect(result).to eq 'test' => %w(a b)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
context 'with upper bound' do
|
|
212
|
+
let(:opts) { {nargs: 0..2} }
|
|
213
|
+
|
|
214
|
+
it 'should eat arguments up to upper bound' do
|
|
215
|
+
option.parse! argv = [t(:text, 'a'), t(:text, 'b'), t(:text, 'c')],
|
|
216
|
+
result = {}
|
|
217
|
+
|
|
218
|
+
expect(argv).to eq [t(:text, 'c')]
|
|
219
|
+
expect(result).to eq 'test' => %w(a b)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'should eat less arguments if there are less tokens' do
|
|
223
|
+
option.parse! argv = [t(:text, 'a'), t(:short, 'b'), t(:text, 'c')],
|
|
224
|
+
result = {}
|
|
225
|
+
|
|
226
|
+
expect(argv).to eq [t(:short, 'b'), t(:text, 'c')]
|
|
227
|
+
expect(result).to eq 'test' => %w(a)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Opt::Switch do
|
|
4
|
+
describe '.new' do
|
|
5
|
+
subject { described_class.new arg }
|
|
6
|
+
|
|
7
|
+
context 'with Switch object' do
|
|
8
|
+
let(:arg) { described_class.new '-x' }
|
|
9
|
+
|
|
10
|
+
it 'should return original switch object' do
|
|
11
|
+
is_expected.to eql arg
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '#initialize' do
|
|
17
|
+
subject { described_class.new arg }
|
|
18
|
+
|
|
19
|
+
context 'with short code string' do
|
|
20
|
+
let(:arg) { '-h' }
|
|
21
|
+
|
|
22
|
+
it 'should parse to short code' do
|
|
23
|
+
expect(subject).to be_short
|
|
24
|
+
expect(subject.name).to eq 'h'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context 'with long code string' do
|
|
29
|
+
let(:arg) { '--help' }
|
|
30
|
+
|
|
31
|
+
it 'should parse to long code' do
|
|
32
|
+
expect(subject).to be_long
|
|
33
|
+
expect(subject.name).to eq 'help'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'with unicode parameter string' do
|
|
38
|
+
let(:arg) { '--めこそ' }
|
|
39
|
+
|
|
40
|
+
it 'should parse to long code' do
|
|
41
|
+
expect(subject).to be_long
|
|
42
|
+
expect(subject.name).to eq 'めこそ'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '.parse' do
|
|
48
|
+
subject { described_class.parse arg }
|
|
49
|
+
|
|
50
|
+
context 'with string' do
|
|
51
|
+
let(:arg) { '-h, --help, -h' }
|
|
52
|
+
|
|
53
|
+
it 'should parse to Set' do
|
|
54
|
+
expect(subject).to be_a Set
|
|
55
|
+
expect(subject.size).to eq 2
|
|
56
|
+
expect(subject.map(&:name)).to match_array %w(h help)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/spec/opt_spec.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Opt do
|
|
4
|
+
let(:opt) { Opt.new }
|
|
5
|
+
|
|
6
|
+
it 'should parse options (I)' do
|
|
7
|
+
opt.option '--help, -h', value: :yes
|
|
8
|
+
|
|
9
|
+
result = opt.parse %w(-h)
|
|
10
|
+
expect(result.help).to eq :yes
|
|
11
|
+
|
|
12
|
+
result = opt.parse %w()
|
|
13
|
+
expect(result.help).to eq nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'should detect double-dash' do
|
|
17
|
+
opt.option '-v', name: :version
|
|
18
|
+
opt.option 'rest', nargs: '+'
|
|
19
|
+
|
|
20
|
+
result = opt.parse %w(-v -- -h)
|
|
21
|
+
expect(result.version?).to be true
|
|
22
|
+
expect(result.rest).to eq %w(-h)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'should parse options (II)' do
|
|
26
|
+
opt.option '--help', default: :no
|
|
27
|
+
opt.option '--version'
|
|
28
|
+
|
|
29
|
+
result = opt.parse %w(--version)
|
|
30
|
+
expect(result.version).to eql true
|
|
31
|
+
expect(result.help).to eql :no
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'should parse options with argument' do
|
|
35
|
+
opt.option '--level, -l', nargs: 1
|
|
36
|
+
|
|
37
|
+
result = opt.parse %w(--level 5)
|
|
38
|
+
expect(result.level).to eq '5'
|
|
39
|
+
|
|
40
|
+
result = opt.parse %w(--level=5)
|
|
41
|
+
expect(result.level).to eq '5'
|
|
42
|
+
|
|
43
|
+
result = opt.parse %w(-l 5)
|
|
44
|
+
expect(result.level).to eq '5'
|
|
45
|
+
|
|
46
|
+
result = opt.parse %w(-l5)
|
|
47
|
+
expect(result.level).to eq '5'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'should parse options with arguments' do
|
|
51
|
+
opt.option '--level, -l', nargs: 2..3
|
|
52
|
+
|
|
53
|
+
result = opt.parse %w(--level 5 6)
|
|
54
|
+
expect(result.level).to eq %w(5 6)
|
|
55
|
+
|
|
56
|
+
result = opt.parse %w(-l 5 6)
|
|
57
|
+
expect(result.level).to eq %w(5 6)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'should parse flags' do
|
|
61
|
+
pending
|
|
62
|
+
|
|
63
|
+
opt.option :force, '-f, --force', default: false
|
|
64
|
+
opt.option :quiet, '-q, --quiet', default: true
|
|
65
|
+
|
|
66
|
+
result = opt.parse %w(--force)
|
|
67
|
+
expect(result[:force]).to be true
|
|
68
|
+
expect(result[:quiet]).to be true
|
|
69
|
+
|
|
70
|
+
result = opt.parse %w(--no-force -q)
|
|
71
|
+
expect(result[:force]).to be false
|
|
72
|
+
expect(result[:quiet]).to be true
|
|
73
|
+
|
|
74
|
+
result = opt.parse %w(-f --no-quiet)
|
|
75
|
+
expect(result[:force]).to be true
|
|
76
|
+
expect(result[:quiet]).to be false
|
|
77
|
+
|
|
78
|
+
result = opt.parse %w(--no-quiet)
|
|
79
|
+
expect(result[:force]).to be false
|
|
80
|
+
expect(result[:quiet]).to be false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'should parse subcommands (I)' do
|
|
84
|
+
opt.option '--force, -f'
|
|
85
|
+
|
|
86
|
+
opt.command :add, 'Add some things' do |cmd|
|
|
87
|
+
cmd.option '--verbose'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
result = opt.parse %w(-f add --verbose)
|
|
91
|
+
|
|
92
|
+
expect(result.force?).to eq true
|
|
93
|
+
expect(result.verbose?).to eq true
|
|
94
|
+
expect(result.command).to eq %w(add)
|
|
95
|
+
|
|
96
|
+
result = opt.parse %w(add -f --verbose)
|
|
97
|
+
|
|
98
|
+
expect(result.force?).to eq true
|
|
99
|
+
expect(result.verbose?).to eq true
|
|
100
|
+
expect(result.command).to eq %w(add)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'should parse subcommands (II)' do
|
|
104
|
+
opt.option '-f'
|
|
105
|
+
|
|
106
|
+
opt.command :add, 'Add some things' do |cmd|
|
|
107
|
+
cmd.option '-a'
|
|
108
|
+
cmd.option '-j', nargs: 1
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
result = opt.parse %w(add -af)
|
|
112
|
+
|
|
113
|
+
expect(result.a?).to eq true
|
|
114
|
+
expect(result.f?).to eq true
|
|
115
|
+
expect(result.command).to eq %w(add)
|
|
116
|
+
|
|
117
|
+
result = opt.parse %w(add -ajf)
|
|
118
|
+
|
|
119
|
+
expect(result.a?).to eq true
|
|
120
|
+
expect(result.f?).to eq nil
|
|
121
|
+
expect(result.j).to eq 'f'
|
|
122
|
+
expect(result.command).to eq %w(add)
|
|
123
|
+
end
|
|
124
|
+
end
|
data/spec/readme_spec.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Opt do
|
|
4
|
+
let(:opt) { Opt.new }
|
|
5
|
+
|
|
6
|
+
it 'should pass README examples' do
|
|
7
|
+
content = File.read(File.expand_path('../../README.md', __FILE__))
|
|
8
|
+
|
|
9
|
+
while content =~ /^```ruby\n(.*?)^```\n(.*?)\z/m
|
|
10
|
+
example = $1
|
|
11
|
+
content = $2
|
|
12
|
+
|
|
13
|
+
example.gsub!(/^\s*(.*?)\s*#=>\s*(.*?)\s*$/, 'expect( \1 ).to eq( \2 )')
|
|
14
|
+
|
|
15
|
+
instance_eval example, __FILE__, __LINE__
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'rspec'
|
|
2
|
+
|
|
3
|
+
if ENV['CI'] || ENV['COVERAGE']
|
|
4
|
+
require 'coveralls'
|
|
5
|
+
Coveralls.wear! do
|
|
6
|
+
add_filter 'spec'
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Load stic
|
|
11
|
+
require 'opt'
|
|
12
|
+
|
|
13
|
+
# Load spec support files
|
|
14
|
+
Dir[File.expand_path('spec/support/**/*.rb')].each {|f| require f }
|
|
15
|
+
|
|
16
|
+
RSpec.configure do |config|
|
|
17
|
+
# Random order
|
|
18
|
+
config.order = 'random'
|
|
19
|
+
end
|