user-choices 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/History.txt +17 -0
  2. data/LICENSE.txt +34 -0
  3. data/Manifest.txt +40 -0
  4. data/README.txt +1 -0
  5. data/Rakefile +19 -0
  6. data/Rakefile.hoe +24 -0
  7. data/examples/older/README.txt +133 -0
  8. data/examples/older/command-line.rb +51 -0
  9. data/examples/older/default-values.rb +47 -0
  10. data/examples/older/multiple-sources.rb +63 -0
  11. data/examples/older/postprocess.rb +45 -0
  12. data/examples/older/switches.rb +50 -0
  13. data/examples/older/two-args.rb +37 -0
  14. data/examples/older/types.rb +67 -0
  15. data/examples/tutorial/index.html +648 -0
  16. data/examples/tutorial/tutorial1.rb +48 -0
  17. data/examples/tutorial/tutorial2.rb +52 -0
  18. data/examples/tutorial/tutorial3.rb +55 -0
  19. data/examples/tutorial/tutorial4.rb +55 -0
  20. data/examples/tutorial/tutorial5.rb +42 -0
  21. data/examples/tutorial/tutorial6.rb +42 -0
  22. data/examples/tutorial/tutorial7.rb +48 -0
  23. data/lib/user-choices/arglist-strategies.rb +178 -0
  24. data/lib/user-choices/builder.rb +89 -0
  25. data/lib/user-choices/command-line-source.rb +220 -0
  26. data/lib/user-choices/command.rb +42 -0
  27. data/lib/user-choices/conversions.rb +154 -0
  28. data/lib/user-choices/ruby-extensions.rb +20 -0
  29. data/lib/user-choices/sources.rb +269 -0
  30. data/lib/user-choices/version.rb +3 -0
  31. data/lib/user-choices.rb +131 -0
  32. data/setup.rb +1585 -0
  33. data/test/arglist-strategy-tests.rb +42 -0
  34. data/test/builder-tests.rb +569 -0
  35. data/test/command-line-source-tests.rb +443 -0
  36. data/test/conversion-tests.rb +157 -0
  37. data/test/set-standalone-test-paths.rb +5 -0
  38. data/test/source-tests.rb +442 -0
  39. data/test/user-choices-slowtests.rb +274 -0
  40. data/user-choices.tmproj +575 -0
  41. metadata +138 -0
@@ -0,0 +1,442 @@
1
+ load "set-standalone-test-paths.rb" unless $started_from_rakefile
2
+ require 'test/unit'
3
+ require 's4t-utils'
4
+ require 'builder'
5
+ require 'user-choices'
6
+ include S4tUtils
7
+ require 'extensions/string'
8
+
9
+
10
+ # The general contract of these objects.
11
+ class TestAbstractSource < Test::Unit::TestCase
12
+ include UserChoices
13
+
14
+ class SubHash < UserChoices::AbstractSource
15
+
16
+ # New never takes arguments. Class-specific initialization is done
17
+ # with an appropriately-named method.
18
+ #
19
+ # That method must return self and set up the external_name hash
20
+ # so that all symbols handled by this object can be given an external
21
+ # name.
22
+ def only_symbol(symbol, external_name, value)
23
+ @external_names[symbol] = external_name
24
+ @symbol = symbol
25
+ @value = value
26
+ self
27
+ end
28
+
29
+ # After fill(), values have been read, but not checked.
30
+ def fill; self[@symbol] = @value; end
31
+
32
+ def source; "the test hash"; end
33
+
34
+ end
35
+
36
+ def test_specific_initializer_notes_external_names
37
+ sh = SubHash.new.only_symbol(:sym, "name", "val")
38
+ assert_equal('name', sh.external_names[:sym])
39
+ end
40
+
41
+ def test_filling_sets_values
42
+ sh = SubHash.new.only_symbol(:sym, "name", "val")
43
+ sh.fill
44
+ assert_equal('val', sh[:sym])
45
+ end
46
+
47
+ def test_will_do_conversion_when_told
48
+ sh = SubHash.new.only_symbol(:sym, "name", "1")
49
+ sh.fill
50
+
51
+ conversions = { :sym => [Conversion.for(:integer)] }
52
+ sh.apply(conversions)
53
+ assert_equal(1, sh[:sym])
54
+ end
55
+
56
+ # Checking - really just want to know that the error message comes out right.
57
+
58
+ def test_will_do_integer_error_checking_when_told
59
+ sh = SubHash.new.only_symbol(:sym, "name", "val")
60
+ sh.fill
61
+
62
+ conversions = { :sym => [Conversion.for(:integer)] }
63
+ assert_raises_with_matching_message(StandardError,
64
+ /^Error in the test hash: name's value must be an integer, and 'val' doesn't look right/) {
65
+ sh.apply(conversions)
66
+ }
67
+ end
68
+
69
+
70
+ def test_will_do_boolean_error_checking_when_told
71
+ sh = SubHash.new.only_symbol(:sym, "name", "val")
72
+ sh.fill
73
+
74
+ conversions = { :sym => [Conversion.for(:boolean)] }
75
+ assert_raises_with_matching_message(StandardError,
76
+ /^Error in the test hash: name's value must be a boolean, and 'val' doesn't look right/) {
77
+ sh.apply(conversions)
78
+ }
79
+ end
80
+
81
+
82
+ def test_will_do_alternative_error_checking_when_told
83
+ sh = SubHash.new.only_symbol(:sym, "name", "val")
84
+ sh.fill
85
+
86
+ conversions = { :sym => [Conversion.for(["foo", "bar"])] }
87
+ assert_raises_with_matching_message(StandardError,
88
+ /^Error in the test hash: name's value must be one of 'foo' or 'bar', and 'val' doesn't look right/) {
89
+ sh.apply(conversions)
90
+ }
91
+ end
92
+
93
+
94
+ def test_will_do_exact_length_checking_when_told
95
+ sh = SubHash.new.only_symbol(:sym, "name", ["one", "two"])
96
+ sh.fill
97
+
98
+ conversions = { :sym => [Conversion.for([:string]), # actually not needed.
99
+ Conversion.for(:length => 5)] }
100
+ assert_raises_with_matching_message(StandardError,
101
+ /^Error in the test hash: name's value must be of length 5, and \["one", "two"\] doesn't look right/) {
102
+ sh.apply(conversions)
103
+ }
104
+ end
105
+
106
+
107
+ def test_will_do_range_length_checking_when_told
108
+ sh = SubHash.new.only_symbol(:sym, "name", ["one", "two"])
109
+ sh.fill
110
+
111
+ conversions = { :sym => [Conversion.for([:string]), # actually not needed.
112
+ Conversion.for(:length => 3..5)] }
113
+ assert_raises_with_matching_message(StandardError,
114
+ /^Error in the test hash: name's value must be a list whose length is in this range: 3..5, and \["one", "two"\] doesn't look right/) {
115
+ sh.apply(conversions)
116
+ }
117
+ end
118
+
119
+
120
+
121
+ def test_it_is_ok_for_key_not_to_appear
122
+ sh = SubHash.new.only_symbol(:sym, "name", "val")
123
+ sh.fill
124
+
125
+ conversions = { :another => [Conversion.for(:integer)] }
126
+ sh.apply(conversions)
127
+ assert_equal('val', sh[:sym])
128
+ assert_equal(nil, sh[:another])
129
+ end
130
+
131
+ end
132
+
133
+ class DefaultSourceTest < Test::Unit::TestCase
134
+ include UserChoices
135
+
136
+ def setup
137
+ @choices = DefaultSource.new.use_hash(:a => 'a')
138
+ @choices.fill
139
+ end
140
+
141
+ def test_default_values_are_created_with_key_not_string
142
+ assert_equal(1, @choices.size)
143
+ assert_equal('a', @choices[:a])
144
+ assert_equal(':a', @choices.external_names[:a])
145
+ end
146
+
147
+ def test_nil_is_default_default
148
+ assert_nil(@choices[:foo])
149
+ end
150
+
151
+
152
+ def test_error_message_will_look_good
153
+ assert_raises_with_matching_message(StandardError,
154
+ /^Error in the default values: :a's value/) {
155
+ @choices.apply( :a => [Conversion.for(:integer)])
156
+ }
157
+ end
158
+
159
+ def test_value_conversions_are_from_strings
160
+ c = DefaultSource.new.use_hash(:a => '5')
161
+ c.fill
162
+
163
+ c.apply(:a => [Conversion.for(:integer)])
164
+ assert_equal(5, c[:a])
165
+ end
166
+
167
+ end
168
+
169
+ class EnvironmentSourceTest < Test::Unit::TestCase
170
+ include UserChoices
171
+
172
+ def test_the_environment_args_of_interest_can_be_described_by_prefix
173
+ with_environment_vars('amazon_option' => "1") do
174
+ choices = EnvironmentSource.new.with_prefix('amazon_')
175
+ choices.fill
176
+ assert_true(choices.has_key?(:option))
177
+ assert_equal('1', choices[:option])
178
+ end
179
+ end
180
+
181
+ def test_the_environment_args_can_use_empty_string_as_the_prefix
182
+ # Though it's a silly thing to do.
183
+ with_environment_vars('amazon_option' => "1") do
184
+ choices = EnvironmentSource.new.with_prefix('')
185
+ choices.fill
186
+ assert_true(choices.has_key?(:amazon_option))
187
+ assert_equal('1', choices[:amazon_option])
188
+ end
189
+ end
190
+
191
+ def test_the_environment_args_of_interest_can_be_listed_explicitly
192
+ with_environment_vars('amazon_option' => "1",
193
+ 'root' => 'ok',
194
+ '~' => 'ok, too') do
195
+ choices = EnvironmentSource.new.mapping(:option => 'amazon_option',
196
+ :root => 'root',
197
+ :home => '~')
198
+ choices.fill
199
+ assert_equal(3, choices.size)
200
+ assert_equal('1', choices[:option])
201
+ assert_equal('ok', choices[:root])
202
+ assert_equal('ok, too', choices[:home])
203
+ end
204
+ end
205
+
206
+ def test_can_also_combine_both_forms
207
+ with_environment_vars('amazon_o' => "1",
208
+ 'other_option' => 'still found') do
209
+ choices = EnvironmentSource.new.with_prefix('amazon_').mapping(:other => 'other_option')
210
+ choices.fill
211
+
212
+ assert_equal(2, choices.size)
213
+ assert_equal('1', choices[:o])
214
+ assert_equal('still found', choices[:other])
215
+ end
216
+ end
217
+
218
+ def test_the_order_of_combination_does_not_matter
219
+ with_environment_vars('amazon_o' => "1",
220
+ 'other_option' => 'still found') do
221
+ choices = EnvironmentSource.new.mapping(:other => 'other_option').with_prefix('amazon_')
222
+ choices.fill
223
+
224
+ assert_equal(2, choices.size)
225
+ assert_equal('1', choices[:o])
226
+ assert_equal('still found', choices[:other])
227
+ end
228
+ end
229
+
230
+ def test_unmentioned_environment_vars_are_ignored
231
+ with_environment_vars('unfound' => "1") do
232
+ choices = EnvironmentSource.new.with_prefix("my_")
233
+ choices.fill
234
+ assert_true(choices.empty?)
235
+ end
236
+ end
237
+
238
+ def test_nil_is_default
239
+ with_environment_vars('found' => "1") do
240
+ choices = EnvironmentSource.new.mapping(:option => 'f')
241
+ choices.fill
242
+ assert_nil(choices[:foo])
243
+ assert_nil(choices[:option]) # for fun
244
+ end
245
+ end
246
+
247
+ def test_value_checking_is_set_up_properly
248
+ with_environment_vars('amazon_option' => "1") do
249
+ assert_raises_with_matching_message(StandardError,
250
+ /^Error in the environment: amazon_option's value/) {
251
+ choices = EnvironmentSource.new.with_prefix('amazon_')
252
+ choices.fill
253
+ choices.apply(:option => [Conversion.for(:boolean)])
254
+ }
255
+ end
256
+ end
257
+
258
+ def test_value_conversion_is_set_up_properly
259
+ with_environment_vars('a' => "1", 'names' => 'foo,bar') do
260
+ choices = EnvironmentSource.new.mapping(:a => 'a', :names => 'names')
261
+ choices.fill
262
+ choices.apply(:a => [Conversion.for(:integer)],
263
+ :names => [Conversion.for([:string])])
264
+ assert_equal(1, choices[:a])
265
+ assert_equal(['foo', 'bar'], choices[:names])
266
+ end
267
+ end
268
+
269
+
270
+ end
271
+
272
+ # Common behavior for all config files. Using XML as an example.
273
+ class FileSourceTestCase < Test::Unit::TestCase
274
+ include UserChoices
275
+
276
+ def setup
277
+ builder = Builder::XmlMarkup.new(:indent => 2)
278
+ @some_xml = builder.config {
279
+ builder.reverse("true")
280
+ builder.maximum("53")
281
+ builder.host('a.com')
282
+ builder.host('b.com')
283
+ }
284
+ end
285
+
286
+
287
+ def test_config_file_need_not_exist
288
+ assert_false(File.exist?(".amazonrc"))
289
+ choices = XmlConfigFileSource.new.from_file(".amazonrc")
290
+
291
+ assert_true(choices.empty?)
292
+ end
293
+
294
+
295
+ def test_config_file_value_checking_is_set_up_properly
296
+ with_local_config_file(".amazonrc", @some_xml) do
297
+ assert_raises_with_matching_message(StandardError,
298
+ %r{Error in configuration file ./.amazonrc: maximum's value.*'low'.*'high'}) {
299
+ choices = XmlConfigFileSource.new.from_file(".amazonrc")
300
+ choices.fill
301
+ choices.apply(:maximum => [Conversion.for(['low', 'high'])])
302
+ }
303
+ end
304
+ end
305
+
306
+
307
+ def test_value_conversions_are_set_up_properly
308
+ with_local_config_file('.amazonrc', @some_xml) do
309
+ choices = XmlConfigFileSource.new.from_file('.amazonrc')
310
+ choices.fill
311
+ choices.apply(:maximum => [Conversion.for(:integer)])
312
+ assert_equal(53, choices[:maximum])
313
+ end
314
+ end
315
+
316
+
317
+
318
+ def test_unmentioned_values_are_nil
319
+ with_local_config_file('.amazonrc', @some_xml) do
320
+ choices = XmlConfigFileSource.new.from_file('.amazonrc')
321
+ choices.fill
322
+ assert_nil(choices[:unmentioned])
323
+ end
324
+ end
325
+
326
+ def test_dashed_choice_names_are_underscored
327
+ with_local_config_file('.amazonrc', "<config><the-name>5</the-name></config>") do
328
+ choices = XmlConfigFileSource.new.from_file('.amazonrc')
329
+ choices.fill
330
+ assert_equal('5', choices[:the_name])
331
+ end
332
+ end
333
+
334
+
335
+ end
336
+
337
+
338
+
339
+ class XmlConfigFileSourceTestCase < Test::Unit::TestCase
340
+ include UserChoices
341
+
342
+ def setup
343
+ builder = Builder::XmlMarkup.new(:indent => 2)
344
+ @some_xml = builder.config {
345
+ builder.reverse("true")
346
+ builder.maximum("53")
347
+ builder.host('a.com')
348
+ builder.host('b.com')
349
+ }
350
+ end
351
+
352
+ def test_xml_config_file_normal_use
353
+ with_local_config_file('.amazonrc', @some_xml) {
354
+ choices = XmlConfigFileSource.new.from_file(".amazonrc")
355
+ choices.fill
356
+ choices.apply(:reverse => [Conversion.for(:boolean)],
357
+ :maximum => [Conversion.for(:integer)])
358
+
359
+ assert_equal(3, choices.size)
360
+ assert_equal(true, choices[:reverse])
361
+ assert_equal(53, choices[:maximum])
362
+ assert_equal(['a.com', 'b.com'], choices[:host])
363
+ }
364
+ end
365
+
366
+ def test_config_file_with_bad_xml
367
+ with_local_config_file('.amazonrc',"<malformed></xml>") {
368
+ assert_raise_with_matching_message(REXML::ParseException,
369
+ %r{Badly formatted configuration file ./.amazonrc: .*Missing end tag}) do
370
+ XmlConfigFileSource.new.from_file(".amazonrc")
371
+ end
372
+ }
373
+ end
374
+
375
+
376
+ end
377
+
378
+
379
+ class YamlConfigFileSourceTestCase < Test::Unit::TestCase
380
+ include UserChoices
381
+
382
+ def setup
383
+ @some_yaml = "
384
+ | ---
385
+ | reverse: true
386
+ | maximum: 53
387
+ | host:
388
+ | - a.com
389
+ | - b.com
390
+ | listarg: 1,2, 3
391
+ ".trim('|')
392
+ end
393
+
394
+ def test_string_assurance
395
+ choices = YamlConfigFileSource.new
396
+ a = [1]
397
+ choices.ensure_element_is_string(a, 0)
398
+ assert_equal(["1"], a)
399
+
400
+ h = {'foo' => false }
401
+ choices.ensure_element_is_string(h, 'foo')
402
+ assert_equal({'foo' => 'false'}, h)
403
+
404
+ a = [1, 2.0, true, 'already']
405
+ choices.ensure_array_values_are_strings(a)
406
+ assert_equal(['1', '2.0', 'true', 'already'], a)
407
+
408
+ h = {'1' => '2', 'false' => true, 99 => 100 }
409
+ choices.ensure_hash_values_are_strings(h)
410
+ assert_equal({'1' => '2', 'false' => 'true', 99 => '100' }, h)
411
+
412
+ h = {'1' => '2', 'false' => [1, true], 99 => {100 => true}}
413
+ choices.ensure_hash_values_are_strings(h)
414
+ assert_equal({'1' => '2', 'false' => ['1', 'true'], 99 => {100 => 'true'} }, h)
415
+ end
416
+
417
+ def test_yaml_config_file_normal_use
418
+ with_local_config_file('.amazonrc', @some_yaml) {
419
+ choices = YamlConfigFileSource.new.from_file(".amazonrc")
420
+ choices.fill
421
+
422
+ assert_equal(4, choices.size)
423
+ assert_equal("true", choices[:reverse])
424
+ assert_equal("53", choices[:maximum])
425
+ assert_equal(['a.com', 'b.com'], choices[:host])
426
+ assert_equal("1,2, 3", choices[:listarg])
427
+ }
428
+ end
429
+
430
+ def test_config_file_with_bad_yaml
431
+ with_local_config_file('.amazonrc',"foo:\n\tfred") {
432
+ assert_raise_with_matching_message(ArgumentError,
433
+ %r{Badly formatted configuration file ./.amazonrc: .*syntax error}) do
434
+ pp YamlConfigFileSource.new.from_file(".amazonrc"), 'should never have been reached'
435
+ end
436
+ }
437
+ end
438
+
439
+
440
+
441
+
442
+ end