cli 0.0.4 → 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.
- data/README.md +151 -0
- data/VERSION +1 -1
- data/cli.gemspec +14 -5
- data/examples/processor +17 -0
- data/examples/sinatra +41 -0
- data/lib/cli.rb +40 -8
- data/lib/cli/arguments.rb +4 -0
- data/lib/cli/dsl.rb +23 -16
- data/lib/cli/options.rb +4 -11
- data/spec/argument_spec.rb +133 -0
- data/spec/cli_spec.rb +17 -557
- data/spec/conflict_reporting_spec.rb +69 -0
- data/spec/option_spec.rb +155 -0
- data/spec/separator_spec.rb +57 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/stdin_spec.rb +74 -0
- data/spec/switch_spec.rb +62 -0
- data/spec/usage_spec.rb +303 -0
- metadata +15 -6
- data/README.rdoc +0 -19
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
3
|
+
|
4
|
+
describe CLI do
|
5
|
+
describe "name conflict reporting" do
|
6
|
+
it "raise error when long names configlict" do
|
7
|
+
lambda {
|
8
|
+
ps = CLI.new do
|
9
|
+
switch :location
|
10
|
+
switch :location
|
11
|
+
end
|
12
|
+
}.should raise_error CLI::ParserError::LongNameSpecifiedTwiceError, 'switch location specified twice'
|
13
|
+
|
14
|
+
lambda {
|
15
|
+
ps = CLI.new do
|
16
|
+
option :location
|
17
|
+
option :location
|
18
|
+
end
|
19
|
+
}.should raise_error CLI::ParserError::LongNameSpecifiedTwiceError, 'option location specified twice'
|
20
|
+
|
21
|
+
lambda {
|
22
|
+
ps = CLI.new do
|
23
|
+
switch :location
|
24
|
+
option :location
|
25
|
+
end
|
26
|
+
}.should raise_error CLI::ParserError::LongNameSpecifiedTwiceError, 'switch and option location specified twice'
|
27
|
+
|
28
|
+
lambda {
|
29
|
+
ps = CLI.new do
|
30
|
+
option :location
|
31
|
+
switch :location
|
32
|
+
end
|
33
|
+
}.should raise_error CLI::ParserError::LongNameSpecifiedTwiceError, 'option and switch location specified twice'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "short name conflict reporting" do
|
38
|
+
it "raise error when short names configlict" do
|
39
|
+
lambda {
|
40
|
+
ps = CLI.new do
|
41
|
+
switch :location, :short => :l
|
42
|
+
switch :location2, :short => :l
|
43
|
+
end
|
44
|
+
}.should raise_error CLI::ParserError::ShortNameSpecifiedTwiceError, 'short switch l specified twice'
|
45
|
+
|
46
|
+
lambda {
|
47
|
+
ps = CLI.new do
|
48
|
+
option :location, :short => :l
|
49
|
+
option :location2, :short => :l
|
50
|
+
end
|
51
|
+
}.should raise_error CLI::ParserError::ShortNameSpecifiedTwiceError, 'short option l specified twice'
|
52
|
+
|
53
|
+
lambda {
|
54
|
+
ps = CLI.new do
|
55
|
+
switch :location, :short => :l
|
56
|
+
option :location2, :short => :l
|
57
|
+
end
|
58
|
+
}.should raise_error CLI::ParserError::ShortNameSpecifiedTwiceError, 'short switch and option l specified twice'
|
59
|
+
|
60
|
+
lambda {
|
61
|
+
ps = CLI.new do
|
62
|
+
option :location2, :short => :l
|
63
|
+
switch :location, :short => :l
|
64
|
+
end
|
65
|
+
}.should raise_error CLI::ParserError::ShortNameSpecifiedTwiceError, 'short option and switch l specified twice'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
data/spec/option_spec.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe CLI do
|
4
|
+
describe 'option handling' do
|
5
|
+
it "should handle long option names" do
|
6
|
+
ps = CLI.new do
|
7
|
+
option :location
|
8
|
+
end.parse(['--location', 'singapore'])
|
9
|
+
ps.location.should be_a String
|
10
|
+
ps.location.should == 'singapore'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should handle short option names" do
|
14
|
+
ps = CLI.new do
|
15
|
+
option :location, :short => :l
|
16
|
+
end.parse(['-l', 'singapore'])
|
17
|
+
ps.location.should be_a String
|
18
|
+
ps.location.should == 'singapore'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should support casting" do
|
22
|
+
ps = CLI.new do
|
23
|
+
option :size, :cast => Integer
|
24
|
+
end.parse(['--size', '24'])
|
25
|
+
ps.size.should be_a Integer
|
26
|
+
ps.size.should == 24
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should support casting with lambda" do
|
30
|
+
ps = CLI.new do
|
31
|
+
option :size, :cast => lambda{|v| v.to_i + 2}
|
32
|
+
end.parse(['--size', '24'])
|
33
|
+
ps.size.should be_a Integer
|
34
|
+
ps.size.should == 26
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should support casting with class" do
|
38
|
+
class Upcaser
|
39
|
+
def initialize(string)
|
40
|
+
@value = string.upcase
|
41
|
+
end
|
42
|
+
attr_reader :value
|
43
|
+
end
|
44
|
+
|
45
|
+
ps = CLI.new do
|
46
|
+
option :text, :cast => Upcaser
|
47
|
+
end.parse(['--text', 'hello world'])
|
48
|
+
ps.text.should be_a Upcaser
|
49
|
+
ps.text.value.should == 'HELLO WORLD'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should handle default values" do
|
53
|
+
ps = CLI.new do
|
54
|
+
option :location, :default => 'singapore'
|
55
|
+
option :size, :cast => Integer, :default => 23
|
56
|
+
end.parse([])
|
57
|
+
ps.location.should be_a String
|
58
|
+
ps.location.should == 'singapore'
|
59
|
+
ps.size.should be_a Integer
|
60
|
+
ps.size.should == 23
|
61
|
+
end
|
62
|
+
|
63
|
+
it "default value is casted" do
|
64
|
+
ps = CLI.new do
|
65
|
+
option :location, :default => 'singapore'
|
66
|
+
option :size, :cast => Integer, :default => 23.99
|
67
|
+
end.parse([])
|
68
|
+
ps.location.should be_a String
|
69
|
+
ps.location.should == 'singapore'
|
70
|
+
ps.size.should be_a Integer
|
71
|
+
ps.size.should == 23
|
72
|
+
end
|
73
|
+
|
74
|
+
it "not given and not defined options should be nil" do
|
75
|
+
ps = CLI.new do
|
76
|
+
option :size, :cast => Integer
|
77
|
+
end.parse([])
|
78
|
+
ps.size.should be_nil
|
79
|
+
ps.gold.should be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should handle multiple long and short intermixed options" do
|
83
|
+
ps = CLI.new do
|
84
|
+
option :location, :short => :l
|
85
|
+
option :group, :default => 'red'
|
86
|
+
option :power_up, :short => :p
|
87
|
+
option :speed, :short => :s, :cast => Integer
|
88
|
+
option :not_given
|
89
|
+
option :size
|
90
|
+
end.parse(['-l', 'singapore', '--power-up', 'yes', '-s', '24', '--size', 'XXXL'])
|
91
|
+
ps.group.should == 'red'
|
92
|
+
ps.power_up.should == 'yes'
|
93
|
+
ps.speed.should == 24
|
94
|
+
ps.not_given.should be_nil
|
95
|
+
ps.size.should == 'XXXL'
|
96
|
+
ps.gold.should be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should raise error if not symbol and optional hash is passed" do
|
100
|
+
lambda {
|
101
|
+
ps = CLI.new do
|
102
|
+
option 'number'
|
103
|
+
end
|
104
|
+
}.should raise_error CLI::ParserError::NameArgumetNotSymbolError, "option name has to be of type Symbol, got String"
|
105
|
+
|
106
|
+
lambda {
|
107
|
+
ps = CLI.new do
|
108
|
+
option :number, :test
|
109
|
+
end
|
110
|
+
}.should raise_error CLI::ParserError::OptionsArgumentNotHashError, "option options has to be of type Hash, got Symbol"
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should raise error on missing option argument" do
|
114
|
+
ps = CLI.new do
|
115
|
+
option :location
|
116
|
+
end
|
117
|
+
|
118
|
+
lambda {
|
119
|
+
ps.parse(['--location'])
|
120
|
+
}.should raise_error CLI::ParsingError::MissingOptionValueError, 'missing value for option --location'
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should raise error on missing mandatory option" do
|
124
|
+
ps = CLI.new do
|
125
|
+
option :location
|
126
|
+
option :weight, :required => true
|
127
|
+
option :size, :required => true
|
128
|
+
option :group, :default => 'red'
|
129
|
+
option :speed, :short => :s, :cast => Integer
|
130
|
+
end
|
131
|
+
|
132
|
+
lambda {
|
133
|
+
ps.parse([])
|
134
|
+
}.should raise_error CLI::ParsingError::MandatoryOptionsNotSpecifiedError, "mandatory options not specified: --size, --weight"
|
135
|
+
end
|
136
|
+
|
137
|
+
it "by default option value should be nil" do
|
138
|
+
ps = CLI.new do
|
139
|
+
option :location
|
140
|
+
option :speed, :short => :s, :cast => Integer
|
141
|
+
option :weight, :required => true
|
142
|
+
option :group, :default => 'red'
|
143
|
+
end
|
144
|
+
|
145
|
+
o = ps.parse(['--weight', '123'])
|
146
|
+
|
147
|
+
o.location.should be_nil
|
148
|
+
o.speed.should be_nil
|
149
|
+
|
150
|
+
o.weight.should == '123'
|
151
|
+
o.group.should == 'red'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe CLI do
|
4
|
+
describe "argument separator" do
|
5
|
+
it "should allow separating arguments from switches so that arguments can contain switch like elements" do
|
6
|
+
ps = CLI.new do
|
7
|
+
option :location, :short => :l
|
8
|
+
option :group, :default => 'red'
|
9
|
+
switch :debug
|
10
|
+
switch :verbose
|
11
|
+
argument :test
|
12
|
+
end.parse(['-l', 'singapore', '--debug', '--', '--verbose'])
|
13
|
+
|
14
|
+
ps.help.should be_nil
|
15
|
+
ps.location.should == 'singapore'
|
16
|
+
ps.group.should == 'red'
|
17
|
+
ps.debug.should be_true
|
18
|
+
ps.test.should == '--verbose'
|
19
|
+
ps.verbose.should be_nil
|
20
|
+
|
21
|
+
ps = CLI.new do
|
22
|
+
option :location, :short => :l
|
23
|
+
option :group, :default => 'red'
|
24
|
+
switch :debug
|
25
|
+
switch :verbose
|
26
|
+
argument :test
|
27
|
+
end.parse(['-l', 'singapore', '--debug', '--help'])
|
28
|
+
|
29
|
+
ps.location.should be_nil
|
30
|
+
ps.group.should be_nil
|
31
|
+
ps.debug.should be_nil
|
32
|
+
ps.verbose.should be_nil
|
33
|
+
ps.help.should_not be_nil
|
34
|
+
|
35
|
+
ps = CLI.new do
|
36
|
+
option :location, :short => :l
|
37
|
+
option :group, :default => 'red'
|
38
|
+
switch :debug
|
39
|
+
switch :merge, :short => :m
|
40
|
+
switch :verbose
|
41
|
+
argument :test
|
42
|
+
argument :test2
|
43
|
+
argument :test3
|
44
|
+
end.parse(['-l', 'singapore', '--debug', '--', '--help', '--version', '-m'])
|
45
|
+
|
46
|
+
ps.help.should be_nil
|
47
|
+
ps.location.should == 'singapore'
|
48
|
+
ps.group.should == 'red'
|
49
|
+
ps.debug.should be_true
|
50
|
+
ps.test.should == '--help'
|
51
|
+
ps.test2.should == '--version'
|
52
|
+
ps.test3.should == '-m'
|
53
|
+
ps.verbose.should be_nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -9,3 +9,57 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
9
9
|
RSpec.configure do |config|
|
10
10
|
|
11
11
|
end
|
12
|
+
|
13
|
+
def stdin_write(data)
|
14
|
+
r, w = IO.pipe
|
15
|
+
old_stdin = STDIN.clone
|
16
|
+
STDIN.reopen r
|
17
|
+
Thread.new do
|
18
|
+
w.write data
|
19
|
+
w.close
|
20
|
+
end
|
21
|
+
begin
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
STDIN.reopen old_stdin
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def stdout_read
|
29
|
+
r, w = IO.pipe
|
30
|
+
old_stdout = STDOUT.clone
|
31
|
+
STDOUT.reopen(w)
|
32
|
+
data = ''
|
33
|
+
t = Thread.new do
|
34
|
+
data << r.read
|
35
|
+
end
|
36
|
+
begin
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
w.close
|
40
|
+
STDOUT.reopen(old_stdout)
|
41
|
+
end
|
42
|
+
t.join
|
43
|
+
data
|
44
|
+
end
|
45
|
+
|
46
|
+
def stderr_read
|
47
|
+
r, w = IO.pipe
|
48
|
+
old_stdout = STDERR.clone
|
49
|
+
STDERR.reopen(w)
|
50
|
+
data = ''
|
51
|
+
t = Thread.new do
|
52
|
+
data << r.read
|
53
|
+
end
|
54
|
+
begin
|
55
|
+
yield
|
56
|
+
ensure
|
57
|
+
w.close
|
58
|
+
STDERR.reopen(old_stdout)
|
59
|
+
end
|
60
|
+
t.join
|
61
|
+
data
|
62
|
+
end
|
63
|
+
|
64
|
+
require 'cli'
|
65
|
+
|
data/spec/stdin_spec.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe CLI do
|
4
|
+
describe 'STDIN handling' do
|
5
|
+
before :all do
|
6
|
+
@yaml = <<EOF
|
7
|
+
---
|
8
|
+
:parser:
|
9
|
+
:successes: 41
|
10
|
+
:failures: 0
|
11
|
+
EOF
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be nil if not specified" do
|
15
|
+
ps = CLI.new.parse
|
16
|
+
ps.stdin.should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return IO if stdin is defined" do
|
20
|
+
ps = CLI.new do
|
21
|
+
stdin
|
22
|
+
end.parse
|
23
|
+
ps.stdin.should be_a IO
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should support casting to YAML" do
|
27
|
+
ps = nil
|
28
|
+
ss = CLI.new do
|
29
|
+
stdin :log_data, :cast => YAML, :description => 'log statistic data in YAML format'
|
30
|
+
end
|
31
|
+
|
32
|
+
stdin_write(@yaml) do
|
33
|
+
ps = ss.parse
|
34
|
+
end
|
35
|
+
|
36
|
+
ps.stdin.should == {:parser=>{:successes=>41, :failures=>0}}
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should support casting with lambda" do
|
40
|
+
ps = nil
|
41
|
+
ss = CLI.new do
|
42
|
+
stdin :log_data, :cast => lambda{|sin| sin.read.upcase}, :description => 'log statistic data in YAML format'
|
43
|
+
end
|
44
|
+
|
45
|
+
stdin_write('hello world') do
|
46
|
+
ps = ss.parse
|
47
|
+
end
|
48
|
+
|
49
|
+
ps.stdin.should == 'HELLO WORLD'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should support casting with custom class" do
|
53
|
+
class Upcaser
|
54
|
+
def initialize(io)
|
55
|
+
@value = io.read.upcase
|
56
|
+
end
|
57
|
+
attr_reader :value
|
58
|
+
end
|
59
|
+
|
60
|
+
ps = nil
|
61
|
+
ss = CLI.new do
|
62
|
+
stdin :log_data, :cast => Upcaser, :description => 'log statistic data in YAML format'
|
63
|
+
end
|
64
|
+
|
65
|
+
stdin_write('hello world') do
|
66
|
+
ps = ss.parse
|
67
|
+
end
|
68
|
+
|
69
|
+
ps.stdin.should be_a Upcaser
|
70
|
+
ps.stdin.value.should == 'HELLO WORLD'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
data/spec/switch_spec.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe CLI do
|
4
|
+
describe 'switch handling' do
|
5
|
+
it "should handle long switch names" do
|
6
|
+
ps = CLI.new do
|
7
|
+
switch :location
|
8
|
+
switch :unset
|
9
|
+
end.parse(['--location'])
|
10
|
+
ps.location.should be_true
|
11
|
+
ps.unset.should be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should handle short switch names" do
|
15
|
+
ps = CLI.new do
|
16
|
+
switch :location, :short => :l
|
17
|
+
switch :unset, :short => :u
|
18
|
+
end.parse(['-l'])
|
19
|
+
ps.location.should be_true
|
20
|
+
ps.unset.should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise error if not symbol and optional hash is passed" do
|
24
|
+
lambda {
|
25
|
+
ps = CLI.new do
|
26
|
+
switch 'number'
|
27
|
+
end.parse([])
|
28
|
+
}.should raise_error CLI::ParserError::NameArgumetNotSymbolError, "switch name has to be of type Symbol, got String"
|
29
|
+
|
30
|
+
lambda {
|
31
|
+
ps = CLI.new do
|
32
|
+
switch :number, :test
|
33
|
+
end
|
34
|
+
}.should raise_error CLI::ParserError::OptionsArgumentNotHashError, "switch options has to be of type Hash, got Symbol"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "shoud raise error is short name is invalid" do
|
38
|
+
lambda {
|
39
|
+
ps = CLI.new do
|
40
|
+
switch :location, :short => "l"
|
41
|
+
end
|
42
|
+
}.should raise_error CLI::ParserError::ShortNameNotSymbolError, 'short name has to be of type Symbol, got String'
|
43
|
+
|
44
|
+
lambda {
|
45
|
+
ps = CLI.new do
|
46
|
+
switch :location, :short => :abc
|
47
|
+
end
|
48
|
+
}.should raise_error CLI::ParserError::ShortNameIsInvalidError, 'short name has to be one letter symbol, got abc'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should raise error on unrecognized switch" do
|
52
|
+
ps = CLI.new do
|
53
|
+
option :location
|
54
|
+
end
|
55
|
+
|
56
|
+
lambda {
|
57
|
+
ps.parse(['--xxx'])
|
58
|
+
}.should raise_error CLI::ParsingError::UnknownSwitchError, 'unknown switch --xxx'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|