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,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-08-09.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
# See the tutorial for explanations.
|
7
|
+
|
8
|
+
### The following adjusts the load path so that the correct version of
|
9
|
+
### a self-contained package is found, no matter where the script is
|
10
|
+
### run from.
|
11
|
+
require 'pathname'
|
12
|
+
$:.unshift((Pathname.new(__FILE__).parent.parent.parent + 'lib').to_s)
|
13
|
+
require 's4t-utils/load-path-auto-adjuster'
|
14
|
+
|
15
|
+
require 'pp'
|
16
|
+
require 'user-choices'
|
17
|
+
|
18
|
+
# See the tutorial for explanations.
|
19
|
+
|
20
|
+
class TutorialExample < UserChoices::Command
|
21
|
+
include UserChoices
|
22
|
+
|
23
|
+
def add_sources(builder)
|
24
|
+
builder.add_source(CommandLineSource, :usage,
|
25
|
+
"Usage: ruby #{$0} [options]")
|
26
|
+
builder.add_source(EnvironmentSource, :with_prefix, "myprog_")
|
27
|
+
builder.add_source(YamlConfigFileSource, :from_file, ".myprog-config.yml")
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_choices(builder)
|
31
|
+
builder.add_choice(:connections, :type=>:integer, :default=>0) { | command_line |
|
32
|
+
command_line.uses_option("-c", "--connections COUNT",
|
33
|
+
"Number of connections to open.")
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute
|
38
|
+
puts "There are #{@user_choices[:connections]} connections."
|
39
|
+
pp @user_choices
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
if $0 == __FILE__
|
45
|
+
S4tUtils.with_pleasant_exceptions do
|
46
|
+
TutorialExample.new.execute
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-08-09.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
|
7
|
+
# See the tutorial for explanations.
|
8
|
+
|
9
|
+
### The following adjusts the load path so that the correct version of
|
10
|
+
### a self-contained package is found, no matter where the script is
|
11
|
+
### run from.
|
12
|
+
require 'pathname'
|
13
|
+
$:.unshift((Pathname.new(__FILE__).parent.parent.parent + 'lib').to_s)
|
14
|
+
require 's4t-utils/load-path-auto-adjuster'
|
15
|
+
|
16
|
+
require 'pp'
|
17
|
+
require 'user-choices'
|
18
|
+
|
19
|
+
class TutorialExample < UserChoices::Command
|
20
|
+
include UserChoices
|
21
|
+
|
22
|
+
def add_sources(builder)
|
23
|
+
builder.add_source(CommandLineSource, :usage,
|
24
|
+
"Usage: ruby #{$0} [options] file1 [file2]")
|
25
|
+
builder.add_source(EnvironmentSource, :with_prefix, "myprog_")
|
26
|
+
builder.add_source(YamlConfigFileSource, :from_file, ".myprog-config.yml")
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_choices(builder)
|
30
|
+
builder.add_choice(:connections, :type=>:integer, :default=>0) { | command_line |
|
31
|
+
command_line.uses_option("-c", "--connections COUNT",
|
32
|
+
"Number of connections to open.")
|
33
|
+
}
|
34
|
+
builder.add_choice(:ssh, :type=>:boolean, :default=>false) { | command_line |
|
35
|
+
command_line.uses_switch("-s", "--ssh",
|
36
|
+
"Use ssh to open connection.")
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def execute
|
41
|
+
puts format("SSH %s be used.", @user_choices[:ssh] ? "should" : "should not")
|
42
|
+
puts "There are #{@user_choices[:connections]} connections."
|
43
|
+
pp @user_choices
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
if $0 == __FILE__
|
49
|
+
S4tUtils.with_pleasant_exceptions do
|
50
|
+
TutorialExample.new.execute
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-08-09.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
|
7
|
+
# See the tutorial for explanations.
|
8
|
+
|
9
|
+
### The following adjusts the load path so that the correct version of
|
10
|
+
### a self-contained package is found, no matter where the script is
|
11
|
+
### run from.
|
12
|
+
require 'pathname'
|
13
|
+
$:.unshift((Pathname.new(__FILE__).parent.parent.parent + 'lib').to_s)
|
14
|
+
require 's4t-utils/load-path-auto-adjuster'
|
15
|
+
|
16
|
+
require 'pp'
|
17
|
+
require 'user-choices'
|
18
|
+
|
19
|
+
class TutorialExample < UserChoices::Command
|
20
|
+
include UserChoices
|
21
|
+
|
22
|
+
def add_sources(builder)
|
23
|
+
builder.add_source(CommandLineSource, :usage,
|
24
|
+
"Usage: ruby #{$0} [options] file1 [file2]")
|
25
|
+
builder.add_source(EnvironmentSource, :with_prefix, "myprog_")
|
26
|
+
builder.add_source(YamlConfigFileSource, :from_file, ".myprog-config.yml")
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_choices(builder)
|
30
|
+
builder.add_choice(:connections, :type=>:integer, :default=>0) { | command_line |
|
31
|
+
command_line.uses_option("-c", "--connections COUNT",
|
32
|
+
"Number of connections to open.")
|
33
|
+
}
|
34
|
+
builder.add_choice(:ssh, :type=>:boolean, :default=>false) { | command_line |
|
35
|
+
command_line.uses_switch("-s", "--ssh",
|
36
|
+
"Use ssh to open connection.")
|
37
|
+
}
|
38
|
+
builder.add_choice(:files) { | command_line |
|
39
|
+
command_line.uses_arglist
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def execute
|
44
|
+
puts format("SSH %s be used.", @user_choices[:ssh] ? "should" : "should not")
|
45
|
+
puts "There are #{@user_choices[:connections]} connections."
|
46
|
+
pp @user_choices
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
if $0 == __FILE__
|
52
|
+
S4tUtils.with_pleasant_exceptions do
|
53
|
+
TutorialExample.new.execute
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-08-09.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
|
7
|
+
# See the tutorial for explanations.
|
8
|
+
|
9
|
+
### The following adjusts the load path so that the correct version of
|
10
|
+
### a self-contained package is found, no matter where the script is
|
11
|
+
### run from.
|
12
|
+
require 'pathname'
|
13
|
+
$:.unshift((Pathname.new(__FILE__).parent.parent.parent + 'lib').to_s)
|
14
|
+
require 's4t-utils/load-path-auto-adjuster'
|
15
|
+
|
16
|
+
require 'pp'
|
17
|
+
require 'user-choices'
|
18
|
+
|
19
|
+
class TutorialExample < UserChoices::Command
|
20
|
+
include UserChoices
|
21
|
+
|
22
|
+
def add_sources(builder)
|
23
|
+
builder.add_source(CommandLineSource, :usage,
|
24
|
+
"Usage: ruby #{$0} [options] file1 [file2]")
|
25
|
+
builder.add_source(EnvironmentSource, :with_prefix, "myprog_")
|
26
|
+
builder.add_source(YamlConfigFileSource, :from_file, ".myprog-config.yml")
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_choices(builder)
|
30
|
+
builder.add_choice(:connections, :type=>:integer, :default=>0) { | command_line |
|
31
|
+
command_line.uses_option("-c", "--connections COUNT",
|
32
|
+
"Number of connections to open.")
|
33
|
+
}
|
34
|
+
builder.add_choice(:ssh, :type=>:boolean, :default=>false) { | command_line |
|
35
|
+
command_line.uses_switch("-s", "--ssh",
|
36
|
+
"Use ssh to open connection.")
|
37
|
+
}
|
38
|
+
builder.add_choice(:files, :length => 1..2) { | command_line |
|
39
|
+
command_line.uses_arglist
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def execute
|
44
|
+
puts format("SSH %s be used.", @user_choices[:ssh] ? "should" : "should not")
|
45
|
+
puts "There are #{@user_choices[:connections]} connections."
|
46
|
+
pp @user_choices
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
if $0 == __FILE__
|
52
|
+
S4tUtils.with_pleasant_exceptions do
|
53
|
+
TutorialExample.new.execute
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-08-09.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
# See the tutorial for explanations.
|
7
|
+
|
8
|
+
### The following adjusts the load path so that the correct version of
|
9
|
+
### a self-contained package is found, no matter where the script is
|
10
|
+
### run from.
|
11
|
+
require 'pathname'
|
12
|
+
$:.unshift((Pathname.new(__FILE__).parent.parent.parent + 'lib').to_s)
|
13
|
+
require 's4t-utils/load-path-auto-adjuster'
|
14
|
+
|
15
|
+
require 'pp'
|
16
|
+
require 'user-choices'
|
17
|
+
|
18
|
+
class TutorialExample < UserChoices::Command
|
19
|
+
include UserChoices
|
20
|
+
|
21
|
+
def add_sources(builder)
|
22
|
+
builder.add_source(CommandLineSource, :usage,
|
23
|
+
"Usage: ruby #{$0} infile")
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_choices(builder)
|
27
|
+
builder.add_choice(:infile) { | command_line |
|
28
|
+
command_line.uses_arg
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute
|
33
|
+
pp @user_choices
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
if $0 == __FILE__
|
39
|
+
S4tUtils.with_pleasant_exceptions do
|
40
|
+
TutorialExample.new.execute
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-08-09.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
# See the tutorial for explanations.
|
7
|
+
|
8
|
+
### The following adjusts the load path so that the correct version of
|
9
|
+
### a self-contained package is found, no matter where the script is
|
10
|
+
### run from.
|
11
|
+
require 'pathname'
|
12
|
+
$:.unshift((Pathname.new(__FILE__).parent.parent.parent + 'lib').to_s)
|
13
|
+
require 's4t-utils/load-path-auto-adjuster'
|
14
|
+
|
15
|
+
require 'pp'
|
16
|
+
require 'user-choices'
|
17
|
+
|
18
|
+
class TutorialExample < UserChoices::Command
|
19
|
+
include UserChoices
|
20
|
+
|
21
|
+
def add_sources(builder)
|
22
|
+
builder.add_source(CommandLineSource, :usage,
|
23
|
+
"Usage: ruby #{$0} infile")
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_choices(builder)
|
27
|
+
builder.add_choice(:infile) { | command_line |
|
28
|
+
command_line.uses_optional_arg
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute
|
33
|
+
pp @user_choices
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
if $0 == __FILE__
|
39
|
+
S4tUtils.with_pleasant_exceptions do
|
40
|
+
TutorialExample.new.execute
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-08-09.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
# See the tutorial for explanations.
|
7
|
+
|
8
|
+
### The following adjusts the load path so that the correct version of
|
9
|
+
### a self-contained package is found, no matter where the script is
|
10
|
+
### run from.
|
11
|
+
require 'pathname'
|
12
|
+
$:.unshift((Pathname.new(__FILE__).parent.parent.parent + 'lib').to_s)
|
13
|
+
require 's4t-utils/load-path-auto-adjuster'
|
14
|
+
|
15
|
+
require 'pp'
|
16
|
+
require 'user-choices'
|
17
|
+
|
18
|
+
class TutorialExample < UserChoices::Command
|
19
|
+
include UserChoices
|
20
|
+
|
21
|
+
def add_sources(builder)
|
22
|
+
builder.add_source(CommandLineSource, :usage,
|
23
|
+
"Usage: ruby #{$0} infile outfile")
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_choices(builder)
|
27
|
+
builder.add_choice(:files, :length => 2) { | command_line |
|
28
|
+
command_line.uses_arglist
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def postprocess_user_choices
|
33
|
+
@user_choices[:infile] = @user_choices[:files][0]
|
34
|
+
@user_choices[:outfile] = @user_choices[:files][1]
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def execute
|
39
|
+
pp @user_choices
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
if $0 == __FILE__
|
45
|
+
S4tUtils.with_pleasant_exceptions do
|
46
|
+
TutorialExample.new.execute
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,178 @@
|
|
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 'user-choices/ruby-extensions'
|
7
|
+
|
8
|
+
module UserChoices # :nodoc:
|
9
|
+
|
10
|
+
# Arglists cause complications, mainly because a command's arglist is
|
11
|
+
# never optional. If you ever want it to be ignored, for example, you have to treat it
|
12
|
+
# specially. An AbstractArglistStrategy is a sequence of messages that can
|
13
|
+
# cope with those sort of complications. These messages are called at the
|
14
|
+
# appropriate time by a CommandLineSource.
|
15
|
+
#
|
16
|
+
# * <b>AbstractArglistStrategy#fill</b> takes the arglist and converts it to
|
17
|
+
# the value of some choice symbol. The name should remind you of AbstractSource#fill.
|
18
|
+
# * There may be conversions that make sense for values (for this choice symbol) when
|
19
|
+
# those values do <i>not</i> come from an arglist, but not when they do.
|
20
|
+
# <b>AbstractArglistStrategy#claim_conversions</b> squirrels them away to protect
|
21
|
+
# them from more generic processing. They are then specially processed by
|
22
|
+
# AbstractArglistStrategy#apply_claimed_conversions.
|
23
|
+
# * After conversions, there may still be work to do. There may be some special
|
24
|
+
# reconciling required to the entire collection of choices. (The final result
|
25
|
+
# may depend on what value the arglist provided and what value some other source
|
26
|
+
# provided.) <b>AbstractArglistStrategy#adjust</b> does that work.
|
27
|
+
class AbstractArglistStrategy # :nodoc:
|
28
|
+
|
29
|
+
attr_reader :choice
|
30
|
+
|
31
|
+
# A strategy applies an argument list named _choice_ that is a key
|
32
|
+
# in the <i>value_holder</i>. It's hackish, but don't give the _choice_ in
|
33
|
+
# the case where there should be no arglist (and thus no choice symbol to
|
34
|
+
# attach it to).
|
35
|
+
def initialize(value_holder, choice=nil)
|
36
|
+
@value_holder = value_holder
|
37
|
+
@choice = choice
|
38
|
+
end
|
39
|
+
|
40
|
+
# This method takes the argument list, an array, and puts it into
|
41
|
+
# the <code>value_holder</code>.
|
42
|
+
def fill(arglist); subclass_responsibility; end
|
43
|
+
|
44
|
+
# Given _conversions_map_, a list of Conversion, select which apply to the arglist,
|
45
|
+
# removing them from the hash.
|
46
|
+
def claim_conversions(conversions_map)
|
47
|
+
@claimed_conversions = []
|
48
|
+
end
|
49
|
+
|
50
|
+
# Apply the claimed conversions to the value previously stored in claim_conversions.
|
51
|
+
def apply_claimed_conversions
|
52
|
+
# None claimed by default
|
53
|
+
end
|
54
|
+
|
55
|
+
# Apply any effects of changes to the arglist to the result for all the choices.
|
56
|
+
def adjust(all_choices)
|
57
|
+
# By default, do nothing.
|
58
|
+
end
|
59
|
+
|
60
|
+
# public for testing.
|
61
|
+
def arglist_arity_error(length, arglist_arity) # :nodoc:
|
62
|
+
plural = length==1 ? '' : 's'
|
63
|
+
expected = case arglist_arity
|
64
|
+
when Integer
|
65
|
+
arglist_arity.to_s
|
66
|
+
when Range
|
67
|
+
if arglist_arity.end == arglist_arity.begin.succ
|
68
|
+
"#{arglist_arity.begin} or #{arglist_arity.end}"
|
69
|
+
else
|
70
|
+
arglist_arity.in_words
|
71
|
+
end
|
72
|
+
else
|
73
|
+
arglist_arity.inspect
|
74
|
+
end
|
75
|
+
"#{length} argument#{plural} given, #{expected} expected."
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def claim_length_check(conversions_map)
|
82
|
+
@length_check = conversions_map[@choice].find { |c| c.does_length_check? }
|
83
|
+
if @length_check
|
84
|
+
conversions_map[@choice].reject { |c| c.does_length_check? }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
# An AbstractArglistStrategy that rejects any non-empty arglist.
|
92
|
+
class NoArguments < AbstractArglistStrategy # :nodoc:
|
93
|
+
def fill(arglist)
|
94
|
+
user_claims(arglist.length == 0) do
|
95
|
+
"No arguments are allowed."
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# The arglist is to be treated as a list, possibly with a Conversion that
|
102
|
+
# limits its length. It defers processing of an empty arglist until the
|
103
|
+
# last possible moment and only does it if there's no other value for the
|
104
|
+
# choice symbol.
|
105
|
+
class ArbitraryArglist < AbstractArglistStrategy # :nodoc:
|
106
|
+
def fill(arglist)
|
107
|
+
@value_holder[@choice] = arglist unless arglist.empty?
|
108
|
+
end
|
109
|
+
|
110
|
+
def claim_conversions(conversions_map)
|
111
|
+
claim_length_check(conversions_map)
|
112
|
+
end
|
113
|
+
|
114
|
+
def apply_claimed_conversions
|
115
|
+
apply_length_check
|
116
|
+
end
|
117
|
+
|
118
|
+
def adjust(all_choices)
|
119
|
+
return if @value_holder[@choice]
|
120
|
+
return if all_choices.has_key?(@choice)
|
121
|
+
|
122
|
+
all_choices[@choice] = []
|
123
|
+
@value_holder[@choice] = all_choices[@choice]
|
124
|
+
apply_length_check
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def apply_length_check
|
130
|
+
return unless @length_check
|
131
|
+
return unless @value_holder[@choice]
|
132
|
+
|
133
|
+
value = @value_holder[@choice]
|
134
|
+
user_claims(@length_check.suitable?(value)) {
|
135
|
+
arglist_arity_error(value.length, @length_check.required_length)
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# General handling for cases where the Arglist isn't treated as a list, but
|
141
|
+
# rather as a single (possibly optional) element. Subclasses handle the
|
142
|
+
# optional/non-optional case.
|
143
|
+
class NonListStrategy < AbstractArglistStrategy # :nodoc:
|
144
|
+
def arity; subclass_responsibility; end
|
145
|
+
|
146
|
+
def fill(arglist)
|
147
|
+
case arglist.length
|
148
|
+
when 0: # This is not considered an error because another source
|
149
|
+
# might fill in the value.
|
150
|
+
when 1: @value_holder[@choice] = arglist[0]
|
151
|
+
else user_is_bewildered(arglist_arity_error(arglist.length, self.arity))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def claim_conversions(conversions_map)
|
156
|
+
claim_length_check(conversions_map)
|
157
|
+
user_denies(@length_check) {
|
158
|
+
"Don't specify the length of an argument list when it's not treated as an array."
|
159
|
+
}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
class OneRequiredArg < NonListStrategy # :nodoc:
|
165
|
+
def arity; 1; end
|
166
|
+
|
167
|
+
def adjust(all_choices)
|
168
|
+
return if all_choices.has_key?(@choice)
|
169
|
+
user_is_bewildered(arglist_arity_error(0,1))
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
class OneOptionalArg < NonListStrategy # :nodoc:
|
175
|
+
def arity; 0..1; end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 's4t-utils'
|
2
|
+
include S4tUtils
|
3
|
+
require 'enumerator'
|
4
|
+
|
5
|
+
require 'user-choices/conversions'
|
6
|
+
require 'user-choices/sources'
|
7
|
+
|
8
|
+
module UserChoices
|
9
|
+
|
10
|
+
# This class accepts a series of source and choice descriptions
|
11
|
+
# and then builds a hash-like object that describes all the choices
|
12
|
+
# a user has made before (or while) invoking a script.
|
13
|
+
class ChoicesBuilder
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@defaults = {}
|
17
|
+
@conversions = {}
|
18
|
+
@sources = []
|
19
|
+
end
|
20
|
+
|
21
|
+
# Add the choice named _choice_, a symbol. _args_ is a keyword
|
22
|
+
# argument:
|
23
|
+
# * <tt>:default</tt> takes a value that is the default value of the _choice_.
|
24
|
+
# * <tt>:type</tt> can be given an array of valid string values. These are
|
25
|
+
# checked.
|
26
|
+
# * <tt>:type</tt> can also be given <tt>:integer</tt>. The value is cast into
|
27
|
+
# an integer. If that's impossible, an exception is raised.
|
28
|
+
# * <tt>:type</tt> can also be given <tt>:boolean</tt>. The value is converted into
|
29
|
+
# +true+ or +false+ (or an exception is raised).
|
30
|
+
# * <tt>:type</tt> can also be given <tt>[:string]</tt>. The value
|
31
|
+
# will be an array of strings. For example, "--value a,b,c" will
|
32
|
+
# produce ['a', 'b', 'c'].
|
33
|
+
#
|
34
|
+
# You might also give <tt>:length => 5</tt> or <tt>:length => 3..4</tt>. (In
|
35
|
+
# this case, a <tt>:type</tt> of <tt>[:string]</tt> is assumed.)
|
36
|
+
#
|
37
|
+
# The _block_ is passed a CommandLineSource object. It's used
|
38
|
+
# to describe the command line.
|
39
|
+
def add_choice(choice, args={}, &block)
|
40
|
+
# TODO: does the has_key? actually make a difference?
|
41
|
+
@defaults[choice] = args[:default] if args.has_key?(:default)
|
42
|
+
@conversions[choice] = []
|
43
|
+
Conversion.record_for(args[:type], @conversions[choice])
|
44
|
+
if args.has_key?(:length)
|
45
|
+
Conversion.record_for({:length => args[:length]}, @conversions[choice])
|
46
|
+
end
|
47
|
+
block.call(ArgForwarder.new(@command_line_source, choice)) if block
|
48
|
+
end
|
49
|
+
|
50
|
+
# This adds a source of choices. The _source_ is a class like
|
51
|
+
# CommandLineSource. The <tt>messages_and_args</tt> are sent
|
52
|
+
# to a new object of that class.
|
53
|
+
def add_source(source_class, *messages_and_args)
|
54
|
+
source = source_class.new
|
55
|
+
message_sends(messages_and_args).each { | send_me | source.send(*send_me) }
|
56
|
+
@sources << source
|
57
|
+
@command_line_source = source if source_class == CommandLineSource
|
58
|
+
end
|
59
|
+
|
60
|
+
# Once sources and choices have been described, this builds and
|
61
|
+
# returns a hash-like object indexed by the choices.
|
62
|
+
def build
|
63
|
+
retval = {}
|
64
|
+
@sources << DefaultSource.new.use_hash(@defaults)
|
65
|
+
@sources.each { |s| s.fill }
|
66
|
+
@sources.each { |s| s.apply(@conversions) }
|
67
|
+
@sources.reverse.each { |s| retval.merge!(s) }
|
68
|
+
@sources.each { |s| s.adjust(retval) }
|
69
|
+
retval
|
70
|
+
end
|
71
|
+
|
72
|
+
# Public for testing.
|
73
|
+
|
74
|
+
def message_sends(messages_and_args) # :nodoc:
|
75
|
+
where_at = symbol_indices(messages_and_args)
|
76
|
+
where_end = where_at[1..-1] + [messages_and_args.length]
|
77
|
+
where_at.to_enum(:each_with_index).collect do |start, where_end_index |
|
78
|
+
messages_and_args[start...where_end[where_end_index]]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def symbol_indices(array) # :nodoc:
|
83
|
+
array.to_enum(:each_with_index).collect do |obj, index|
|
84
|
+
index if obj.is_a?(Symbol)
|
85
|
+
end.compact
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|