wool 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.document +5 -0
  2. data/.gitignore +23 -0
  3. data/LICENSE +45 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +77 -0
  6. data/TODO.md +17 -0
  7. data/VERSION +1 -0
  8. data/bin/wool +4 -0
  9. data/features/step_definitions/wool_steps.rb +39 -0
  10. data/features/support/env.rb +14 -0
  11. data/features/support/testdata/1_input +1 -0
  12. data/features/support/testdata/1_output +1 -0
  13. data/features/support/testdata/2_input +4 -0
  14. data/features/support/testdata/2_output +4 -0
  15. data/features/support/testdata/3_input +8 -0
  16. data/features/support/testdata/3_output +11 -0
  17. data/features/support/testdata/4_input +5 -0
  18. data/features/support/testdata/4_output +5 -0
  19. data/features/wool.feature +24 -0
  20. data/lib/wool.rb +40 -0
  21. data/lib/wool/advice/advice.rb +42 -0
  22. data/lib/wool/advice/comment_advice.rb +37 -0
  23. data/lib/wool/analysis/annotations.rb +34 -0
  24. data/lib/wool/analysis/annotations/next_annotation.rb +26 -0
  25. data/lib/wool/analysis/annotations/parent_annotation.rb +20 -0
  26. data/lib/wool/analysis/annotations/scope_annotation.rb +37 -0
  27. data/lib/wool/analysis/lexical_analysis.rb +165 -0
  28. data/lib/wool/analysis/protocol_registry.rb +32 -0
  29. data/lib/wool/analysis/protocols.rb +82 -0
  30. data/lib/wool/analysis/scope.rb +13 -0
  31. data/lib/wool/analysis/sexp_analysis.rb +98 -0
  32. data/lib/wool/analysis/signature.rb +16 -0
  33. data/lib/wool/analysis/symbol.rb +10 -0
  34. data/lib/wool/analysis/visitor.rb +36 -0
  35. data/lib/wool/analysis/wool_class.rb +47 -0
  36. data/lib/wool/rake/task.rb +42 -0
  37. data/lib/wool/runner.rb +156 -0
  38. data/lib/wool/scanner.rb +160 -0
  39. data/lib/wool/support/module_extensions.rb +84 -0
  40. data/lib/wool/third_party/trollop.rb +845 -0
  41. data/lib/wool/warning.rb +145 -0
  42. data/lib/wool/warnings/comment_spacing.rb +30 -0
  43. data/lib/wool/warnings/extra_blank_lines.rb +29 -0
  44. data/lib/wool/warnings/extra_whitespace.rb +15 -0
  45. data/lib/wool/warnings/line_length.rb +113 -0
  46. data/lib/wool/warnings/misaligned_unindentation.rb +16 -0
  47. data/lib/wool/warnings/operator_spacing.rb +63 -0
  48. data/lib/wool/warnings/rescue_exception.rb +41 -0
  49. data/lib/wool/warnings/semicolon.rb +24 -0
  50. data/lib/wool/warnings/useless_double_quotes.rb +37 -0
  51. data/spec/advice_specs/advice_spec.rb +69 -0
  52. data/spec/advice_specs/comment_advice_spec.rb +38 -0
  53. data/spec/advice_specs/spec_helper.rb +1 -0
  54. data/spec/analysis_specs/annotations_specs/next_prev_annotation_spec.rb +47 -0
  55. data/spec/analysis_specs/annotations_specs/parent_annotation_spec.rb +41 -0
  56. data/spec/analysis_specs/annotations_specs/spec_helper.rb +5 -0
  57. data/spec/analysis_specs/lexical_analysis_spec.rb +179 -0
  58. data/spec/analysis_specs/protocol_registry_spec.rb +58 -0
  59. data/spec/analysis_specs/protocols_spec.rb +49 -0
  60. data/spec/analysis_specs/scope_spec.rb +20 -0
  61. data/spec/analysis_specs/sexp_analysis_spec.rb +134 -0
  62. data/spec/analysis_specs/spec_helper.rb +2 -0
  63. data/spec/analysis_specs/visitor_spec.rb +53 -0
  64. data/spec/analysis_specs/wool_class_spec.rb +54 -0
  65. data/spec/rake_specs/spec_helper.rb +1 -0
  66. data/spec/rake_specs/task_spec.rb +67 -0
  67. data/spec/runner_spec.rb +171 -0
  68. data/spec/scanner_spec.rb +75 -0
  69. data/spec/spec.opts +1 -0
  70. data/spec/spec_helper.rb +93 -0
  71. data/spec/support_specs/module_extensions_spec.rb +91 -0
  72. data/spec/support_specs/spec_helper.rb +1 -0
  73. data/spec/warning_spec.rb +95 -0
  74. data/spec/warning_specs/comment_spacing_spec.rb +57 -0
  75. data/spec/warning_specs/extra_blank_lines_spec.rb +70 -0
  76. data/spec/warning_specs/extra_whitespace_spec.rb +33 -0
  77. data/spec/warning_specs/line_length_spec.rb +165 -0
  78. data/spec/warning_specs/misaligned_unindentation_spec.rb +35 -0
  79. data/spec/warning_specs/operator_spacing_spec.rb +101 -0
  80. data/spec/warning_specs/rescue_exception_spec.rb +105 -0
  81. data/spec/warning_specs/semicolon_spec.rb +58 -0
  82. data/spec/warning_specs/spec_helper.rb +1 -0
  83. data/spec/warning_specs/useless_double_quotes_spec.rb +62 -0
  84. data/spec/wool_spec.rb +8 -0
  85. data/status_reports/2010/12/2010-12-14.md +163 -0
  86. data/test/third_party_tests/test_trollop.rb +1181 -0
  87. data/wool.gemspec +173 -0
  88. 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,8 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Wool do
4
+ it "has a version" do
5
+ VERSION.should_not be_nil
6
+ VERSION.should >= "0.5.0"
7
+ end
8
+ end
@@ -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