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.
- data/History.txt +17 -0
- data/LICENSE.txt +34 -0
- data/Manifest.txt +40 -0
- data/README.txt +1 -0
- data/Rakefile +19 -0
- data/Rakefile.hoe +24 -0
- data/examples/older/README.txt +133 -0
- data/examples/older/command-line.rb +51 -0
- data/examples/older/default-values.rb +47 -0
- data/examples/older/multiple-sources.rb +63 -0
- data/examples/older/postprocess.rb +45 -0
- data/examples/older/switches.rb +50 -0
- data/examples/older/two-args.rb +37 -0
- data/examples/older/types.rb +67 -0
- data/examples/tutorial/index.html +648 -0
- data/examples/tutorial/tutorial1.rb +48 -0
- data/examples/tutorial/tutorial2.rb +52 -0
- data/examples/tutorial/tutorial3.rb +55 -0
- data/examples/tutorial/tutorial4.rb +55 -0
- data/examples/tutorial/tutorial5.rb +42 -0
- data/examples/tutorial/tutorial6.rb +42 -0
- data/examples/tutorial/tutorial7.rb +48 -0
- data/lib/user-choices/arglist-strategies.rb +178 -0
- data/lib/user-choices/builder.rb +89 -0
- data/lib/user-choices/command-line-source.rb +220 -0
- data/lib/user-choices/command.rb +42 -0
- data/lib/user-choices/conversions.rb +154 -0
- data/lib/user-choices/ruby-extensions.rb +20 -0
- data/lib/user-choices/sources.rb +269 -0
- data/lib/user-choices/version.rb +3 -0
- data/lib/user-choices.rb +131 -0
- data/setup.rb +1585 -0
- data/test/arglist-strategy-tests.rb +42 -0
- data/test/builder-tests.rb +569 -0
- data/test/command-line-source-tests.rb +443 -0
- data/test/conversion-tests.rb +157 -0
- data/test/set-standalone-test-paths.rb +5 -0
- data/test/source-tests.rb +442 -0
- data/test/user-choices-slowtests.rb +274 -0
- data/user-choices.tmproj +575 -0
- 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
|