wool 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/LICENSE +45 -0
- data/README.rdoc +17 -0
- data/Rakefile +77 -0
- data/TODO.md +17 -0
- data/VERSION +1 -0
- data/bin/wool +4 -0
- data/features/step_definitions/wool_steps.rb +39 -0
- data/features/support/env.rb +14 -0
- data/features/support/testdata/1_input +1 -0
- data/features/support/testdata/1_output +1 -0
- data/features/support/testdata/2_input +4 -0
- data/features/support/testdata/2_output +4 -0
- data/features/support/testdata/3_input +8 -0
- data/features/support/testdata/3_output +11 -0
- data/features/support/testdata/4_input +5 -0
- data/features/support/testdata/4_output +5 -0
- data/features/wool.feature +24 -0
- data/lib/wool.rb +40 -0
- data/lib/wool/advice/advice.rb +42 -0
- data/lib/wool/advice/comment_advice.rb +37 -0
- data/lib/wool/analysis/annotations.rb +34 -0
- data/lib/wool/analysis/annotations/next_annotation.rb +26 -0
- data/lib/wool/analysis/annotations/parent_annotation.rb +20 -0
- data/lib/wool/analysis/annotations/scope_annotation.rb +37 -0
- data/lib/wool/analysis/lexical_analysis.rb +165 -0
- data/lib/wool/analysis/protocol_registry.rb +32 -0
- data/lib/wool/analysis/protocols.rb +82 -0
- data/lib/wool/analysis/scope.rb +13 -0
- data/lib/wool/analysis/sexp_analysis.rb +98 -0
- data/lib/wool/analysis/signature.rb +16 -0
- data/lib/wool/analysis/symbol.rb +10 -0
- data/lib/wool/analysis/visitor.rb +36 -0
- data/lib/wool/analysis/wool_class.rb +47 -0
- data/lib/wool/rake/task.rb +42 -0
- data/lib/wool/runner.rb +156 -0
- data/lib/wool/scanner.rb +160 -0
- data/lib/wool/support/module_extensions.rb +84 -0
- data/lib/wool/third_party/trollop.rb +845 -0
- data/lib/wool/warning.rb +145 -0
- data/lib/wool/warnings/comment_spacing.rb +30 -0
- data/lib/wool/warnings/extra_blank_lines.rb +29 -0
- data/lib/wool/warnings/extra_whitespace.rb +15 -0
- data/lib/wool/warnings/line_length.rb +113 -0
- data/lib/wool/warnings/misaligned_unindentation.rb +16 -0
- data/lib/wool/warnings/operator_spacing.rb +63 -0
- data/lib/wool/warnings/rescue_exception.rb +41 -0
- data/lib/wool/warnings/semicolon.rb +24 -0
- data/lib/wool/warnings/useless_double_quotes.rb +37 -0
- data/spec/advice_specs/advice_spec.rb +69 -0
- data/spec/advice_specs/comment_advice_spec.rb +38 -0
- data/spec/advice_specs/spec_helper.rb +1 -0
- data/spec/analysis_specs/annotations_specs/next_prev_annotation_spec.rb +47 -0
- data/spec/analysis_specs/annotations_specs/parent_annotation_spec.rb +41 -0
- data/spec/analysis_specs/annotations_specs/spec_helper.rb +5 -0
- data/spec/analysis_specs/lexical_analysis_spec.rb +179 -0
- data/spec/analysis_specs/protocol_registry_spec.rb +58 -0
- data/spec/analysis_specs/protocols_spec.rb +49 -0
- data/spec/analysis_specs/scope_spec.rb +20 -0
- data/spec/analysis_specs/sexp_analysis_spec.rb +134 -0
- data/spec/analysis_specs/spec_helper.rb +2 -0
- data/spec/analysis_specs/visitor_spec.rb +53 -0
- data/spec/analysis_specs/wool_class_spec.rb +54 -0
- data/spec/rake_specs/spec_helper.rb +1 -0
- data/spec/rake_specs/task_spec.rb +67 -0
- data/spec/runner_spec.rb +171 -0
- data/spec/scanner_spec.rb +75 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +93 -0
- data/spec/support_specs/module_extensions_spec.rb +91 -0
- data/spec/support_specs/spec_helper.rb +1 -0
- data/spec/warning_spec.rb +95 -0
- data/spec/warning_specs/comment_spacing_spec.rb +57 -0
- data/spec/warning_specs/extra_blank_lines_spec.rb +70 -0
- data/spec/warning_specs/extra_whitespace_spec.rb +33 -0
- data/spec/warning_specs/line_length_spec.rb +165 -0
- data/spec/warning_specs/misaligned_unindentation_spec.rb +35 -0
- data/spec/warning_specs/operator_spacing_spec.rb +101 -0
- data/spec/warning_specs/rescue_exception_spec.rb +105 -0
- data/spec/warning_specs/semicolon_spec.rb +58 -0
- data/spec/warning_specs/spec_helper.rb +1 -0
- data/spec/warning_specs/useless_double_quotes_spec.rb +62 -0
- data/spec/wool_spec.rb +8 -0
- data/status_reports/2010/12/2010-12-14.md +163 -0
- data/test/third_party_tests/test_trollop.rb +1181 -0
- data/wool.gemspec +173 -0
- metadata +235 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe RescueExceptionWarning do
|
4
|
+
it 'is a file-based warning' do
|
5
|
+
RescueExceptionWarning.new('(stdin)', 'hello').should be_a(FileWarning)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'matches against a rescue of Exception as the only type' do
|
9
|
+
RescueExceptionWarning.should warn('begin; puts x; rescue Exception; end')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'does not match when a rescue of StandardError is used' do
|
13
|
+
RescueExceptionWarning.should_not warn('begin; puts x; rescue StandardError; end')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'matches against a rescue of Exception with an additional identifier' do
|
17
|
+
RescueExceptionWarning.should warn('begin; puts x; rescue Exception => e; end')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does not match when a rescue of StandardError is used with an additional identifier' do
|
21
|
+
RescueExceptionWarning.should_not warn('begin; puts x; rescue StandardError => e; end')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'matches against a rescue of Exception that is specified second' do
|
25
|
+
RescueExceptionWarning.should warn('begin; puts x; rescue StandardError, Exception; end')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not match when a rescue of StandardError is specified second' do
|
29
|
+
RescueExceptionWarning.should_not warn('begin; puts x; rescue StandardError, StandardError; end')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'matches against a rescue of Exception that is specified second w/ addl identifier' do
|
33
|
+
RescueExceptionWarning.should warn('begin; puts x; rescue StandardError, Exception => err; end')
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'does not match when a rescue of StandardError is specified second w/ addl identifier' do
|
37
|
+
RescueExceptionWarning.should_not warn('begin; puts x; rescue StandardError, StandardError => err; end')
|
38
|
+
end
|
39
|
+
|
40
|
+
context '#fix' do
|
41
|
+
it 'fixes a rescue of Exception as the only type' do
|
42
|
+
input = <<-EOF
|
43
|
+
begin
|
44
|
+
puts x
|
45
|
+
rescue Exception # bad
|
46
|
+
end
|
47
|
+
EOF
|
48
|
+
output = <<-EOF
|
49
|
+
begin
|
50
|
+
puts x
|
51
|
+
rescue StandardError # bad
|
52
|
+
end
|
53
|
+
EOF
|
54
|
+
RescueExceptionWarning.new('(stdin)', input).match?(input).first.fix.should == output
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'fixes a rescue of Exception with an additional identifier' do
|
58
|
+
input = <<-EOF
|
59
|
+
begin
|
60
|
+
puts x
|
61
|
+
rescue Exception => e # bad
|
62
|
+
end
|
63
|
+
EOF
|
64
|
+
output = <<-EOF
|
65
|
+
begin
|
66
|
+
puts x
|
67
|
+
rescue StandardError => e # bad
|
68
|
+
end
|
69
|
+
EOF
|
70
|
+
RescueExceptionWarning.new('(stdin)', input).match?(input).first.fix.should == output
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'fixes a rescue of Exception that is specified second' do
|
74
|
+
input = <<-EOF
|
75
|
+
begin
|
76
|
+
puts x
|
77
|
+
rescue StandardError, Exception # bad
|
78
|
+
end
|
79
|
+
EOF
|
80
|
+
output = <<-EOF
|
81
|
+
begin
|
82
|
+
puts x
|
83
|
+
rescue StandardError, StandardError # bad
|
84
|
+
end
|
85
|
+
EOF
|
86
|
+
RescueExceptionWarning.new('(stdin)', input).match?(input).first.fix.should == output
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'fixes a rescue of Exception that is specified second w/ addl identifier' do
|
90
|
+
input = <<-EOF
|
91
|
+
begin
|
92
|
+
puts x
|
93
|
+
rescue StandardError, Exception => e # bad
|
94
|
+
end
|
95
|
+
EOF
|
96
|
+
output = <<-EOF
|
97
|
+
begin
|
98
|
+
puts x
|
99
|
+
rescue StandardError, StandardError => e # bad
|
100
|
+
end
|
101
|
+
EOF
|
102
|
+
RescueExceptionWarning.new('(stdin)', input).match?(input).first.fix.should == output
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe SemicolonWarning do
|
4
|
+
it 'is a line-based warning' do
|
5
|
+
SemicolonWarning.new('(stdin)', 'hello').should be_a(LineWarning)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'matches when a semicolon splits two expressions' do
|
9
|
+
SemicolonWarning.should warn('puts x; puts y')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'matches when a semicolon splits two expressions that have strings' do
|
13
|
+
SemicolonWarning.should warn('puts "x"; puts "y"')
|
14
|
+
end
|
15
|
+
|
16
|
+
it "doesn't match when a semicolon is in a string" do
|
17
|
+
SemicolonWarning.should_not warn('puts "x;y"')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "doesn't match when a semicolon is in a single-quoted string" do
|
21
|
+
SemicolonWarning.should_not warn("puts 'x;y'")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "doesn't match when a semicolon is used in an Exception definition" do
|
25
|
+
SemicolonWarning.should_not warn('class AError < BError; end"')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has a lower severity when quotes are involved due to unsure-ness' do
|
29
|
+
SemicolonWarning.new('(stdin)', "hello' world' ; there").severity.should <
|
30
|
+
SemicolonWarning.new('(stdin)', 'hello world ; there').severity
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has a remotely descriptive description' do
|
34
|
+
SemicolonWarning.new('(stdin)', 'hello ; world').desc.should =~ /semicolon/
|
35
|
+
end
|
36
|
+
|
37
|
+
it "doesn't match when a semicolon is in a comment" do
|
38
|
+
SemicolonWarning.should_not warn("hello # indeed; i agree")
|
39
|
+
end
|
40
|
+
|
41
|
+
context '#fix' do
|
42
|
+
it 'converts the simplest semicolon use to two lines' do
|
43
|
+
SemicolonWarning.should correct_to('a;b', "a\nb")
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'converts the simplest triple semicolon use to two lines' do
|
47
|
+
SemicolonWarning.should correct_to('a;b;c', "a\nb\nc")
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'maintains indentation on new lines' do
|
51
|
+
SemicolonWarning.should correct_to(' a;b', " a\n b")
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'maintains indentation on all new lines' do
|
55
|
+
SemicolonWarning.should correct_to(' a;b;c', " a\n b\n c")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe UselessDoubleQuotesWarning do
|
4
|
+
it 'is a file-based warning' do
|
5
|
+
UselessDoubleQuotesWarning.new('(stdin)', 'hello').should be_a(FileWarning)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'matches when a simple string is in double quotes unnecessarily' do
|
9
|
+
UselessDoubleQuotesWarning.should warn('simple "example, okay?"')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'matches when a simple string is in %Q{} unnecessarily' do
|
13
|
+
UselessDoubleQuotesWarning.should warn('simple %Q{example, okay?}')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'does not match when an escape sequence is used' do
|
17
|
+
UselessDoubleQuotesWarning.should_not warn('simple "example\n okay?"')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does not match when an apostrophe is present' do
|
21
|
+
UselessDoubleQuotesWarning.should_not warn('simple "example\' okay?"')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'does not match when text interpolation is used' do
|
25
|
+
UselessDoubleQuotesWarning.should_not warn('simple "exaple\n #{h stuff} okay?"')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does match when a useless double-quoted string is used inside text interpolation' do
|
29
|
+
UselessDoubleQuotesWarning.should warn('simple "example, #{h "guy"} okay?"')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not warn about single quotes that are nice and simple' do
|
33
|
+
UselessDoubleQuotesWarning.should_not warn("simple 'string is okay'")
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'does not warn about %q syntax that are simple' do
|
37
|
+
UselessDoubleQuotesWarning.should_not warn("simple %q{string is okay}")
|
38
|
+
end
|
39
|
+
|
40
|
+
context '#fix' do
|
41
|
+
it 'fixes a simple string using double quotes unnecessarily' do
|
42
|
+
checker = UselessDoubleQuotesWarning.new('(stdin)', 'simple "example, okay?"')
|
43
|
+
warnings = checker.match?
|
44
|
+
warnings.size.should == 1
|
45
|
+
warnings.first.fix('simple "example, okay?"').should == "simple 'example, okay?'"
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'fixes a simple string using %Q{} unnecessarily' do
|
49
|
+
checker = UselessDoubleQuotesWarning.new('(stdin)', 'simple %Q{example, okay?}')
|
50
|
+
warnings = checker.match?
|
51
|
+
warnings.size.should == 1
|
52
|
+
warnings.first.fix('simple %Q{example, okay?}').should == "simple %q{example, okay?}"
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'fixes a simple string inside a complex one' do
|
56
|
+
checker = UselessDoubleQuotesWarning.new('(stdin)', 'simple "example, #{h "guy"} okay?"')
|
57
|
+
warnings = checker.match?
|
58
|
+
warnings.size.should == 1
|
59
|
+
warnings.first.fix('simple "example, #{h "guy"} okay?"').should == 'simple "example, #{h \'guy\'} okay?"'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/spec/wool_spec.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
Today, I've pushed significant work on Wool. It's still very rough around the edges, but I've got it doing the following new, cool, and somewhat-advanced analyses:
|
2
|
+
|
3
|
+
## Using a semicolon to separate statements
|
4
|
+
|
5
|
+
If you'd like to warn against using a semicolon to separate statements on a single line, you can turn this warning on. However, due to personal preference, if you do this:
|
6
|
+
|
7
|
+
class MyError < StandardError; end
|
8
|
+
|
9
|
+
it won't complain. This, though:
|
10
|
+
|
11
|
+
puts x; puts y
|
12
|
+
|
13
|
+
will be an error. Wool is capable of fixing this error while maintaining indentation levels, up to arbitrary number of violating semicolons:
|
14
|
+
|
15
|
+
# some comment
|
16
|
+
p x; p y; p z; p 10
|
17
|
+
|
18
|
+
becomes
|
19
|
+
|
20
|
+
# some comment
|
21
|
+
p x
|
22
|
+
p y
|
23
|
+
p z
|
24
|
+
p 10
|
25
|
+
|
26
|
+
Cool, eh? I think I have this one nailed down to 100% accuracy, too.
|
27
|
+
|
28
|
+
## Whitespace Tomfoolery
|
29
|
+
|
30
|
+
Two separate ones here, extra blank lines and trailing whitespace. Both of these are significant mainly because when you violate them, you hurt signal-to-noise in VCS diffs. Committing 2 rows deleted and added with just trailing whitespace differences is just a shame.
|
31
|
+
|
32
|
+
### Extra Blank Lines
|
33
|
+
|
34
|
+
Ever leave blank lines at the end of your file? This warning tells you how many lines you left at the end, and fixes them if you wish.
|
35
|
+
|
36
|
+
### Trailing Whitespace
|
37
|
+
|
38
|
+
Extra spaces and tabs at the end of a line fall in this category, and can be fixed without disturbing the source code.
|
39
|
+
|
40
|
+
## Operator Spacing
|
41
|
+
|
42
|
+
This one is a bit dicy, but it tries to enforce spacing between operators. This gets tough because of lines with block declarations (where `|`s form the variable declaration area in a block, and do not act as a binary or operator), array splatting, block argument declarations, and so on. That's mainly a problem if you use regexes, which the current implementation does use. The parser will be employed shortly to remedy this situation.
|
43
|
+
|
44
|
+
## Misaligned Indentation
|
45
|
+
|
46
|
+
This is another one that needs a bit of an overhaul, but it works pretty well so far. This warning checks how well you indent, namely, that you indent at consistent multiples, and that you unindent evenly. However, it doesn't yet take into account line continuation, where you should then indent relevant to the previous line (and not via the multiplication pattern). So it can detect and correct these mistakes:
|
47
|
+
|
48
|
+
if x
|
49
|
+
if y
|
50
|
+
if z
|
51
|
+
p x
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
becoming
|
57
|
+
|
58
|
+
if x
|
59
|
+
if y
|
60
|
+
if z
|
61
|
+
p x
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Cool eh? It does make some mistakes though, and there are definite examples in the wool code itself that this will mess up on.
|
67
|
+
|
68
|
+
## Line Length
|
69
|
+
|
70
|
+
Line Length is a fun one!
|
71
|
+
|
72
|
+
The line length warnings let you specify multiple levels of severity for different violations, such as a level 2 warning for being between 80-89 chars and a 4 for 90+. Detecting this is extremely simple. Also, it considers whitespace stripping when calculating a line's minimum possible length.
|
73
|
+
|
74
|
+
The really fun part is *fixing* it. Because Ruby code is sensitive to newlines, we can't just break it up into neat 80-char lines on the whitespace. So there's a few quick heuristics currently in play that I think are significant to brag about.
|
75
|
+
|
76
|
+
### Comment Rearranging
|
77
|
+
|
78
|
+
If a line is over the limit, but it has a comment at the end of it, we know we can take the comment and put it on the line above. It really belongs there anyway if it's going off the edge – the line it's describing is long enough to go near the edge, so it's probably quite complex. Its comment should have its own line.
|
79
|
+
|
80
|
+
This heuristic however could create a *new* line that's over the limit, if the comment is comically long. So we also break the comment into correctly-sized lines, all indented to match the original line.
|
81
|
+
|
82
|
+
An example, if the max line length is 20:
|
83
|
+
|
84
|
+
puts x * y * z / a # this computes a significant equation involving math
|
85
|
+
|
86
|
+
you'll get back:
|
87
|
+
|
88
|
+
# this computes a
|
89
|
+
# significant
|
90
|
+
# equation involving
|
91
|
+
# math
|
92
|
+
# puts x * y * z / a
|
93
|
+
|
94
|
+
which still has no line length violations! It naturally smooths out at longer line lengths.
|
95
|
+
|
96
|
+
### Guard rearranging
|
97
|
+
|
98
|
+
Here's one that bugs me. When I write a conditional Exception raise, I think of it this way:
|
99
|
+
|
100
|
+
raise FooBarError.new('Steve forgot to write the Foo bar library') unless steve.finished?
|
101
|
+
|
102
|
+
but that's usually way over the line length limit. In truth, I should check it in like this:
|
103
|
+
|
104
|
+
unless steve.finished?
|
105
|
+
raise FooBarError.new('Steve forgot to write the Foo bar library')
|
106
|
+
end
|
107
|
+
|
108
|
+
And if you use Wool now, you can leave code in the first form and it will transform to the second form! In fact, it works for multiple guards, too:
|
109
|
+
|
110
|
+
raise FooBarError.new('Steve forgot to write the Foo bar library') unless steve.finished?(schedule) if foo_bar.missing?(search_results) unless working?(test_data, &test_proc)
|
111
|
+
|
112
|
+
wool magically transforms into:
|
113
|
+
|
114
|
+
unless working?(test_data, &test_proc)
|
115
|
+
if foo_bar.missing?(search_results)
|
116
|
+
unless steve.finished?(schedule)
|
117
|
+
raise FooBarError.new('Steve forgot to write the Foo bar library')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
Which is clean and better. It actually picks up on-the-line cases I missed on a somewhat regular basis!
|
123
|
+
|
124
|
+
That's all for line length shortening. I know there's a lot more to do with continued lines (ending a line with a `+` token before the line-length cutoff, for example, means you know you can break the line into a new one at that point, resulting in a smaller problem and 1 valid line. Repeating this with all the safe tokens should result in a *lot* of progress.
|
125
|
+
|
126
|
+
## Inline comment spacing
|
127
|
+
|
128
|
+
My style guide (derived mainly from the Google style guide, with a few exceptions that everyone takes because it's a wee bit off) says that inline comments must have exactly 2 spaces between the last non-whitespace character and the hash mark beginning the comment. This is easily detected, enforced, and corrected. So Wool does!
|
129
|
+
|
130
|
+
## Unnecessary Double Quotes
|
131
|
+
|
132
|
+
When you use double quotes, the parser has to go and check for string interpolation and escape sequences. This is also true when you use the `%Q{}` syntax. Thus it is slower to use double quotes or `%Q{}` when you don't need those features. Wool detects this and corrects double-quotes to single quotes (unless there is an escape sequence in the text or an apostrophe) and `%Q{}` to `%q{}`. It uses the raw AST, so it isn't error-prone like the regex-based passes.
|
133
|
+
|
134
|
+
## `rescue Exception`
|
135
|
+
|
136
|
+
is bad! If you're rescuing Exception, you're rescuing a *lot* of things. You're covering up syntax errors. Just about the only thing you don't rescue is `SystemExit`. Almost always, you should be `rescue`ing `StandardError`. Wool currently can detect this based on the AST in all cases, but it cannot auto-correct them yet.
|
137
|
+
|
138
|
+
# Up Next: Immediate
|
139
|
+
|
140
|
+
Next, I'm going to start defining the API for constant-ness, with a tag on the AST that I'll make a pass to infer. I'll also be adding
|
141
|
+
|
142
|
+
default_attr_accessor :pure, false
|
143
|
+
|
144
|
+
to Method and Proc, so methods can be annotated as Pure. Thus, a method call on a constant object with constant arguments (if any) and a constant proc for a block (because it contains only pure calls...) is itself a constant expression. All these rules can be enumerated and inferred, and once the standard library batch of annotations is underway a lot of cool examples of success are going to come out of this. For example, [Rubinius](http://rubini.us) could use our constant-ness evaluator to (with a flag of course, not by default) allow constant assignment in methods to constant expressions:
|
145
|
+
|
146
|
+
def enable_global_settings
|
147
|
+
LOGGER = DEBUG_MODE ? StdoutLogger.new : nil
|
148
|
+
end
|
149
|
+
|
150
|
+
# Down The Line
|
151
|
+
|
152
|
+
Here's a quick brain dump of down-the-line ideas:
|
153
|
+
|
154
|
+
* Next large goal: Load in class and method defs in the main, simple cases, and hammer out an annotation system and a LALR(1) parser for it. Use racc to implement parser.
|
155
|
+
* I could discuss getting the annotations contributed to RubySpec. That's a bit boastful but hey, if they're accurate, they could be helpful.
|
156
|
+
* I'll be starting with the assumption that type annotations are available. That is because, in the spirit of [Gilad's pluggable types](http://bracha.org/pluggableTypesPosition.pdf) inference can be provided as tooling, but is not necessary to develop the rich type system you wish to describe.
|
157
|
+
* Rubinius could use an annotated method to improve code paths.
|
158
|
+
* I could dump the basic flow data I collect, including types of blocks and return types, variables, etc. to a database or YAML or something. I'll be getting a lot. That YAML could be integrated into an editor to provide hover-over type information. Smart autocomplete could be within reach, not that RubyMine is that bad.
|
159
|
+
* A few more syntax things should get done, and I need to overhaul how tokens come out of the lexer process, as right now I'm just handling them raw, which makes for horrible-to-maintain code.
|
160
|
+
* Coverage is near-100% in my eyes. The specs are just wonderful. There are a couple known bugs and those are in my sights.
|
161
|
+
* I tried integrating with Redcar to get an automatic "fix unnecessary double quotes" plugin going, but I ran into huge trouble. Wool needs Ruby 1.9.2, and Redcar uses JRuby, but for some reason when I ran redcar with `rvm use 1.9.2-head`, it blows up on startup. If I run redcar with `rvm use system`, it runs just as dandily as always, but now Wool won't run because it needs 1.9.2. Probably some loader magic to do to find a 1.9 ruby executable, potentially having to interact with `rvm` to infer it.
|
162
|
+
|
163
|
+
Stay tuned, as always, for more.
|
@@ -0,0 +1,1181 @@
|
|
1
|
+
## test/test_trollop.rb -- unit tests for trollop
|
2
|
+
## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net)
|
3
|
+
## Copyright:: Copyright 2007 William Morgan
|
4
|
+
## License:: GNU GPL version 2
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'stringio'
|
8
|
+
require 'wool/third_party/trollop'
|
9
|
+
|
10
|
+
module Trollop
|
11
|
+
module Test
|
12
|
+
|
13
|
+
class Trollop < ::Test::Unit::TestCase
|
14
|
+
def setup
|
15
|
+
@p = Parser.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_die_without_options_ever_run
|
19
|
+
::Trollop.send(:instance_variable_set, "@last_parser", nil)
|
20
|
+
assert_raise(ArgumentError) { ::Trollop.die 'hello' }
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_unknown_arguments
|
24
|
+
assert_raise(CommandlineError) { @p.parse(%w(--arg)) }
|
25
|
+
@p.opt "arg"
|
26
|
+
assert_nothing_raised { @p.parse(%w(--arg)) }
|
27
|
+
assert_raise(CommandlineError) { @p.parse(%w(--arg2)) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_syntax_check
|
31
|
+
@p.opt "arg"
|
32
|
+
|
33
|
+
assert_nothing_raised { @p.parse(%w(--arg)) }
|
34
|
+
assert_nothing_raised { @p.parse(%w(arg)) }
|
35
|
+
assert_raise(CommandlineError) { @p.parse(%w(---arg)) }
|
36
|
+
assert_raise(CommandlineError) { @p.parse(%w(-arg)) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_required_flags_are_required
|
40
|
+
@p.opt "arg", "desc", :required => true
|
41
|
+
@p.opt "arg2", "desc", :required => false
|
42
|
+
@p.opt "arg3", "desc", :required => false
|
43
|
+
|
44
|
+
assert_nothing_raised { @p.parse(%w(--arg)) }
|
45
|
+
assert_nothing_raised { @p.parse(%w(--arg --arg2)) }
|
46
|
+
assert_raise(CommandlineError) { @p.parse(%w(--arg2)) }
|
47
|
+
assert_raise(CommandlineError) { @p.parse(%w(--arg2 --arg3)) }
|
48
|
+
end
|
49
|
+
|
50
|
+
## flags that take an argument error unless given one
|
51
|
+
def test_argflags_demand_args
|
52
|
+
@p.opt "goodarg", "desc", :type => String
|
53
|
+
@p.opt "goodarg2", "desc", :type => String
|
54
|
+
|
55
|
+
assert_nothing_raised { @p.parse(%w(--goodarg goat)) }
|
56
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg --goodarg2 goat)) }
|
57
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg)) }
|
58
|
+
end
|
59
|
+
|
60
|
+
## flags that don't take arguments ignore them
|
61
|
+
def test_arglessflags_refuse_args
|
62
|
+
@p.opt "goodarg"
|
63
|
+
@p.opt "goodarg2"
|
64
|
+
assert_nothing_raised { @p.parse(%w(--goodarg)) }
|
65
|
+
assert_nothing_raised { @p.parse(%w(--goodarg --goodarg2)) }
|
66
|
+
opts = @p.parse %w(--goodarg a)
|
67
|
+
assert_equal true, opts["goodarg"]
|
68
|
+
assert_equal ["a"], @p.leftovers
|
69
|
+
end
|
70
|
+
|
71
|
+
## flags that require args of a specific type refuse args of other
|
72
|
+
## types
|
73
|
+
def test_typed_args_refuse_args_of_other_types
|
74
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :type => :int }
|
75
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :type => :asdf }
|
76
|
+
|
77
|
+
assert_nothing_raised { @p.parse(%w(--goodarg 3)) }
|
78
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg 4.2)) }
|
79
|
+
assert_raise(CommandlineError) { @p.parse(%w(--goodarg hello)) }
|
80
|
+
end
|
81
|
+
|
82
|
+
## type is correctly derived from :default
|
83
|
+
def test_type_correctly_derived_from_default
|
84
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :default => [] }
|
85
|
+
assert_raise(ArgumentError) { @p.opt "badarg3", "desc", :default => [{1 => 2}] }
|
86
|
+
assert_raise(ArgumentError) { @p.opt "badarg4", "desc", :default => Hash.new }
|
87
|
+
|
88
|
+
opts = nil
|
89
|
+
|
90
|
+
# single arg: int
|
91
|
+
assert_nothing_raised { @p.opt "argsi", "desc", :default => 0 }
|
92
|
+
assert_nothing_raised { opts = @p.parse(%w(--)) }
|
93
|
+
assert_equal 0, opts["argsi"]
|
94
|
+
assert_nothing_raised { opts = @p.parse(%w(--argsi 4)) }
|
95
|
+
assert_equal 4, opts["argsi"]
|
96
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argsi 4.2)) }
|
97
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argsi hello)) }
|
98
|
+
|
99
|
+
# single arg: float
|
100
|
+
assert_nothing_raised { @p.opt "argsf", "desc", :default => 3.14 }
|
101
|
+
assert_nothing_raised { opts = @p.parse(%w(--)) }
|
102
|
+
assert_equal 3.14, opts["argsf"]
|
103
|
+
assert_nothing_raised { opts = @p.parse(%w(--argsf 2.41)) }
|
104
|
+
assert_equal 2.41, opts["argsf"]
|
105
|
+
assert_nothing_raised { opts = @p.parse(%w(--argsf 2)) }
|
106
|
+
assert_equal 2, opts["argsf"]
|
107
|
+
assert_nothing_raised { opts = @p.parse(%w(--argsf 1.0e-2)) }
|
108
|
+
assert_equal 1.0e-2, opts["argsf"]
|
109
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argsf hello)) }
|
110
|
+
|
111
|
+
# single arg: date
|
112
|
+
date = Date.today
|
113
|
+
assert_nothing_raised { @p.opt "argsd", "desc", :default => date }
|
114
|
+
assert_nothing_raised { opts = @p.parse(%w(--)) }
|
115
|
+
assert_equal Date.today, opts["argsd"]
|
116
|
+
assert_nothing_raised { opts = @p.parse(['--argsd', 'Jan 4, 2007']) }
|
117
|
+
assert_equal Date.civil(2007, 1, 4), opts["argsd"]
|
118
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argsd hello)) }
|
119
|
+
|
120
|
+
# single arg: string
|
121
|
+
assert_nothing_raised { @p.opt "argss", "desc", :default => "foobar" }
|
122
|
+
assert_nothing_raised { opts = @p.parse(%w(--)) }
|
123
|
+
assert_equal "foobar", opts["argss"]
|
124
|
+
assert_nothing_raised { opts = @p.parse(%w(--argss 2.41)) }
|
125
|
+
assert_equal "2.41", opts["argss"]
|
126
|
+
assert_nothing_raised { opts = @p.parse(%w(--argss hello)) }
|
127
|
+
assert_equal "hello", opts["argss"]
|
128
|
+
|
129
|
+
# multi args: ints
|
130
|
+
assert_nothing_raised { @p.opt "argmi", "desc", :default => [3, 5] }
|
131
|
+
assert_nothing_raised { opts = @p.parse(%w(--)) }
|
132
|
+
assert_equal [3, 5], opts["argmi"]
|
133
|
+
assert_nothing_raised { opts = @p.parse(%w(--argmi 4)) }
|
134
|
+
assert_equal [4], opts["argmi"]
|
135
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argmi 4.2)) }
|
136
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argmi hello)) }
|
137
|
+
|
138
|
+
# multi args: floats
|
139
|
+
assert_nothing_raised { @p.opt "argmf", "desc", :default => [3.34, 5.21] }
|
140
|
+
assert_nothing_raised { opts = @p.parse(%w(--)) }
|
141
|
+
assert_equal [3.34, 5.21], opts["argmf"]
|
142
|
+
assert_nothing_raised { opts = @p.parse(%w(--argmf 2)) }
|
143
|
+
assert_equal [2], opts["argmf"]
|
144
|
+
assert_nothing_raised { opts = @p.parse(%w(--argmf 4.0)) }
|
145
|
+
assert_equal [4.0], opts["argmf"]
|
146
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argmf hello)) }
|
147
|
+
|
148
|
+
# multi args: dates
|
149
|
+
dates = [Date.today, Date.civil(2007, 1, 4)]
|
150
|
+
assert_nothing_raised { @p.opt "argmd", "desc", :default => dates }
|
151
|
+
assert_nothing_raised { opts = @p.parse(%w(--)) }
|
152
|
+
assert_equal dates, opts["argmd"]
|
153
|
+
assert_nothing_raised { opts = @p.parse(['--argmd', 'Jan 4, 2007']) }
|
154
|
+
assert_equal [Date.civil(2007, 1, 4)], opts["argmd"]
|
155
|
+
assert_raise(CommandlineError) { @p.parse(%w(--argmd hello)) }
|
156
|
+
|
157
|
+
# multi args: strings
|
158
|
+
assert_nothing_raised { @p.opt "argmst", "desc", :default => %w(hello world) }
|
159
|
+
assert_nothing_raised { opts = @p.parse(%w(--)) }
|
160
|
+
assert_equal %w(hello world), opts["argmst"]
|
161
|
+
assert_nothing_raised { opts = @p.parse(%w(--argmst 3.4)) }
|
162
|
+
assert_equal ["3.4"], opts["argmst"]
|
163
|
+
assert_nothing_raised { opts = @p.parse(%w(--argmst goodbye)) }
|
164
|
+
assert_equal ["goodbye"], opts["argmst"]
|
165
|
+
end
|
166
|
+
|
167
|
+
## :type and :default must match if both are specified
|
168
|
+
def test_type_and_default_must_match
|
169
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :type => :int, :default => "hello" }
|
170
|
+
assert_raise(ArgumentError) { @p.opt "badarg2", "desc", :type => :String, :default => 4 }
|
171
|
+
assert_raise(ArgumentError) { @p.opt "badarg2", "desc", :type => :String, :default => ["hi"] }
|
172
|
+
assert_raise(ArgumentError) { @p.opt "badarg2", "desc", :type => :ints, :default => [3.14] }
|
173
|
+
|
174
|
+
assert_nothing_raised { @p.opt "argsi", "desc", :type => :int, :default => 4 }
|
175
|
+
assert_nothing_raised { @p.opt "argsf", "desc", :type => :float, :default => 3.14 }
|
176
|
+
assert_nothing_raised { @p.opt "argsd", "desc", :type => :date, :default => Date.today }
|
177
|
+
assert_nothing_raised { @p.opt "argss", "desc", :type => :string, :default => "yo" }
|
178
|
+
assert_nothing_raised { @p.opt "argmi", "desc", :type => :ints, :default => [4] }
|
179
|
+
assert_nothing_raised { @p.opt "argmf", "desc", :type => :floats, :default => [3.14] }
|
180
|
+
assert_nothing_raised { @p.opt "argmd", "desc", :type => :dates, :default => [Date.today] }
|
181
|
+
assert_nothing_raised { @p.opt "argmst", "desc", :type => :strings, :default => ["yo"] }
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_long_detects_bad_names
|
185
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :long => "none" }
|
186
|
+
assert_nothing_raised { @p.opt "goodarg2", "desc", :long => "--two" }
|
187
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :long => "" }
|
188
|
+
assert_raise(ArgumentError) { @p.opt "badarg2", "desc", :long => "--" }
|
189
|
+
assert_raise(ArgumentError) { @p.opt "badarg3", "desc", :long => "-one" }
|
190
|
+
assert_raise(ArgumentError) { @p.opt "badarg4", "desc", :long => "---toomany" }
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_short_detects_bad_names
|
194
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :short => "a" }
|
195
|
+
assert_nothing_raised { @p.opt "goodarg2", "desc", :short => "-b" }
|
196
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :short => "" }
|
197
|
+
assert_raise(ArgumentError) { @p.opt "badarg2", "desc", :short => "-ab" }
|
198
|
+
assert_raise(ArgumentError) { @p.opt "badarg3", "desc", :short => "--t" }
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_short_names_created_automatically
|
202
|
+
@p.opt "arg"
|
203
|
+
@p.opt "arg2"
|
204
|
+
@p.opt "arg3"
|
205
|
+
opts = @p.parse %w(-a -g)
|
206
|
+
assert_equal true, opts["arg"]
|
207
|
+
assert_equal false, opts["arg2"]
|
208
|
+
assert_equal true, opts["arg3"]
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_short_autocreation_skips_dashes_and_numbers
|
212
|
+
@p.opt :arg # auto: a
|
213
|
+
@p.opt :arg_potato # auto: r
|
214
|
+
@p.opt :arg_muffin # auto: g
|
215
|
+
assert_nothing_raised { @p.opt :arg_daisy } # auto: d (not _)!
|
216
|
+
assert_nothing_raised { @p.opt :arg_r2d2f } # auto: f (not 2)!
|
217
|
+
|
218
|
+
opts = @p.parse %w(-f -d)
|
219
|
+
assert_equal true, opts[:arg_daisy]
|
220
|
+
assert_equal true, opts[:arg_r2d2f]
|
221
|
+
assert_equal false, opts[:arg]
|
222
|
+
assert_equal false, opts[:arg_potato]
|
223
|
+
assert_equal false, opts[:arg_muffin]
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_short_autocreation_is_ok_with_running_out_of_chars
|
227
|
+
@p.opt :arg1 # auto: a
|
228
|
+
@p.opt :arg2 # auto: r
|
229
|
+
@p.opt :arg3 # auto: g
|
230
|
+
@p.opt :arg4 # auto: uh oh!
|
231
|
+
assert_nothing_raised { @p.parse [] }
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_short_can_be_nothing
|
235
|
+
assert_nothing_raised do
|
236
|
+
@p.opt "arg", "desc", :short => :none
|
237
|
+
@p.parse []
|
238
|
+
end
|
239
|
+
|
240
|
+
sio = StringIO.new "w"
|
241
|
+
@p.educate sio
|
242
|
+
assert sio.string =~ /--arg:\s+desc/
|
243
|
+
|
244
|
+
assert_raise(CommandlineError) { @p.parse %w(-a) }
|
245
|
+
end
|
246
|
+
|
247
|
+
## two args can't have the same name
|
248
|
+
def test_conflicting_names_are_detected
|
249
|
+
assert_nothing_raised { @p.opt "goodarg" }
|
250
|
+
assert_raise(ArgumentError) { @p.opt "goodarg" }
|
251
|
+
end
|
252
|
+
|
253
|
+
## two args can't have the same :long
|
254
|
+
def test_conflicting_longs_detected
|
255
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :long => "--goodarg" }
|
256
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :long => "--goodarg" }
|
257
|
+
end
|
258
|
+
|
259
|
+
## two args can't have the same :short
|
260
|
+
def test_conflicting_shorts_detected
|
261
|
+
assert_nothing_raised { @p.opt "goodarg", "desc", :short => "-g" }
|
262
|
+
assert_raise(ArgumentError) { @p.opt "badarg", "desc", :short => "-g" }
|
263
|
+
end
|
264
|
+
|
265
|
+
def test_flag_defaults
|
266
|
+
@p.opt "defaultfalse", "desc"
|
267
|
+
@p.opt "defaulttrue", "desc", :default => true
|
268
|
+
opts = @p.parse []
|
269
|
+
assert_equal false, opts["defaultfalse"]
|
270
|
+
assert_equal true, opts["defaulttrue"]
|
271
|
+
|
272
|
+
opts = @p.parse %w(--defaultfalse --defaulttrue)
|
273
|
+
assert_equal true, opts["defaultfalse"]
|
274
|
+
assert_equal false, opts["defaulttrue"]
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_special_flags_work
|
278
|
+
@p.version "asdf fdas"
|
279
|
+
assert_raise(VersionNeeded) { @p.parse(%w(-v)) }
|
280
|
+
assert_raise(HelpNeeded) { @p.parse(%w(-h)) }
|
281
|
+
end
|
282
|
+
|
283
|
+
def test_short_options_combine
|
284
|
+
@p.opt :arg1, "desc", :short => "a"
|
285
|
+
@p.opt :arg2, "desc", :short => "b"
|
286
|
+
@p.opt :arg3, "desc", :short => "c", :type => :int
|
287
|
+
|
288
|
+
opts = nil
|
289
|
+
assert_nothing_raised { opts = @p.parse %w(-a -b) }
|
290
|
+
assert_equal true, opts[:arg1]
|
291
|
+
assert_equal true, opts[:arg2]
|
292
|
+
assert_equal nil, opts[:arg3]
|
293
|
+
|
294
|
+
assert_nothing_raised { opts = @p.parse %w(-ab) }
|
295
|
+
assert_equal true, opts[:arg1]
|
296
|
+
assert_equal true, opts[:arg2]
|
297
|
+
assert_equal nil, opts[:arg3]
|
298
|
+
|
299
|
+
assert_nothing_raised { opts = @p.parse %w(-ac 4 -b) }
|
300
|
+
assert_equal true, opts[:arg1]
|
301
|
+
assert_equal true, opts[:arg2]
|
302
|
+
assert_equal 4, opts[:arg3]
|
303
|
+
|
304
|
+
assert_raises(CommandlineError) { @p.parse %w(-cab 4) }
|
305
|
+
assert_raises(CommandlineError) { @p.parse %w(-cba 4) }
|
306
|
+
end
|
307
|
+
|
308
|
+
def test_version_only_appears_if_set
|
309
|
+
@p.opt "arg"
|
310
|
+
assert_raise(CommandlineError) { @p.parse %w(-v) }
|
311
|
+
@p.version "trollop 1.2.3.4"
|
312
|
+
assert_raise(VersionNeeded) { @p.parse %w(-v) }
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_doubledash_ends_option_processing
|
316
|
+
@p.opt :arg1, "desc", :short => "a", :default => 0
|
317
|
+
@p.opt :arg2, "desc", :short => "b", :default => 0
|
318
|
+
opts = nil
|
319
|
+
assert_nothing_raised { opts = @p.parse %w(-- -a 3 -b 2) }
|
320
|
+
assert_equal opts[:arg1], 0
|
321
|
+
assert_equal opts[:arg2], 0
|
322
|
+
assert_equal %w(-a 3 -b 2), @p.leftovers
|
323
|
+
assert_nothing_raised { opts = @p.parse %w(-a 3 -- -b 2) }
|
324
|
+
assert_equal opts[:arg1], 3
|
325
|
+
assert_equal opts[:arg2], 0
|
326
|
+
assert_equal %w(-b 2), @p.leftovers
|
327
|
+
assert_nothing_raised { opts = @p.parse %w(-a 3 -b 2 --) }
|
328
|
+
assert_equal opts[:arg1], 3
|
329
|
+
assert_equal opts[:arg2], 2
|
330
|
+
assert_equal %w(), @p.leftovers
|
331
|
+
end
|
332
|
+
|
333
|
+
def test_wrap
|
334
|
+
assert_equal [""], @p.wrap("")
|
335
|
+
assert_equal ["a"], @p.wrap("a")
|
336
|
+
assert_equal ["one two", "three"], @p.wrap("one two three", :width => 8)
|
337
|
+
assert_equal ["one two three"], @p.wrap("one two three", :width => 80)
|
338
|
+
assert_equal ["one", "two", "three"], @p.wrap("one two three", :width => 3)
|
339
|
+
assert_equal ["onetwothree"], @p.wrap("onetwothree", :width => 3)
|
340
|
+
assert_equal [
|
341
|
+
"Test is an awesome program that does something very, very important.",
|
342
|
+
"",
|
343
|
+
"Usage:",
|
344
|
+
" test [options] <filenames>+",
|
345
|
+
"where [options] are:"], @p.wrap(<<EOM, :width => 100)
|
346
|
+
Test is an awesome program that does something very, very important.
|
347
|
+
|
348
|
+
Usage:
|
349
|
+
test [options] <filenames>+
|
350
|
+
where [options] are:
|
351
|
+
EOM
|
352
|
+
end
|
353
|
+
|
354
|
+
def test_floating_point_formatting
|
355
|
+
@p.opt :arg, "desc", :type => :float, :short => "f"
|
356
|
+
opts = nil
|
357
|
+
assert_nothing_raised { opts = @p.parse %w(-f 1) }
|
358
|
+
assert_equal 1.0, opts[:arg]
|
359
|
+
assert_nothing_raised { opts = @p.parse %w(-f 1.0) }
|
360
|
+
assert_equal 1.0, opts[:arg]
|
361
|
+
assert_nothing_raised { opts = @p.parse %w(-f 0.1) }
|
362
|
+
assert_equal 0.1, opts[:arg]
|
363
|
+
assert_nothing_raised { opts = @p.parse %w(-f .1) }
|
364
|
+
assert_equal 0.1, opts[:arg]
|
365
|
+
assert_nothing_raised { opts = @p.parse %w(-f .99999999999999999999) }
|
366
|
+
assert_equal 1.0, opts[:arg]
|
367
|
+
assert_nothing_raised { opts = @p.parse %w(-f -1) }
|
368
|
+
assert_equal(-1.0, opts[:arg])
|
369
|
+
assert_nothing_raised { opts = @p.parse %w(-f -1.0) }
|
370
|
+
assert_equal(-1.0, opts[:arg])
|
371
|
+
assert_nothing_raised { opts = @p.parse %w(-f -0.1) }
|
372
|
+
assert_equal(-0.1, opts[:arg])
|
373
|
+
assert_nothing_raised { opts = @p.parse %w(-f -.1) }
|
374
|
+
assert_equal(-0.1, opts[:arg])
|
375
|
+
assert_raises(CommandlineError) { @p.parse %w(-f a) }
|
376
|
+
assert_raises(CommandlineError) { @p.parse %w(-f 1a) }
|
377
|
+
assert_raises(CommandlineError) { @p.parse %w(-f 1.a) }
|
378
|
+
assert_raises(CommandlineError) { @p.parse %w(-f a.1) }
|
379
|
+
assert_raises(CommandlineError) { @p.parse %w(-f 1.0.0) }
|
380
|
+
assert_raises(CommandlineError) { @p.parse %w(-f .) }
|
381
|
+
assert_raises(CommandlineError) { @p.parse %w(-f -.) }
|
382
|
+
end
|
383
|
+
|
384
|
+
def test_date_formatting
|
385
|
+
@p.opt :arg, "desc", :type => :date, :short => 'd'
|
386
|
+
opts = nil
|
387
|
+
assert_nothing_raised { opts = @p.parse(['-d', 'Jan 4, 2007']) }
|
388
|
+
assert_equal Date.civil(2007, 1, 4), opts[:arg]
|
389
|
+
end
|
390
|
+
|
391
|
+
def test_short_options_cant_be_numeric
|
392
|
+
assert_raises(ArgumentError) { @p.opt :arg, "desc", :short => "-1" }
|
393
|
+
@p.opt :a1b, "desc"
|
394
|
+
@p.opt :a2b, "desc"
|
395
|
+
assert_not_equal "2", @p.specs[:a2b][:short]
|
396
|
+
end
|
397
|
+
|
398
|
+
def test_short_options_can_be_weird
|
399
|
+
assert_nothing_raised { @p.opt :arg1, "desc", :short => "#" }
|
400
|
+
assert_nothing_raised { @p.opt :arg2, "desc", :short => "." }
|
401
|
+
assert_raises(ArgumentError) { @p.opt :arg3, "desc", :short => "-" }
|
402
|
+
end
|
403
|
+
|
404
|
+
def test_options_cant_be_set_multiple_times_if_not_specified
|
405
|
+
@p.opt :arg, "desc", :short => "-x"
|
406
|
+
assert_nothing_raised { @p.parse %w(-x) }
|
407
|
+
assert_raises(CommandlineError) { @p.parse %w(-x -x) }
|
408
|
+
assert_raises(CommandlineError) { @p.parse %w(-xx) }
|
409
|
+
end
|
410
|
+
|
411
|
+
def test_options_can_be_set_multiple_times_if_specified
|
412
|
+
assert_nothing_raised do
|
413
|
+
@p.opt :arg, "desc", :short => "-x", :multi => true
|
414
|
+
end
|
415
|
+
assert_nothing_raised { @p.parse %w(-x) }
|
416
|
+
assert_nothing_raised { @p.parse %w(-x -x) }
|
417
|
+
assert_nothing_raised { @p.parse %w(-xx) }
|
418
|
+
end
|
419
|
+
|
420
|
+
def test_short_options_with_multiple_options
|
421
|
+
opts = nil
|
422
|
+
|
423
|
+
assert_nothing_raised do
|
424
|
+
@p.opt :xarg, "desc", :short => "-x", :type => String, :multi => true
|
425
|
+
end
|
426
|
+
assert_nothing_raised { opts = @p.parse %w(-x a -x b) }
|
427
|
+
assert_equal %w(a b), opts[:xarg]
|
428
|
+
assert_equal [], @p.leftovers
|
429
|
+
end
|
430
|
+
|
431
|
+
def short_options_with_multiple_options_does_not_affect_flags_type
|
432
|
+
opts = nil
|
433
|
+
|
434
|
+
assert_nothing_raised do
|
435
|
+
@p.opt :xarg, "desc", :short => "-x", :type => :flag, :multi => true
|
436
|
+
end
|
437
|
+
|
438
|
+
assert_nothing_raised { opts = @p.parse %w(-x a) }
|
439
|
+
assert_equal true, opts[:xarg]
|
440
|
+
assert_equal %w(a), @p.leftovers
|
441
|
+
|
442
|
+
assert_nothing_raised { opts = @p.parse %w(-x a -x b) }
|
443
|
+
assert_equal true, opts[:xarg]
|
444
|
+
assert_equal %w(a b), @p.leftovers
|
445
|
+
|
446
|
+
assert_nothing_raised { opts = @p.parse %w(-xx a -x b) }
|
447
|
+
assert_equal true, opts[:xarg]
|
448
|
+
assert_equal %w(a b), @p.leftovers
|
449
|
+
end
|
450
|
+
|
451
|
+
def test_short_options_with_multiple_arguments
|
452
|
+
opts = nil
|
453
|
+
|
454
|
+
@p.opt :xarg, "desc", :type => :ints
|
455
|
+
assert_nothing_raised { opts = @p.parse %w(-x 3 4 0) }
|
456
|
+
assert_equal [3, 4, 0], opts[:xarg]
|
457
|
+
assert_equal [], @p.leftovers
|
458
|
+
|
459
|
+
@p.opt :yarg, "desc", :type => :floats
|
460
|
+
assert_nothing_raised { opts = @p.parse %w(-y 3.14 4.21 0.66) }
|
461
|
+
assert_equal [3.14, 4.21, 0.66], opts[:yarg]
|
462
|
+
assert_equal [], @p.leftovers
|
463
|
+
|
464
|
+
@p.opt :zarg, "desc", :type => :strings
|
465
|
+
assert_nothing_raised { opts = @p.parse %w(-z a b c) }
|
466
|
+
assert_equal %w(a b c), opts[:zarg]
|
467
|
+
assert_equal [], @p.leftovers
|
468
|
+
end
|
469
|
+
|
470
|
+
def test_short_options_with_multiple_options_and_arguments
|
471
|
+
opts = nil
|
472
|
+
|
473
|
+
@p.opt :xarg, "desc", :type => :ints, :multi => true
|
474
|
+
assert_nothing_raised { opts = @p.parse %w(-x 3 4 5 -x 6 7) }
|
475
|
+
assert_equal [[3, 4, 5], [6, 7]], opts[:xarg]
|
476
|
+
assert_equal [], @p.leftovers
|
477
|
+
|
478
|
+
@p.opt :yarg, "desc", :type => :floats, :multi => true
|
479
|
+
assert_nothing_raised { opts = @p.parse %w(-y 3.14 4.21 5.66 -y 6.99 7.01) }
|
480
|
+
assert_equal [[3.14, 4.21, 5.66], [6.99, 7.01]], opts[:yarg]
|
481
|
+
assert_equal [], @p.leftovers
|
482
|
+
|
483
|
+
@p.opt :zarg, "desc", :type => :strings, :multi => true
|
484
|
+
assert_nothing_raised { opts = @p.parse %w(-z a b c -z d e) }
|
485
|
+
assert_equal [%w(a b c), %w(d e)], opts[:zarg]
|
486
|
+
assert_equal [], @p.leftovers
|
487
|
+
end
|
488
|
+
|
489
|
+
def test_combined_short_options_with_multiple_arguments
|
490
|
+
@p.opt :arg1, "desc", :short => "a"
|
491
|
+
@p.opt :arg2, "desc", :short => "b"
|
492
|
+
@p.opt :arg3, "desc", :short => "c", :type => :ints
|
493
|
+
@p.opt :arg4, "desc", :short => "d", :type => :floats
|
494
|
+
|
495
|
+
opts = nil
|
496
|
+
|
497
|
+
assert_nothing_raised { opts = @p.parse %w(-abc 4 6 9) }
|
498
|
+
assert_equal true, opts[:arg1]
|
499
|
+
assert_equal true, opts[:arg2]
|
500
|
+
assert_equal [4, 6, 9], opts[:arg3]
|
501
|
+
|
502
|
+
assert_nothing_raised { opts = @p.parse %w(-ac 4 6 9 -bd 3.14 2.41) }
|
503
|
+
assert_equal true, opts[:arg1]
|
504
|
+
assert_equal true, opts[:arg2]
|
505
|
+
assert_equal [4, 6, 9], opts[:arg3]
|
506
|
+
assert_equal [3.14, 2.41], opts[:arg4]
|
507
|
+
|
508
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(-abcd 3.14 2.41) }
|
509
|
+
end
|
510
|
+
|
511
|
+
def test_long_options_with_multiple_options
|
512
|
+
@p.opt :xarg, "desc", :type => String, :multi => true
|
513
|
+
opts = nil
|
514
|
+
assert_nothing_raised { opts = @p.parse %w(--xarg=a --xarg=b) }
|
515
|
+
assert_equal %w(a b), opts[:xarg]
|
516
|
+
assert_equal [], @p.leftovers
|
517
|
+
assert_nothing_raised { opts = @p.parse %w(--xarg a --xarg b) }
|
518
|
+
assert_equal %w(a b), opts[:xarg]
|
519
|
+
assert_equal [], @p.leftovers
|
520
|
+
end
|
521
|
+
|
522
|
+
def test_long_options_with_multiple_arguments
|
523
|
+
opts = nil
|
524
|
+
|
525
|
+
@p.opt :xarg, "desc", :type => :ints
|
526
|
+
assert_nothing_raised { opts = @p.parse %w(--xarg 3 2 5) }
|
527
|
+
assert_equal [3, 2, 5], opts[:xarg]
|
528
|
+
assert_equal [], @p.leftovers
|
529
|
+
assert_nothing_raised { opts = @p.parse %w(--xarg=3) }
|
530
|
+
assert_equal [3], opts[:xarg]
|
531
|
+
assert_equal [], @p.leftovers
|
532
|
+
|
533
|
+
@p.opt :yarg, "desc", :type => :floats
|
534
|
+
assert_nothing_raised { opts = @p.parse %w(--yarg 3.14 2.41 5.66) }
|
535
|
+
assert_equal [3.14, 2.41, 5.66], opts[:yarg]
|
536
|
+
assert_equal [], @p.leftovers
|
537
|
+
assert_nothing_raised { opts = @p.parse %w(--yarg=3.14) }
|
538
|
+
assert_equal [3.14], opts[:yarg]
|
539
|
+
assert_equal [], @p.leftovers
|
540
|
+
|
541
|
+
@p.opt :zarg, "desc", :type => :strings
|
542
|
+
assert_nothing_raised { opts = @p.parse %w(--zarg a b c) }
|
543
|
+
assert_equal %w(a b c), opts[:zarg]
|
544
|
+
assert_equal [], @p.leftovers
|
545
|
+
assert_nothing_raised { opts = @p.parse %w(--zarg=a) }
|
546
|
+
assert_equal %w(a), opts[:zarg]
|
547
|
+
assert_equal [], @p.leftovers
|
548
|
+
end
|
549
|
+
|
550
|
+
def test_long_options_with_multiple_options_and_arguments
|
551
|
+
opts = nil
|
552
|
+
|
553
|
+
@p.opt :xarg, "desc", :type => :ints, :multi => true
|
554
|
+
assert_nothing_raised { opts = @p.parse %w(--xarg 3 2 5 --xarg 2 1) }
|
555
|
+
assert_equal [[3, 2, 5], [2, 1]], opts[:xarg]
|
556
|
+
assert_equal [], @p.leftovers
|
557
|
+
assert_nothing_raised { opts = @p.parse %w(--xarg=3 --xarg=2) }
|
558
|
+
assert_equal [[3], [2]], opts[:xarg]
|
559
|
+
assert_equal [], @p.leftovers
|
560
|
+
|
561
|
+
@p.opt :yarg, "desc", :type => :floats, :multi => true
|
562
|
+
assert_nothing_raised { opts = @p.parse %w(--yarg 3.14 2.72 5 --yarg 2.41 1.41) }
|
563
|
+
assert_equal [[3.14, 2.72, 5], [2.41, 1.41]], opts[:yarg]
|
564
|
+
assert_equal [], @p.leftovers
|
565
|
+
assert_nothing_raised { opts = @p.parse %w(--yarg=3.14 --yarg=2.41) }
|
566
|
+
assert_equal [[3.14], [2.41]], opts[:yarg]
|
567
|
+
assert_equal [], @p.leftovers
|
568
|
+
|
569
|
+
@p.opt :zarg, "desc", :type => :strings, :multi => true
|
570
|
+
assert_nothing_raised { opts = @p.parse %w(--zarg a b c --zarg d e) }
|
571
|
+
assert_equal [%w(a b c), %w(d e)], opts[:zarg]
|
572
|
+
assert_equal [], @p.leftovers
|
573
|
+
assert_nothing_raised { opts = @p.parse %w(--zarg=a --zarg=d) }
|
574
|
+
assert_equal [%w(a), %w(d)], opts[:zarg]
|
575
|
+
assert_equal [], @p.leftovers
|
576
|
+
end
|
577
|
+
|
578
|
+
def test_long_options_also_take_equals
|
579
|
+
@p.opt :arg, "desc", :long => "arg", :type => String, :default => "hello"
|
580
|
+
opts = nil
|
581
|
+
assert_nothing_raised { opts = @p.parse %w() }
|
582
|
+
assert_equal "hello", opts[:arg]
|
583
|
+
assert_nothing_raised { opts = @p.parse %w(--arg goat) }
|
584
|
+
assert_equal "goat", opts[:arg]
|
585
|
+
assert_nothing_raised { opts = @p.parse %w(--arg=goat) }
|
586
|
+
assert_equal "goat", opts[:arg]
|
587
|
+
## actually, this next one is valid. empty string for --arg, and goat as a
|
588
|
+
## leftover.
|
589
|
+
## assert_raises(CommandlineError) { opts = @p.parse %w(--arg= goat) }
|
590
|
+
end
|
591
|
+
|
592
|
+
def test_auto_generated_long_names_convert_underscores_to_hyphens
|
593
|
+
@p.opt :hello_there
|
594
|
+
assert_equal "hello-there", @p.specs[:hello_there][:long]
|
595
|
+
end
|
596
|
+
|
597
|
+
def test_arguments_passed_through_block
|
598
|
+
@goat = 3
|
599
|
+
boat = 4
|
600
|
+
Parser.new(@goat) do |goat|
|
601
|
+
boat = goat
|
602
|
+
end
|
603
|
+
assert_equal @goat, boat
|
604
|
+
end
|
605
|
+
|
606
|
+
def test_help_has_default_banner
|
607
|
+
@p = Parser.new
|
608
|
+
sio = StringIO.new "w"
|
609
|
+
@p.parse []
|
610
|
+
@p.educate sio
|
611
|
+
help = sio.string.split "\n"
|
612
|
+
assert help[0] =~ /options/i
|
613
|
+
assert_equal 2, help.length # options, then -h
|
614
|
+
|
615
|
+
@p = Parser.new
|
616
|
+
@p.version "my version"
|
617
|
+
sio = StringIO.new "w"
|
618
|
+
@p.parse []
|
619
|
+
@p.educate sio
|
620
|
+
help = sio.string.split "\n"
|
621
|
+
assert help[0] =~ /my version/i
|
622
|
+
assert_equal 4, help.length # version, options, -h, -v
|
623
|
+
|
624
|
+
@p = Parser.new
|
625
|
+
@p.banner "my own banner"
|
626
|
+
sio = StringIO.new "w"
|
627
|
+
@p.parse []
|
628
|
+
@p.educate sio
|
629
|
+
help = sio.string.split "\n"
|
630
|
+
assert help[0] =~ /my own banner/i
|
631
|
+
assert_equal 2, help.length # banner, -h
|
632
|
+
end
|
633
|
+
|
634
|
+
def test_help_preserves_positions
|
635
|
+
@p.opt :zzz, "zzz"
|
636
|
+
@p.opt :aaa, "aaa"
|
637
|
+
sio = StringIO.new "w"
|
638
|
+
@p.educate sio
|
639
|
+
|
640
|
+
help = sio.string.split "\n"
|
641
|
+
assert help[1] =~ /zzz/
|
642
|
+
assert help[2] =~ /aaa/
|
643
|
+
end
|
644
|
+
|
645
|
+
def test_help_includes_option_types
|
646
|
+
@p.opt :arg1, 'arg', :type => :int
|
647
|
+
@p.opt :arg2, 'arg', :type => :ints
|
648
|
+
@p.opt :arg3, 'arg', :type => :string
|
649
|
+
@p.opt :arg4, 'arg', :type => :strings
|
650
|
+
@p.opt :arg5, 'arg', :type => :float
|
651
|
+
@p.opt :arg6, 'arg', :type => :floats
|
652
|
+
@p.opt :arg7, 'arg', :type => :io
|
653
|
+
@p.opt :arg8, 'arg', :type => :ios
|
654
|
+
@p.opt :arg9, 'arg', :type => :date
|
655
|
+
@p.opt :arg10, 'arg', :type => :dates
|
656
|
+
sio = StringIO.new "w"
|
657
|
+
@p.educate sio
|
658
|
+
|
659
|
+
help = sio.string.split "\n"
|
660
|
+
assert help[1] =~ /<i>/
|
661
|
+
assert help[2] =~ /<i\+>/
|
662
|
+
assert help[3] =~ /<s>/
|
663
|
+
assert help[4] =~ /<s\+>/
|
664
|
+
assert help[5] =~ /<f>/
|
665
|
+
assert help[6] =~ /<f\+>/
|
666
|
+
assert help[7] =~ /<filename\/uri>/
|
667
|
+
assert help[8] =~ /<filename\/uri\+>/
|
668
|
+
assert help[9] =~ /<date>/
|
669
|
+
assert help[10] =~ /<date\+>/
|
670
|
+
end
|
671
|
+
|
672
|
+
def test_help_has_grammatical_default_text
|
673
|
+
@p.opt :arg1, 'description with period.', :default => 'hello'
|
674
|
+
@p.opt :arg2, 'description without period', :default => 'world'
|
675
|
+
sio = StringIO.new 'w'
|
676
|
+
@p.educate sio
|
677
|
+
|
678
|
+
help = sio.string.split "\n"
|
679
|
+
assert help[1] =~ /Default/
|
680
|
+
assert help[2] =~ /default/
|
681
|
+
end
|
682
|
+
|
683
|
+
def test_version_and_help_short_args_can_be_overridden
|
684
|
+
@p.opt :verbose, "desc", :short => "-v"
|
685
|
+
@p.opt :hello, "desc", :short => "-h"
|
686
|
+
@p.version "version"
|
687
|
+
|
688
|
+
assert_nothing_raised { @p.parse(%w(-v)) }
|
689
|
+
assert_raises(VersionNeeded) { @p.parse(%w(--version)) }
|
690
|
+
assert_nothing_raised { @p.parse(%w(-h)) }
|
691
|
+
assert_raises(HelpNeeded) { @p.parse(%w(--help)) }
|
692
|
+
end
|
693
|
+
|
694
|
+
def test_version_and_help_long_args_can_be_overridden
|
695
|
+
@p.opt :asdf, "desc", :long => "help"
|
696
|
+
@p.opt :asdf2, "desc2", :long => "version"
|
697
|
+
assert_nothing_raised { @p.parse %w() }
|
698
|
+
assert_nothing_raised { @p.parse %w(--help) }
|
699
|
+
assert_nothing_raised { @p.parse %w(--version) }
|
700
|
+
assert_nothing_raised { @p.parse %w(-h) }
|
701
|
+
assert_nothing_raised { @p.parse %w(-v) }
|
702
|
+
end
|
703
|
+
|
704
|
+
def test_version_and_help_override_errors
|
705
|
+
@p.opt :asdf, "desc", :type => String
|
706
|
+
@p.version "version"
|
707
|
+
assert_nothing_raised { @p.parse %w(--asdf goat) }
|
708
|
+
assert_raises(CommandlineError) { @p.parse %w(--asdf) }
|
709
|
+
assert_raises(HelpNeeded) { @p.parse %w(--asdf --help) }
|
710
|
+
assert_raises(VersionNeeded) { @p.parse %w(--asdf --version) }
|
711
|
+
end
|
712
|
+
|
713
|
+
def test_conflicts
|
714
|
+
@p.opt :one
|
715
|
+
assert_raises(ArgumentError) { @p.conflicts :one, :two }
|
716
|
+
@p.opt :two
|
717
|
+
assert_nothing_raised { @p.conflicts :one, :two }
|
718
|
+
assert_nothing_raised { @p.parse %w(--one) }
|
719
|
+
assert_nothing_raised { @p.parse %w(--two) }
|
720
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--one --two) }
|
721
|
+
|
722
|
+
@p.opt :hello
|
723
|
+
@p.opt :yellow
|
724
|
+
@p.opt :mellow
|
725
|
+
@p.opt :jello
|
726
|
+
@p.conflicts :hello, :yellow, :mellow, :jello
|
727
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--hello --yellow --mellow --jello) }
|
728
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--hello --mellow --jello) }
|
729
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--hello --jello) }
|
730
|
+
|
731
|
+
assert_nothing_raised { opts = @p.parse %w(--hello) }
|
732
|
+
assert_nothing_raised { opts = @p.parse %w(--jello) }
|
733
|
+
assert_nothing_raised { opts = @p.parse %w(--yellow) }
|
734
|
+
assert_nothing_raised { opts = @p.parse %w(--mellow) }
|
735
|
+
|
736
|
+
assert_nothing_raised { opts = @p.parse %w(--mellow --one) }
|
737
|
+
assert_nothing_raised { opts = @p.parse %w(--mellow --two) }
|
738
|
+
|
739
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--mellow --two --jello) }
|
740
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--one --mellow --two --jello) }
|
741
|
+
end
|
742
|
+
|
743
|
+
def test_conflict_error_messages
|
744
|
+
@p.opt :one
|
745
|
+
@p.opt "two"
|
746
|
+
@p.conflicts :one, "two"
|
747
|
+
|
748
|
+
begin
|
749
|
+
@p.parse %w(--one --two)
|
750
|
+
flunk "no error thrown"
|
751
|
+
rescue CommandlineError => e
|
752
|
+
assert_match(/--one/, e.message)
|
753
|
+
assert_match(/--two/, e.message)
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
def test_depends
|
758
|
+
@p.opt :one
|
759
|
+
assert_raises(ArgumentError) { @p.depends :one, :two }
|
760
|
+
@p.opt :two
|
761
|
+
assert_nothing_raised { @p.depends :one, :two }
|
762
|
+
assert_nothing_raised { opts = @p.parse %w(--one --two) }
|
763
|
+
assert_raises(CommandlineError) { @p.parse %w(--one) }
|
764
|
+
assert_raises(CommandlineError) { @p.parse %w(--two) }
|
765
|
+
|
766
|
+
@p.opt :hello
|
767
|
+
@p.opt :yellow
|
768
|
+
@p.opt :mellow
|
769
|
+
@p.opt :jello
|
770
|
+
@p.depends :hello, :yellow, :mellow, :jello
|
771
|
+
assert_nothing_raised { opts = @p.parse %w(--hello --yellow --mellow --jello) }
|
772
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--hello --mellow --jello) }
|
773
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--hello --jello) }
|
774
|
+
|
775
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--hello) }
|
776
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--mellow) }
|
777
|
+
|
778
|
+
assert_nothing_raised { opts = @p.parse %w(--hello --yellow --mellow --jello --one --two) }
|
779
|
+
assert_nothing_raised { opts = @p.parse %w(--hello --yellow --mellow --jello --one --two a b c) }
|
780
|
+
|
781
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--mellow --two --jello --one) }
|
782
|
+
end
|
783
|
+
|
784
|
+
def test_depend_error_messages
|
785
|
+
@p.opt :one
|
786
|
+
@p.opt "two"
|
787
|
+
@p.depends :one, "two"
|
788
|
+
|
789
|
+
assert_nothing_raised { @p.parse %w(--one --two) }
|
790
|
+
|
791
|
+
begin
|
792
|
+
@p.parse %w(--one)
|
793
|
+
flunk "no error thrown"
|
794
|
+
rescue CommandlineError => e
|
795
|
+
assert_match(/--one/, e.message)
|
796
|
+
assert_match(/--two/, e.message)
|
797
|
+
end
|
798
|
+
|
799
|
+
begin
|
800
|
+
@p.parse %w(--two)
|
801
|
+
flunk "no error thrown"
|
802
|
+
rescue CommandlineError => e
|
803
|
+
assert_match(/--one/, e.message)
|
804
|
+
assert_match(/--two/, e.message)
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
## courtesy neill zero
|
809
|
+
def test_two_required_one_missing_accuses_correctly
|
810
|
+
@p.opt "arg1", "desc1", :required => true
|
811
|
+
@p.opt "arg2", "desc2", :required => true
|
812
|
+
|
813
|
+
begin
|
814
|
+
@p.parse(%w(--arg1))
|
815
|
+
flunk "should have failed on a missing req"
|
816
|
+
rescue CommandlineError => e
|
817
|
+
assert e.message =~ /arg2/, "didn't mention arg2 in the error msg: #{e.message}"
|
818
|
+
end
|
819
|
+
|
820
|
+
begin
|
821
|
+
@p.parse(%w(--arg2))
|
822
|
+
flunk "should have failed on a missing req"
|
823
|
+
rescue CommandlineError => e
|
824
|
+
assert e.message =~ /arg1/, "didn't mention arg1 in the error msg: #{e.message}"
|
825
|
+
end
|
826
|
+
|
827
|
+
assert_nothing_raised { @p.parse(%w(--arg1 --arg2)) }
|
828
|
+
end
|
829
|
+
|
830
|
+
def test_stopwords_mixed
|
831
|
+
@p.opt "arg1", :default => false
|
832
|
+
@p.opt "arg2", :default => false
|
833
|
+
@p.stop_on %w(happy sad)
|
834
|
+
|
835
|
+
opts = @p.parse %w(--arg1 happy --arg2)
|
836
|
+
assert_equal true, opts["arg1"]
|
837
|
+
assert_equal false, opts["arg2"]
|
838
|
+
|
839
|
+
## restart parsing
|
840
|
+
@p.leftovers.shift
|
841
|
+
opts = @p.parse @p.leftovers
|
842
|
+
assert_equal false, opts["arg1"]
|
843
|
+
assert_equal true, opts["arg2"]
|
844
|
+
end
|
845
|
+
|
846
|
+
def test_stopwords_no_stopwords
|
847
|
+
@p.opt "arg1", :default => false
|
848
|
+
@p.opt "arg2", :default => false
|
849
|
+
@p.stop_on %w(happy sad)
|
850
|
+
|
851
|
+
opts = @p.parse %w(--arg1 --arg2)
|
852
|
+
assert_equal true, opts["arg1"]
|
853
|
+
assert_equal true, opts["arg2"]
|
854
|
+
|
855
|
+
## restart parsing
|
856
|
+
@p.leftovers.shift
|
857
|
+
opts = @p.parse @p.leftovers
|
858
|
+
assert_equal false, opts["arg1"]
|
859
|
+
assert_equal false, opts["arg2"]
|
860
|
+
end
|
861
|
+
|
862
|
+
def test_stopwords_multiple_stopwords
|
863
|
+
@p.opt "arg1", :default => false
|
864
|
+
@p.opt "arg2", :default => false
|
865
|
+
@p.stop_on %w(happy sad)
|
866
|
+
|
867
|
+
opts = @p.parse %w(happy sad --arg1 --arg2)
|
868
|
+
assert_equal false, opts["arg1"]
|
869
|
+
assert_equal false, opts["arg2"]
|
870
|
+
|
871
|
+
## restart parsing
|
872
|
+
@p.leftovers.shift
|
873
|
+
opts = @p.parse @p.leftovers
|
874
|
+
assert_equal false, opts["arg1"]
|
875
|
+
assert_equal false, opts["arg2"]
|
876
|
+
|
877
|
+
## restart parsing again
|
878
|
+
@p.leftovers.shift
|
879
|
+
opts = @p.parse @p.leftovers
|
880
|
+
assert_equal true, opts["arg1"]
|
881
|
+
assert_equal true, opts["arg2"]
|
882
|
+
end
|
883
|
+
|
884
|
+
def test_stopwords_with_short_args
|
885
|
+
@p.opt :global_option, "This is a global option", :short => "-g"
|
886
|
+
@p.stop_on %w(sub-command-1 sub-command-2)
|
887
|
+
|
888
|
+
global_opts = @p.parse %w(-g sub-command-1 -c)
|
889
|
+
cmd = @p.leftovers.shift
|
890
|
+
|
891
|
+
@q = Parser.new
|
892
|
+
@q.opt :cmd_option, "This is an option only for the subcommand", :short => "-c"
|
893
|
+
cmd_opts = @q.parse @p.leftovers
|
894
|
+
|
895
|
+
assert_equal true, global_opts[:global_option]
|
896
|
+
assert_nil global_opts[:cmd_option]
|
897
|
+
|
898
|
+
assert_equal true, cmd_opts[:cmd_option]
|
899
|
+
assert_nil cmd_opts[:global_option]
|
900
|
+
|
901
|
+
assert_equal cmd, "sub-command-1"
|
902
|
+
assert_equal @q.leftovers, []
|
903
|
+
end
|
904
|
+
|
905
|
+
def assert_parses_correctly(parser, commandline, expected_opts,
|
906
|
+
expected_leftovers)
|
907
|
+
opts = parser.parse commandline
|
908
|
+
assert_equal expected_opts, opts
|
909
|
+
assert_equal expected_leftovers, parser.leftovers
|
910
|
+
end
|
911
|
+
|
912
|
+
def test_unknown_subcommand
|
913
|
+
@p.opt :global_flag, "Global flag", :short => "-g", :type => :flag
|
914
|
+
@p.opt :global_param, "Global parameter", :short => "-p", :default => 5
|
915
|
+
@p.stop_on_unknown
|
916
|
+
|
917
|
+
expected_opts = { :global_flag => true, :help => false, :global_param => 5, :global_flag_given => true }
|
918
|
+
expected_leftovers = [ "my_subcommand", "-c" ]
|
919
|
+
|
920
|
+
assert_parses_correctly @p, %w(--global-flag my_subcommand -c), \
|
921
|
+
expected_opts, expected_leftovers
|
922
|
+
assert_parses_correctly @p, %w(-g my_subcommand -c), \
|
923
|
+
expected_opts, expected_leftovers
|
924
|
+
|
925
|
+
expected_opts = { :global_flag => false, :help => false, :global_param => 5, :global_param_given => true }
|
926
|
+
expected_leftovers = [ "my_subcommand", "-c" ]
|
927
|
+
|
928
|
+
assert_parses_correctly @p, %w(-p 5 my_subcommand -c), \
|
929
|
+
expected_opts, expected_leftovers
|
930
|
+
assert_parses_correctly @p, %w(--global-param 5 my_subcommand -c), \
|
931
|
+
expected_opts, expected_leftovers
|
932
|
+
end
|
933
|
+
|
934
|
+
def test_alternate_args
|
935
|
+
args = %w(-a -b -c)
|
936
|
+
|
937
|
+
opts = ::Trollop.options(args) do
|
938
|
+
opt :alpher, "Ralph Alpher", :short => "-a"
|
939
|
+
opt :bethe, "Hans Bethe", :short => "-b"
|
940
|
+
opt :gamow, "George Gamow", :short => "-c"
|
941
|
+
end
|
942
|
+
|
943
|
+
physicists_with_humor = [:alpher, :bethe, :gamow]
|
944
|
+
physicists_with_humor.each do |physicist|
|
945
|
+
assert_equal true, opts[physicist]
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|
949
|
+
def test_date_arg_type
|
950
|
+
temp = Date.new
|
951
|
+
@p.opt :arg, 'desc', :type => :date
|
952
|
+
@p.opt :arg2, 'desc', :type => Date
|
953
|
+
@p.opt :arg3, 'desc', :default => temp
|
954
|
+
|
955
|
+
opts = nil
|
956
|
+
assert_nothing_raised { opts = @p.parse }
|
957
|
+
assert_equal temp, opts[:arg3]
|
958
|
+
|
959
|
+
assert_nothing_raised { opts = @p.parse %w(--arg 5/1/2010) }
|
960
|
+
assert_kind_of Date, opts[:arg]
|
961
|
+
assert_equal Date.parse('5/1/2010'), opts[:arg]
|
962
|
+
|
963
|
+
assert_nothing_raised { opts = @p.parse %w(--arg2 5/1/2010) }
|
964
|
+
assert_kind_of Date, opts[:arg2]
|
965
|
+
assert_equal Date.parse('5/1/2010'), opts[:arg2]
|
966
|
+
end
|
967
|
+
|
968
|
+
def test_unknown_arg_class_type
|
969
|
+
assert_raise ArgumentError do
|
970
|
+
@p.opt :arg, 'desc', :type => Hash
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
def test_io_arg_type
|
975
|
+
@p.opt :arg, "desc", :type => :io
|
976
|
+
@p.opt :arg2, "desc", :type => IO
|
977
|
+
@p.opt :arg3, "desc", :default => $stdout
|
978
|
+
|
979
|
+
opts = nil
|
980
|
+
assert_nothing_raised { opts = @p.parse() }
|
981
|
+
assert_equal $stdout, opts[:arg3]
|
982
|
+
|
983
|
+
assert_nothing_raised { opts = @p.parse %w(--arg /dev/null) }
|
984
|
+
assert_kind_of File, opts[:arg]
|
985
|
+
assert_equal "/dev/null", opts[:arg].path
|
986
|
+
|
987
|
+
#TODO: move to mocks
|
988
|
+
#assert_nothing_raised { opts = @p.parse %w(--arg2 http://google.com/) }
|
989
|
+
#assert_kind_of StringIO, opts[:arg2]
|
990
|
+
|
991
|
+
assert_nothing_raised { opts = @p.parse %w(--arg3 stdin) }
|
992
|
+
assert_equal $stdin, opts[:arg3]
|
993
|
+
|
994
|
+
assert_raises(CommandlineError) { opts = @p.parse %w(--arg /fdasfasef/fessafef/asdfasdfa/fesasf) }
|
995
|
+
end
|
996
|
+
|
997
|
+
def test_openstruct_style_access
|
998
|
+
@p.opt "arg1", "desc", :type => :int
|
999
|
+
@p.opt :arg2, "desc", :type => :int
|
1000
|
+
|
1001
|
+
opts = @p.parse(%w(--arg1 3 --arg2 4))
|
1002
|
+
|
1003
|
+
assert_nothing_raised { opts.arg1 }
|
1004
|
+
assert_nothing_raised { opts.arg2 }
|
1005
|
+
assert_equal 3, opts.arg1
|
1006
|
+
assert_equal 4, opts.arg2
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
def test_multi_args_autobox_defaults
|
1010
|
+
@p.opt :arg1, "desc", :default => "hello", :multi => true
|
1011
|
+
@p.opt :arg2, "desc", :default => ["hello"], :multi => true
|
1012
|
+
|
1013
|
+
opts = @p.parse
|
1014
|
+
assert_equal ["hello"], opts[:arg1]
|
1015
|
+
assert_equal ["hello"], opts[:arg2]
|
1016
|
+
|
1017
|
+
opts = @p.parse %w(--arg1 hello)
|
1018
|
+
assert_equal ["hello"], opts[:arg1]
|
1019
|
+
assert_equal ["hello"], opts[:arg2]
|
1020
|
+
|
1021
|
+
opts = @p.parse %w(--arg1 hello --arg1 there)
|
1022
|
+
assert_equal ["hello", "there"], opts[:arg1]
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
def test_ambigious_multi_plus_array_default_resolved_as_specified_by_documentation
|
1026
|
+
@p.opt :arg1, "desc", :default => ["potato"], :multi => true
|
1027
|
+
@p.opt :arg2, "desc", :default => ["potato"], :multi => true, :type => :strings
|
1028
|
+
@p.opt :arg3, "desc", :default => ["potato"]
|
1029
|
+
@p.opt :arg4, "desc", :default => ["potato", "rhubarb"], :short => :none, :multi => true
|
1030
|
+
|
1031
|
+
## arg1 should be multi-occurring but not multi-valued
|
1032
|
+
opts = @p.parse %w(--arg1 one two)
|
1033
|
+
assert_equal ["one"], opts[:arg1]
|
1034
|
+
assert_equal ["two"], @p.leftovers
|
1035
|
+
|
1036
|
+
opts = @p.parse %w(--arg1 one --arg1 two)
|
1037
|
+
assert_equal ["one", "two"], opts[:arg1]
|
1038
|
+
assert_equal [], @p.leftovers
|
1039
|
+
|
1040
|
+
## arg2 should be multi-valued and multi-occurring
|
1041
|
+
opts = @p.parse %w(--arg2 one two)
|
1042
|
+
assert_equal [["one", "two"]], opts[:arg2]
|
1043
|
+
assert_equal [], @p.leftovers
|
1044
|
+
|
1045
|
+
## arg3 should be multi-valued but not multi-occurring
|
1046
|
+
opts = @p.parse %w(--arg3 one two)
|
1047
|
+
assert_equal ["one", "two"], opts[:arg3]
|
1048
|
+
assert_equal [], @p.leftovers
|
1049
|
+
|
1050
|
+
## arg4 should be multi-valued but not multi-occurring
|
1051
|
+
opts = @p.parse %w()
|
1052
|
+
assert_equal ["potato", "rhubarb"], opts[:arg4]
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
def test_given_keys
|
1056
|
+
@p.opt :arg1
|
1057
|
+
@p.opt :arg2
|
1058
|
+
|
1059
|
+
opts = @p.parse %w(--arg1)
|
1060
|
+
assert opts[:arg1_given]
|
1061
|
+
assert !opts[:arg2_given]
|
1062
|
+
|
1063
|
+
opts = @p.parse %w(--arg2)
|
1064
|
+
assert !opts[:arg1_given]
|
1065
|
+
assert opts[:arg2_given]
|
1066
|
+
|
1067
|
+
opts = @p.parse []
|
1068
|
+
assert !opts[:arg1_given]
|
1069
|
+
assert !opts[:arg2_given]
|
1070
|
+
|
1071
|
+
opts = @p.parse %w(--arg1 --arg2)
|
1072
|
+
assert opts[:arg1_given]
|
1073
|
+
assert opts[:arg2_given]
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
def test_default_shorts_assigned_only_after_user_shorts
|
1077
|
+
@p.opt :aab, "aaa" # should be assigned to -b
|
1078
|
+
@p.opt :ccd, "bbb" # should be assigned to -d
|
1079
|
+
@p.opt :user1, "user1", :short => 'a'
|
1080
|
+
@p.opt :user2, "user2", :short => 'c'
|
1081
|
+
|
1082
|
+
opts = @p.parse %w(-a -b)
|
1083
|
+
assert opts[:user1]
|
1084
|
+
assert !opts[:user2]
|
1085
|
+
assert opts[:aab]
|
1086
|
+
assert !opts[:ccd]
|
1087
|
+
|
1088
|
+
opts = @p.parse %w(-c -d)
|
1089
|
+
assert !opts[:user1]
|
1090
|
+
assert opts[:user2]
|
1091
|
+
assert !opts[:aab]
|
1092
|
+
assert opts[:ccd]
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
def test_accepts_arguments_with_spaces
|
1096
|
+
@p.opt :arg1, "arg", :type => String
|
1097
|
+
@p.opt :arg2, "arg2", :type => String
|
1098
|
+
|
1099
|
+
opts = @p.parse ["--arg1", "hello there", "--arg2=hello there"]
|
1100
|
+
assert_equal "hello there", opts[:arg1]
|
1101
|
+
assert_equal "hello there", opts[:arg2]
|
1102
|
+
assert_equal 0, @p.leftovers.size
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
def test_multi_args_default_to_empty_array
|
1106
|
+
@p.opt :arg1, "arg", :multi => true
|
1107
|
+
opts = @p.parse []
|
1108
|
+
assert_equal [], opts[:arg1]
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
def test_simple_interface_handles_help
|
1112
|
+
ARGV.clear
|
1113
|
+
ARGV.unshift "-h"
|
1114
|
+
assert_raises(SystemExit) do
|
1115
|
+
opts = ::Trollop::options do
|
1116
|
+
opt :potato
|
1117
|
+
end
|
1118
|
+
raise "broken"
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
def test_simple_interface_handles_version
|
1123
|
+
ARGV.clear
|
1124
|
+
ARGV.unshift "-v"
|
1125
|
+
assert_raises(SystemExit) do
|
1126
|
+
opts = ::Trollop::options do
|
1127
|
+
version "1.2"
|
1128
|
+
opt :potato
|
1129
|
+
end
|
1130
|
+
raise "broken"
|
1131
|
+
end
|
1132
|
+
end
|
1133
|
+
|
1134
|
+
def test_simple_interface_handles_regular_usage
|
1135
|
+
ARGV.clear
|
1136
|
+
ARGV.unshift "--potato"
|
1137
|
+
opts = nil
|
1138
|
+
assert_nothing_raised do
|
1139
|
+
opts = ::Trollop::options do
|
1140
|
+
opt :potato
|
1141
|
+
end
|
1142
|
+
end
|
1143
|
+
assert opts[:potato]
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
def test_simple_interface_handles_die
|
1147
|
+
old_stderr, $stderr = $stderr, StringIO.new('w')
|
1148
|
+
ARGV.clear
|
1149
|
+
ARGV.unshift "--potato"
|
1150
|
+
opts = ::Trollop::options do
|
1151
|
+
opt :potato
|
1152
|
+
end
|
1153
|
+
assert_raises(SystemExit) { ::Trollop::die :potato, "is invalid" }
|
1154
|
+
ensure
|
1155
|
+
$stderr = old_stderr
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
def test_simple_interface_handles_die_without_message
|
1159
|
+
old_stderr, $stderr = $stderr, StringIO.new('w')
|
1160
|
+
ARGV.clear
|
1161
|
+
ARGV.unshift "--potato"
|
1162
|
+
opts = ::Trollop::options do
|
1163
|
+
opt :potato
|
1164
|
+
end
|
1165
|
+
assert_raises(SystemExit) { ::Trollop::die :potato }
|
1166
|
+
assert $stderr.string =~ /Error:/
|
1167
|
+
ensure
|
1168
|
+
$stderr = old_stderr
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
def test_with_standard_exception_handling
|
1172
|
+
assert_raise(SystemExit) do
|
1173
|
+
::Trollop.with_standard_exception_handling(@p) do
|
1174
|
+
raise ::Trollop::CommandlineError.new('cl error')
|
1175
|
+
end
|
1176
|
+
end
|
1177
|
+
end
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
end
|
1181
|
+
end
|