qoobaa-user-choices 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +39 -0
  4. data/README.rdoc +7 -0
  5. data/Rakefile +56 -0
  6. data/VERSION +1 -0
  7. data/examples/older/README.txt +133 -0
  8. data/examples/older/command-line.rb +46 -0
  9. data/examples/older/default-values.rb +41 -0
  10. data/examples/older/multiple-sources.rb +58 -0
  11. data/examples/older/postprocess.rb +39 -0
  12. data/examples/older/switches.rb +44 -0
  13. data/examples/older/two-args.rb +31 -0
  14. data/examples/older/types.rb +61 -0
  15. data/examples/tutorial/css/LICENSE.txt +1 -0
  16. data/examples/tutorial/css/bg2.gif +0 -0
  17. data/examples/tutorial/css/left.gif +0 -0
  18. data/examples/tutorial/css/left_on.gif +0 -0
  19. data/examples/tutorial/css/main.css +242 -0
  20. data/examples/tutorial/css/right.gif +0 -0
  21. data/examples/tutorial/css/right_on.gif +0 -0
  22. data/examples/tutorial/css/tvline.gif +0 -0
  23. data/examples/tutorial/css/von-foerster.jpg +0 -0
  24. data/examples/tutorial/css/von-foerster2.jpg +0 -0
  25. data/examples/tutorial/index.html +703 -0
  26. data/examples/tutorial/tutorial1.rb +41 -0
  27. data/examples/tutorial/tutorial2.rb +44 -0
  28. data/examples/tutorial/tutorial3.rb +47 -0
  29. data/examples/tutorial/tutorial4.rb +47 -0
  30. data/examples/tutorial/tutorial5.rb +35 -0
  31. data/examples/tutorial/tutorial6.rb +35 -0
  32. data/examples/tutorial/tutorial7.rb +41 -0
  33. data/lib/user-choices.rb +131 -0
  34. data/lib/user-choices/arglist-strategies.rb +179 -0
  35. data/lib/user-choices/builder.rb +118 -0
  36. data/lib/user-choices/command-line-source.rb +224 -0
  37. data/lib/user-choices/command.rb +42 -0
  38. data/lib/user-choices/conversions.rb +169 -0
  39. data/lib/user-choices/ruby-extensions.rb +20 -0
  40. data/lib/user-choices/sources.rb +278 -0
  41. data/lib/user-choices/version.rb +3 -0
  42. data/test/arglist_strategy_test.rb +42 -0
  43. data/test/builder_test.rb +631 -0
  44. data/test/command_line_source_test.rb +443 -0
  45. data/test/conversion_test.rb +172 -0
  46. data/test/source_test.rb +451 -0
  47. data/test/test_helper.rb +9 -0
  48. data/test/user_choices_slow_test.rb +276 -0
  49. data/user-choices.gemspec +104 -0
  50. metadata +122 -0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brian Marick on 2007-08-10.
4
+ # Copyright (c) 2007. All rights reserved.
5
+
6
+
7
+ class Range # :nodoc:
8
+ def in_words
9
+ last_element = self.last
10
+ last_element -= 1 if exclude_end?
11
+ "#{self.first} to #{last_element}"
12
+ end
13
+ end
14
+
15
+ class String # :nodoc:
16
+ def to_inputable_sym
17
+ gsub(/-/, '_').to_sym
18
+ end
19
+ end
20
+
@@ -0,0 +1,278 @@
1
+ require 'xmlsimple' unless self.class.const_defined?("XmlSimple") # ActiveSupport includes it in /vendor. Grr.
2
+ require 'yaml'
3
+ require 's4t-utils'
4
+ include S4tUtils
5
+
6
+ require 'user-choices/ruby-extensions'
7
+ require 'user-choices/conversions'
8
+
9
+
10
+ module UserChoices # :nodoc
11
+
12
+
13
+ # TODO: Right now, elements that are named in a source, but not in an
14
+ # add_choice() call, nevertheless appear in the final array. Is that good?
15
+ # Bad? Irrelevant?
16
+ class AbstractSource < Hash # :nodoc:
17
+
18
+ attr_reader :external_names
19
+
20
+ def initialize
21
+ super()
22
+ @external_names = {}
23
+ end
24
+
25
+ def source; subclass_responsibility; end
26
+
27
+
28
+ def fill; subclass_responsibility; end
29
+
30
+ def apply(choice_conversions)
31
+ each_conversion(choice_conversions) do | choice, conversion |
32
+ next unless self.has_key?(choice)
33
+
34
+ user_claims(conversion.suitable?(self[choice])) {
35
+ error_prefix + bad_look(choice, conversion)
36
+ }
37
+
38
+ self[choice] = conversion.convert(self[choice])
39
+ end
40
+ end
41
+
42
+ def adjust(final_results)
43
+ # Do nothing
44
+ end
45
+
46
+ def each_conversion(choice_conversion_hash)
47
+ choice_conversion_hash.each do | choice, conversion_list |
48
+ conversion_list.each do | conversion |
49
+ yield(choice, conversion)
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+ protected
56
+
57
+ def error_prefix
58
+ "Error in #{source}: "
59
+ end
60
+
61
+ def pretty_value(value)
62
+ case value
63
+ when Array
64
+ value.inspect
65
+ else
66
+ "'#{value}'"
67
+ end
68
+ end
69
+
70
+ def bad_look(key, conversion)
71
+ like_what = conversion.description
72
+ "#{@external_names[key]}'s value must be #{like_what}, and #{pretty_value(self[key])} doesn't look right."
73
+ end
74
+
75
+ end
76
+
77
+ class DefaultSource < AbstractSource # :nodoc:
78
+
79
+ def use_hash(defaults)
80
+ @defaults = defaults
81
+ count_symbols_as_external_names(@defaults.keys)
82
+ self
83
+ end
84
+
85
+ def fill
86
+ merge!(@defaults)
87
+ end
88
+
89
+ def source
90
+ "the default values"
91
+ end
92
+
93
+ def count_symbols_as_external_names(symbols)
94
+ symbols.each { | symbol |
95
+ # Use inspect so that symbol prints with leading colon
96
+ @external_names[symbol] = symbol.inspect
97
+ }
98
+ end
99
+ end
100
+ DefaultChoices = DefaultSource # Backward compatibility
101
+
102
+
103
+
104
+ # Describe the environment as a source of choices.
105
+ class EnvironmentSource < AbstractSource
106
+ def fill # :nodoc:
107
+ @external_names.each { | key, env_var |
108
+ self[key] = ENV[env_var] if ENV.has_key?(env_var)
109
+ }
110
+ end
111
+
112
+ # Environment variables beginning with _prefix_ (a string)
113
+ # are considered to be user choices relevant to this script.
114
+ # Everything after the prefix names a choice (that is, a symbol).
115
+ # Dashes are converted to underscores. Examples:
116
+ # * Environment variable <tt>prefix-my-choice</tt> with prefix <tt>"prefix-" is choice <tt>:my_choice</tt>.
117
+ # * Environment variable <tt>PREFIX_FOO</tt> with prefix <tt>"PREFIX_" is choice <tt>:FOO</tt>
118
+ #
119
+ # If you want an array of strings, separate the values by commas:
120
+ # ENV_VAR=a,b,c
121
+ # There's currently no way to escape a comma and no cleverness about
122
+ # quotes or whitespace.
123
+
124
+ def with_prefix(prefix)
125
+ matches = ENV.collect do | env_var, ignored_value |
126
+ if /^#{prefix}(.+)/ =~ env_var
127
+ [$1.to_inputable_sym, env_var]
128
+ end
129
+ end
130
+ @external_names.merge!(Hash[*matches.compact.flatten])
131
+ self
132
+ end
133
+
134
+ # Some environment variables have names you don't like. For example, $HOME
135
+ # might be annoying because of the uppercase. Also, if most of your program's
136
+ # environment variables have some prefix (see with_prefix) but you also want to use
137
+ # $HOME, you need a way to do that. You can satisfy both desires with
138
+ #
139
+ # EnvironmentSource.new.with_prefix("my_").mapping(:home => "HOME")
140
+
141
+ def mapping(map)
142
+ @external_names.merge!(map)
143
+ self
144
+ end
145
+
146
+
147
+ def source # :nodoc:
148
+ "the environment"
149
+ end
150
+ end
151
+ EnvironmentChoices = EnvironmentSource # Backward compatibility
152
+
153
+
154
+ class FileSource < AbstractSource # :nodoc:
155
+
156
+ def from_file(filename)
157
+ from_complete_path(File.join(S4tUtils.find_home, filename))
158
+ end
159
+
160
+ def from_complete_path(path)
161
+ @path = path
162
+ @contents_as_hash = self.read_into_hash
163
+ @contents_as_hash.each do | external_name, value |
164
+ sym = external_name.to_inputable_sym
165
+ @external_names[sym] = external_name
166
+ end
167
+ self
168
+ end
169
+
170
+ def fill # :nodoc:
171
+ @external_names.each do | symbol, external_name |
172
+ self[symbol] = @contents_as_hash[external_name]
173
+ end
174
+ end
175
+
176
+ def source # :nodoc:
177
+ "configuration file #{@path}"
178
+ end
179
+
180
+ def read_into_hash # :nodoc:
181
+ return {} unless File.exist?(@path)
182
+ begin
183
+ format_specific_reading
184
+ rescue Exception => ex
185
+ if format_specific_exception?(ex)
186
+ msg = "Badly formatted #{source}: " + format_specific_message(ex)
187
+ ex = ex.class.new(msg)
188
+ end
189
+ raise ex
190
+ end
191
+ end
192
+
193
+ protected
194
+
195
+ def format_specific_message(ex)
196
+ ex.message
197
+ end
198
+
199
+ def format_specific_exception_handling(ex); subclass_responsibility; end
200
+ def format_specific_reading; subclass_responsibility; end
201
+ end
202
+
203
+ # Use an XML file as a source of choices. The XML file is parsed
204
+ # with <tt>XmlSimple('ForceArray' => false)</tt>. That means that
205
+ # single elements like <home>Mars</home> are read as the value
206
+ # <tt>"Mars"</tt>, whereas <home>Mars</home><home>Venus</home> is
207
+ # read as <tt>["Mars", "Venus"]</tt>.
208
+ class XmlConfigFileSource < FileSource
209
+
210
+ # Treat _filename_ as the configuration file. _filename_ is expected
211
+ # to be in the home directory. The home directory is found in the
212
+ # same way Rubygems finds it. (First look in environment variables
213
+ # <tt>$HOME</tt>, <tt>$USERPROFILE</tt>, <tt>$HOMEDRIVE:$HOMEPATH</tt>,
214
+ # file expansion of <tt>"~"</tt> and finally the root.)
215
+ def format_specific_reading
216
+ XmlSimple.xml_in(@path, 'ForceArray' => false)
217
+ end
218
+
219
+ def format_specific_exception?(ex)
220
+ ex.is_a?(REXML::ParseException)
221
+ end
222
+
223
+ def format_specific_message(ex)
224
+ ex.continued_exception
225
+ end
226
+
227
+ end
228
+ XmlConfigFileChoices = XmlConfigFileSource # Backward compatibility
229
+
230
+
231
+
232
+
233
+ # Use an YAML file as a source of choices. Note: because the YAML parser
234
+ # can produce something out of many typo-filled YAML files, it's a
235
+ # good idea to check that your file looks like you'd expect before
236
+ # trusting in it. Do that with:
237
+ #
238
+ # irb> require 'yaml'
239
+ # irb> YAML.load_file('config.yaml')
240
+ #
241
+ class YamlConfigFileSource < FileSource
242
+ # Treat _filename_ as the configuration file. _filename_ is expected
243
+ # to be in the home directory. The home directory is found in the
244
+ # same way Rubygems finds it.
245
+ def format_specific_reading
246
+ result = YAML.load_file(@path)
247
+ ensure_hash_values_are_strings(result)
248
+ result
249
+ end
250
+
251
+ def format_specific_exception?(ex)
252
+ ex.is_a?(ArgumentError)
253
+ end
254
+
255
+
256
+
257
+ def ensure_hash_values_are_strings(h)
258
+ h.each { |k, v| ensure_element_is_string(h, k) }
259
+ end
260
+
261
+ def ensure_array_values_are_strings(a)
262
+ a.each_with_index { |elt, index| ensure_element_is_string(a, index) }
263
+ end
264
+
265
+ def ensure_element_is_string(collection, key)
266
+ case collection[key]
267
+ when Hash
268
+ ensure_hash_values_are_strings(collection[key])
269
+ when Array
270
+ ensure_array_values_are_strings(collection[key])
271
+ else
272
+ collection[key] = collection[key].to_s
273
+ end
274
+ end
275
+
276
+
277
+ end
278
+ end
@@ -0,0 +1,3 @@
1
+ module UserChoices
2
+ Version = '1.1.6'
3
+ end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brian Marick on 2007-08-10.
4
+ # Copyright (c) 2007. All rights reserved.
5
+
6
+ require 'test/unit'
7
+ require 's4t-utils'
8
+ require 'builder'
9
+ require 'user-choices/arglist-strategies'
10
+ include S4tUtils
11
+ set_test_paths(__FILE__)
12
+
13
+ # Since ArglistStrategies were extracted from CommandLineSource, most
14
+ # of the testing is implicit.
15
+
16
+ class AbstractArglistStrategyTests < Test::Unit::TestCase
17
+ include UserChoices
18
+
19
+ def test_range_violation_descriptions
20
+ @arglist_handler = AbstractArglistStrategy.new('unimportant')
21
+ # Good about plurals.
22
+ assert_match(/2 arguments given, 3 expected/,
23
+ @arglist_handler.arglist_arity_error(2, 3))
24
+
25
+ assert_match(/1 argument given, 3 expected/,
26
+ @arglist_handler.arglist_arity_error(1, 3))
27
+
28
+ assert_match(/0 arguments given, 1 expected/,
29
+ @arglist_handler.arglist_arity_error(0, 1))
30
+
31
+ # Handle both types of ranges.
32
+ assert_match(/2 arguments given, 3 to 5 expected/,
33
+ @arglist_handler.arglist_arity_error(2, 3..5))
34
+ assert_match(/1 argument given, 3 to 5 expected/,
35
+ @arglist_handler.arglist_arity_error(1, 3...6))
36
+
37
+ # Use 'or' if there are only two alternatives.
38
+ assert_match(/2 arguments given, 3 or 4 expected/,
39
+ @arglist_handler.arglist_arity_error(2, 3..4))
40
+
41
+ end
42
+ end
@@ -0,0 +1,631 @@
1
+ require 'test/unit'
2
+ require 's4t-utils'
3
+ require 'user-choices'
4
+ include S4tUtils
5
+ set_test_paths(__FILE__)
6
+
7
+
8
+ class TestDefaultsAndTypes < Test::Unit::TestCase
9
+ include UserChoices
10
+
11
+ def test_builder_can_add_defaults
12
+ b = ChoicesBuilder.new
13
+ b.add_choice(:trip_steps, :default => '5')
14
+ choices = b.build
15
+ assert_equal('5', choices[:trip_steps])
16
+ end
17
+
18
+ def test_defaults_need_not_be_strings
19
+ b = ChoicesBuilder.new
20
+ b.add_choice(:trip_steps, :default => 5)
21
+ choices = b.build
22
+ assert_equal(5, choices[:trip_steps])
23
+ end
24
+
25
+ def test_builder_can_declare_types_and_do_error_checking
26
+ b = ChoicesBuilder.new
27
+ b.add_choice(:trip_steps, :default => 'a', :type => :integer)
28
+ assert_raises_with_matching_message(StandardError,
29
+ /:trip_steps's value must be an integer, and 'a'/) {
30
+ b.build
31
+ }
32
+ end
33
+
34
+ def test_builder_can_declare_types_and_do_conversions
35
+ b = ChoicesBuilder.new
36
+ b.add_choice(:csv, :default => 'true', :type => :boolean)
37
+ choices = b.build
38
+ assert_equal(true, choices[:csv])
39
+ end
40
+
41
+ def test_some_types_cause_no_conversion
42
+ # Checking is done
43
+ b = ChoicesBuilder.new
44
+ b.add_choice(:trip_steps, :default => 'a', :type => ['b', 'c'])
45
+ assert_raises_with_matching_message(StandardError,
46
+ /'a' doesn't look right/) {
47
+ b.build
48
+ }
49
+
50
+ # ... but, if checking passes, no changes are made
51
+ b = ChoicesBuilder.new
52
+ b.add_choice(:trip_steps, :default => 'b', :type => ['b', 'c'])
53
+ assert_equal('b', b.build[:trip_steps])
54
+ end
55
+
56
+ def test_arrays_can_be_built_from_comma_separated_list
57
+ b = ChoicesBuilder.new
58
+ b.add_choice(:targets, :default => 'a,b,cd',
59
+ :type => [:string])
60
+ assert_equal(['a', 'b', 'cd'],
61
+ b.build[:targets])
62
+ end
63
+
64
+ def test_arrays_can_be_accepted_as_is
65
+ b = ChoicesBuilder.new
66
+ b.add_choice(:targets, :default => ['a', 'b', 'c'],
67
+ :type => [:string])
68
+ assert_equal(['a', 'b', 'c'], b.build[:targets])
69
+ end
70
+
71
+ def test_arrays_are_constructed_from_single_elements
72
+ b = ChoicesBuilder.new
73
+ b.add_choice(:targets, :default => 'a',
74
+ :type => [:string])
75
+ assert_equal(['a'], b.build[:targets])
76
+ end
77
+
78
+ def test_array_lengths_can_be_specified_exactly
79
+ b = ChoicesBuilder.new
80
+ b.add_choice(:targets, :length => 2, :default => ['wrong'])
81
+ assert_raises_with_matching_message(StandardError, /must be of length 2/) {
82
+ b.build
83
+ }
84
+ end
85
+
86
+ def test_array_lengths_can_be_specified_by_range
87
+ b = ChoicesBuilder.new
88
+ b.add_choice(:targets, :length => 2..3, :default => ['wrong'])
89
+ assert_raises_with_matching_message(StandardError, /this range: 2..3/) {
90
+ b.build
91
+ }
92
+ end
93
+
94
+ def test_array_lengths_apply_to_command_line_args # didn't used to, not in the same way.
95
+ with_command_args("a b c d") {
96
+ b = ChoicesBuilder.new
97
+ b.add_source(CommandLineSource, :usage, 'foo')
98
+ b.add_choice(:targets, :length => 2..3) { |command_line |
99
+ command_line.uses_arglist
100
+ }
101
+
102
+ output = capturing_stderr do
103
+ assert_wants_to_exit do
104
+ b.build
105
+ end
106
+ end
107
+ assert_match(/^Error in the command line: 4 arguments given, 2 or 3 expected/, output)
108
+ }
109
+ end
110
+
111
+ def test_missing_required_arg_is_caught_by_command_line
112
+ with_command_args("") {
113
+ b = ChoicesBuilder.new
114
+ b.add_source(PosixCommandLineSource, :usage, 'foo')
115
+ b.add_choice(:targets) { |command_line |
116
+ command_line.uses_arg
117
+ }
118
+
119
+ output = capturing_stderr do
120
+ assert_wants_to_exit do
121
+ b.build
122
+ end
123
+ end
124
+ assert_match(/^Error in the command line: 0 arguments given, 1 expected/, output)
125
+ }
126
+ end
127
+
128
+ def test_extra_required_arg_is_caught_by_command_line
129
+ with_command_args("one extra") {
130
+ b = ChoicesBuilder.new
131
+ b.add_source(CommandLineSource, :usage, 'foo')
132
+ b.add_choice(:targets) { |command_line |
133
+ command_line.uses_arg
134
+ }
135
+
136
+ output = capturing_stderr do
137
+ assert_wants_to_exit do
138
+ b.build
139
+ end
140
+ end
141
+ assert_match(/^Error in the command line: 2 arguments given, 1 expected/, output)
142
+ }
143
+ end
144
+
145
+ def test_extra_optional_arg_is_caught_by_command_line
146
+ with_command_args("one extra") {
147
+ b = ChoicesBuilder.new
148
+ b.add_source(PosixCommandLineSource, :usage, 'foo')
149
+ b.add_choice(:targets) { |command_line |
150
+ command_line.uses_optional_arg
151
+ }
152
+
153
+ output = capturing_stderr do
154
+ assert_wants_to_exit do
155
+ b.build
156
+ end
157
+ end
158
+ assert_match(/^Error in the command line: 2 arguments given, 0 or 1 expected/, output)
159
+ }
160
+ end
161
+ end
162
+
163
+ class TestChainingOfSources < Test::Unit::TestCase
164
+ include UserChoices
165
+
166
+ def test_sources_are_chained_correctly
167
+ with_environment_vars("prefix_in_ecd" => "e") {
168
+ with_local_config_file(".builder_rc",
169
+ "<config>
170
+ <in_ecd>c</in_ecd>
171
+ <in_cd>c</in_cd>
172
+ </config>") {
173
+ b = ChoicesBuilder.new
174
+ b.add_source(EnvironmentSource, :with_prefix, "prefix_")
175
+ b.add_source(XmlConfigFileSource, :from_file, ".builder_rc")
176
+
177
+ b.add_choice(:in_ecd, :default => "d")
178
+ b.add_choice(:in_cd, :default => "d")
179
+ b.add_choice(:in_d, :default => "d")
180
+
181
+ choices = b.build
182
+
183
+ assert_equal('e', choices[:in_ecd])
184
+ assert_equal('c', choices[:in_cd])
185
+ assert_equal('d', choices[:in_d])
186
+ }
187
+ }
188
+ end
189
+
190
+
191
+ def test_priority_over_default
192
+ with_command_args("--option perfectly-fine --only-cmd=oc") {
193
+ b = ChoicesBuilder.new
194
+ b.add_source(CommandLineSource, :usage, "blah, blah")
195
+ b.add_choice(:option, :default => '0.3') { | command_line |
196
+ command_line.uses_option('--option VALUE')
197
+ }
198
+ b.add_choice(:only_cmd) { |command_line|
199
+ command_line.uses_option('--only-cmd VALUE')
200
+ }
201
+ b.add_choice(:only_default, :default => 'od')
202
+ choices = b.build
203
+ assert_equal("perfectly-fine", choices[:option])
204
+ assert_equal("oc", choices[:only_cmd])
205
+ assert_equal("od", choices[:only_default])
206
+ }
207
+ end
208
+
209
+ def test_checking_is_done_for_all_sources
210
+ with_command_args("--command-option perfectly-fine") {
211
+ assert_raises_with_matching_message(StandardError,
212
+ /^Error in the default values/) {
213
+ b = ChoicesBuilder.new
214
+ b.add_source(CommandLineSource, :usage, "blah")
215
+ b.add_choice(:command_option) { | command_line |
216
+ command_line.uses_option('--command-option VALUE')
217
+ }
218
+ b.add_choice(:broken, :default => '0.3', :type => :integer)
219
+ b.build
220
+ }
221
+ }
222
+ end
223
+
224
+ def test_conversions_are_done_for_all_sources
225
+ with_environment_vars("amazon_rc" => "1") {
226
+ b = ChoicesBuilder.new
227
+ b.add_source(EnvironmentSource, :with_prefix, 'amazon')
228
+ b.add_choice(:_rc, :type => :integer, :default => '3')
229
+ assert_equal(1, b.build[:_rc])
230
+ }
231
+ end
232
+
233
+ def test_unmentioned_choices_are_nil
234
+ with_environment_vars("amazon_rc" => "1") {
235
+ b = ChoicesBuilder.new
236
+ b.add_source(EnvironmentSource, :with_prefix, 'amazon_')
237
+ b.add_choice(:rc, :default => 5, :type => :integer)
238
+ choices = b.build
239
+ assert_equal(nil, choices[:unmentioned])
240
+ assert_equal(1, choices[:rc]) # for fun
241
+ }
242
+ end
243
+
244
+ def test_given_optional_args_override_lower_precedence_sources
245
+ with_command_args("override") {
246
+ b = ChoicesBuilder.new
247
+ b.add_source(CommandLineSource, :usage, "blah")
248
+ b.add_choice(:name, :default => 'default') { | command_line |
249
+ command_line.uses_optional_arg
250
+ }
251
+ choices = b.build
252
+ assert_equal('override', choices[:name])
253
+ }
254
+ end
255
+
256
+ def test_non_empty_arglists_override_lower_precedence_sources
257
+ with_command_args("1") {
258
+ b = ChoicesBuilder.new
259
+ b.add_source(CommandLineSource, :usage, 'blah')
260
+ b.add_choice(:name, :default => ['default', '1']) { |command_line|
261
+ command_line.uses_arglist
262
+ }
263
+ assert_equal(['1'], b.build[:name])
264
+ }
265
+ end
266
+
267
+ end
268
+
269
+
270
+ class TestInteractionOfArglistsWithOtherSources < Test::Unit::TestCase
271
+ include UserChoices
272
+
273
+ def test_missing_optional_args_do_not_override_lower_precedence_sources
274
+ with_command_args("") {
275
+ b = ChoicesBuilder.new
276
+ b.add_source(CommandLineSource, :usage, 'blah')
277
+ b.add_choice(:name, :default => 'default') { |command_line|
278
+ command_line.uses_optional_arg
279
+ }
280
+ assert_equal('default', b.build[:name])
281
+ }
282
+ end
283
+
284
+ def test_missing_optional_args_are_ok_if_no_lower_precedence_sources
285
+ with_command_args("") {
286
+ b = ChoicesBuilder.new
287
+ b.add_source(CommandLineSource, :usage, 'blah')
288
+ b.add_choice(:name) { |command_line|
289
+ command_line.uses_optional_arg
290
+ }
291
+ assert_false(b.build.has_key?(:name))
292
+ }
293
+ end
294
+
295
+ def test_present_optional_args_do_override_lower_precedence_sources
296
+ with_command_args("1") {
297
+ b = ChoicesBuilder.new
298
+ b.add_source(CommandLineSource, :usage, 'blah')
299
+ b.add_choice(:name, :default => 'default') { |command_line|
300
+ command_line.uses_optional_arg
301
+ }
302
+ assert_equal('1', b.build[:name])
303
+ }
304
+ end
305
+
306
+
307
+
308
+ def test_empty_arglists_do_not_override_lower_precedence_sources
309
+ # nothing / stuff
310
+ xml = "<config><names>1</names><names>2</names></config>"
311
+ with_local_config_file("test-config", xml) {
312
+ with_command_args("") {
313
+ b = ChoicesBuilder.new
314
+ b.add_source(CommandLineSource, :usage, 'blah')
315
+ b.add_source(XmlConfigFileSource, :from_file, 'test-config')
316
+ b.add_choice(:names) { |command_line|
317
+ command_line.uses_arglist
318
+ }
319
+ assert_equal(['1', '2'], b.build[:names])
320
+ }
321
+ }
322
+ end
323
+
324
+ def test_default_empty_arglist_is_empty_array
325
+ # nothing / nothing
326
+ with_command_args("") {
327
+ b = ChoicesBuilder.new
328
+ b.add_source(CommandLineSource, :usage, 'blah')
329
+ b.add_choice(:names) { |command_line|
330
+ command_line.uses_arglist
331
+ }
332
+ assert_equal([], b.build[:names])
333
+ }
334
+ end
335
+
336
+ def test_default_empty_arglist_can_be_set_explicitly
337
+ # nothing / nothing
338
+ with_command_args("") {
339
+ b = ChoicesBuilder.new
340
+ b.add_source(CommandLineSource, :usage, 'blah')
341
+ b.add_choice(:names, :default => ['fred']) { |command_line|
342
+ command_line.uses_arglist
343
+ }
344
+ assert_equal(['fred'], b.build[:names])
345
+ }
346
+ end
347
+
348
+ def test_present_arglist_does_override_lower_precedence_sources
349
+ # stuff / stuff
350
+ with_command_args("1") {
351
+ b = ChoicesBuilder.new
352
+ b.add_source(CommandLineSource, :usage, 'blah')
353
+ b.add_choice(:names, :default => ['default']) { |command_line|
354
+ command_line.uses_arglist
355
+ }
356
+ assert_equal(['1'], b.build[:names])
357
+ }
358
+ end
359
+
360
+ def test_setup_for_overridable_empty_argument_list_is_compatible_with_length
361
+ # This is an implementation-dependent test. Command-line arguments can
362
+ # be empty, yet overridden by less-important sources. This is done by
363
+ # initializing the default value to the empty list. But that must not
364
+ # trigger the length check.
365
+ with_command_args("1 2") {
366
+ b = ChoicesBuilder.new
367
+ b.add_source(CommandLineSource, :usage, 'blah')
368
+ b.add_choice(:names, :length => 2) { |command_line|
369
+ command_line.uses_arglist
370
+ }
371
+ assert_equal(['1', '2'], b.build[:names])
372
+ }
373
+ end
374
+
375
+ def test_an_empty_arglist_is_caught_by_the_length_check
376
+ with_command_args("") {
377
+ b = ChoicesBuilder.new
378
+ b.add_source(CommandLineSource, :usage, 'blah')
379
+ b.add_choice(:names, :length => 2) { |command_line|
380
+ command_line.uses_arglist
381
+ }
382
+ output = capturing_stderr do
383
+ assert_wants_to_exit do
384
+ b.build
385
+ end
386
+ end
387
+ assert_match(/command line: 0 arguments given, 2 expected/, output)
388
+ }
389
+ end
390
+
391
+
392
+ def test_choices_from_earlier_defaults_prevent_failing_arglist_arity_check
393
+ with_command_args("") {
394
+ b = ChoicesBuilder.new
395
+ b.add_source(CommandLineSource, :usage, 'blah')
396
+ b.add_choice(:names, :length => 1..2, :default => ['1']) { |command_line|
397
+ command_line.uses_arglist
398
+ }
399
+ assert_equal(['1'], b.build[:names])
400
+ }
401
+
402
+ end
403
+ end
404
+
405
+ class TestCommandLineConstruction < Test::Unit::TestCase
406
+ include UserChoices
407
+
408
+ def test_command_line_choices_requires_blocks_for_initialization
409
+ with_command_args("--switch -c 5 arg") {
410
+ b = ChoicesBuilder.new
411
+ b.add_source(CommandLineSource, :usage, "hi")
412
+
413
+ b.add_choice(:unused) { | command_line |
414
+ command_line.uses_switch("-u", "--unused")
415
+ }
416
+
417
+ b.add_choice(:switch, :type=>:boolean) { | command_line |
418
+ command_line.uses_switch("--switch")
419
+ }
420
+
421
+ b.add_choice(:clear, :type => :integer) { | command_line |
422
+ command_line.uses_option("-c", "--clear N",
423
+ "Clear the frobbistat N times.")
424
+ }
425
+
426
+ b.add_choice(:args) { | command_line |
427
+ command_line.uses_arglist
428
+ }
429
+
430
+ choices = b.build
431
+
432
+ assert_equal(true, choices[:switch])
433
+ assert_false(choices.has_key?(:unused))
434
+ assert_equal(5, choices[:clear])
435
+ assert_equal(['arg'], choices[:args])
436
+ }
437
+ end
438
+
439
+ def test_command_line_source_initializes_help_text
440
+ with_command_args('--help') {
441
+ output = capturing_stderr do
442
+ assert_wants_to_exit do
443
+ b = ChoicesBuilder.new
444
+ b.add_source(CommandLineSource, :usage,
445
+ "Usage: prog [options]",
446
+ "This is supplemental.")
447
+
448
+ b.add_choice(:test, :type => :boolean) { | command_line |
449
+ command_line.uses_switch("--test",
450
+ "Here's text for a switch")
451
+ }
452
+ b.add_choice(:renew) { | command_line |
453
+ command_line.uses_option("-r", "--renew VALUE",
454
+ "Here's text for an option")
455
+ }
456
+ b.build
457
+ end
458
+ end
459
+ assert(l1 = output.index("Usage: prog [options]"))
460
+ assert(l2 = output.index("This is supplemental"))
461
+ assert(l3 = output.index(/--\[no-\]test.*Here's text for a switch/))
462
+ assert(l4 = output.index(/-r.*--renew.*VALUE.*Here's text for an option/))
463
+ assert(l5 = output.index("--help"))
464
+
465
+ assert(l1 < l2)
466
+ assert(l2 < l3)
467
+ assert(l3 < l4)
468
+ assert(l4 < l5)
469
+ }
470
+ end
471
+
472
+ def test_builder_can_add_separators_to_help_text
473
+ with_command_args('--help') {
474
+ output = capturing_stderr do
475
+ assert_wants_to_exit do
476
+ b = ChoicesBuilder.new
477
+ b.add_source(CommandLineSource, :usage,
478
+ "Usage: prog [options]",
479
+ "This is supplemental.")
480
+
481
+ b.add_help_line("==============")
482
+ b.add_choice(:test) { | command_line |
483
+ command_line.uses_switch("--test",
484
+ "Here's text for a switch")
485
+ }
486
+ b.build
487
+ end
488
+ end
489
+ assert(l1 = output.index("This is supplemental"))
490
+ assert(l2 = output.index(/==============/))
491
+ assert(l3 = output.index(/--\[no-\]test.*Here's text for a switch/))
492
+
493
+ assert(l1 < l2)
494
+ assert(l2 < l3)
495
+ }
496
+ end
497
+
498
+ def test_builder_can_group_help_text_in_sections
499
+ with_command_args('--help') {
500
+ output = capturing_stderr do
501
+ assert_wants_to_exit do
502
+ b = ChoicesBuilder.new
503
+ b.add_source(CommandLineSource, :usage,
504
+ "Usage: prog [options]",
505
+ "This is supplemental.")
506
+
507
+ b.section("section head") do
508
+ b.add_choice(:test) { | command_line |
509
+ command_line.uses_switch("--test",
510
+ "Here's text for a switch")
511
+ }
512
+ end
513
+ b.add_choice(:renew) { | command_line |
514
+ command_line.uses_option("-r", "--renew VALUE",
515
+ "Here's text for an option")
516
+ }
517
+ b.build
518
+ end
519
+ end
520
+ assert(l1 = output.index("This is supplemental"))
521
+ assert(l2 = output.index(/section head/))
522
+ assert(l3 = output.index(/--\[no-\]test.*Here's text for a switch/))
523
+ assert(l4 = output.index(/---------/))
524
+ assert(l5 = output.index(/Here's text for an option/))
525
+
526
+ assert(l1 < l2)
527
+ assert(l2 < l3)
528
+ assert(l3 < l4)
529
+ assert(l4 < l5)
530
+ }
531
+ end
532
+
533
+ end
534
+
535
+ class TestSpecialCases < Test::Unit::TestCase
536
+ include UserChoices
537
+
538
+ def test_environment_choices_can_be_given_prefix_and_mapping
539
+ with_environment_vars("prefix_e" => "e", "HOME" => '/Users/marick') {
540
+ b = ChoicesBuilder.new
541
+ b.add_source(EnvironmentSource, :with_prefix, "prefix_", :mapping, {:home => "HOME" })
542
+ b.add_choice(:e)
543
+ b.add_choice(:home)
544
+ choices = b.build
545
+ assert_equal("e", choices[:e])
546
+ assert_equal("/Users/marick", choices[:home])
547
+ }
548
+
549
+ end
550
+
551
+
552
+ def test_required_arg_with_type_conversion
553
+ with_command_args("2") {
554
+ b = ChoicesBuilder.new
555
+ b.add_source(CommandLineSource, :usage, "blah")
556
+ b.add_choice(:e, :type => :integer) { |command_line |
557
+ command_line.uses_arg
558
+ }
559
+ choices = b.build
560
+ assert_equal(2, choices[:e])
561
+ }
562
+ end
563
+
564
+ def test_required_arg_conversion_prints_right_message
565
+ with_command_args("b") {
566
+ b = ChoicesBuilder.new
567
+ b.add_source(CommandLineSource, :usage, "blah")
568
+ b.add_choice(:e, :type => :integer) { |command_line |
569
+ command_line.uses_arg
570
+ }
571
+ output = capturing_stderr do
572
+ assert_wants_to_exit do
573
+ b.build
574
+ end
575
+ end
576
+ assert_no_match(/^Error in the command line: Error in the command line: /, output)
577
+ }
578
+ end
579
+
580
+ def test_optional_arg_with_type_conversion
581
+ with_command_args('2') {
582
+ b = ChoicesBuilder.new
583
+ b.add_source(CommandLineSource, :usage, "blah")
584
+ b.add_choice(:e, :type => :integer) { |command_line |
585
+ command_line.uses_optional_arg
586
+ }
587
+ choices = b.build
588
+ assert_equal(2, choices[:e])
589
+ }
590
+ end
591
+
592
+ def test_missing_optional_arg_with_type_conversion_is_OK
593
+ # The type check applies only if the value is given.
594
+ with_command_args('') {
595
+ b = ChoicesBuilder.new
596
+ b.add_source(CommandLineSource, :usage, "blah")
597
+ b.add_choice(:e, :type => :integer) { |command_line |
598
+ command_line.uses_optional_arg
599
+ }
600
+ assert_equal(nil, b.build[:e])
601
+ }
602
+ end
603
+
604
+ end
605
+
606
+
607
+ class TestUtilities < Test::Unit::TestCase
608
+ include UserChoices
609
+
610
+ def setup
611
+ @builder = ChoicesBuilder.new
612
+ end
613
+
614
+ def test_message_send_splitter
615
+ assert_equal([[:usage, 'arg']],
616
+ @builder.message_sends([:usage, 'arg']))
617
+ assert_equal([[:usage, 'arg1', 2]],
618
+ @builder.message_sends([:usage, 'arg1', 2]))
619
+ assert_equal([[:msg, 'arg1', 2], [:next, 1]],
620
+ @builder.message_sends([:msg, 'arg1', 2, :next, 1]))
621
+ # a common case
622
+ assert_equal([[:with_prefix, 'p_'], [:mapping, {:home => 'home'}]],
623
+ @builder.message_sends([:with_prefix, 'p_', :mapping, {:home => 'home'}]))
624
+ end
625
+
626
+ def test_symbol_indices
627
+ assert_equal([0, 3], @builder.symbol_indices([:foo, 1, 2, :bar, "quux"]))
628
+ end
629
+ end
630
+
631
+