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,41 @@
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
+ require 'pp'
9
+ require 'user-choices'
10
+
11
+ # See the tutorial for explanations.
12
+
13
+ class TutorialExample < UserChoices::Command
14
+ include UserChoices
15
+
16
+ def add_sources(builder)
17
+ builder.add_source(CommandLineSource, :usage,
18
+ "Usage: ruby #{$0} [options]")
19
+ builder.add_source(EnvironmentSource, :with_prefix, "myprog_")
20
+ builder.add_source(YamlConfigFileSource, :from_file, ".myprog-config.yml")
21
+ end
22
+
23
+ def add_choices(builder)
24
+ builder.add_choice(:connections, :type=>:integer, :default=>0) { | command_line |
25
+ command_line.uses_option("-c", "--connections COUNT",
26
+ "Number of connections to open.")
27
+ }
28
+ end
29
+
30
+ def execute
31
+ puts "There are #{@user_choices[:connections]} connections."
32
+ pp @user_choices
33
+ end
34
+ end
35
+
36
+
37
+ if $0 == __FILE__
38
+ S4tUtils.with_pleasant_exceptions do
39
+ TutorialExample.new.execute
40
+ end
41
+ end
@@ -0,0 +1,44 @@
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
+ require 'pp'
9
+ require 'user-choices'
10
+
11
+ class TutorialExample < UserChoices::Command
12
+ include UserChoices
13
+
14
+ def add_sources(builder)
15
+ builder.add_source(CommandLineSource, :usage,
16
+ "Usage: ruby #{$0} [options] file1 [file2]")
17
+ builder.add_source(EnvironmentSource, :with_prefix, "myprog_")
18
+ builder.add_source(YamlConfigFileSource, :from_file, ".myprog-config.yml")
19
+ end
20
+
21
+ def add_choices(builder)
22
+ builder.add_choice(:connections, :type=>:integer, :default=>0) { | command_line |
23
+ command_line.uses_option("-c", "--connections COUNT",
24
+ "Number of connections to open.")
25
+ }
26
+ builder.add_choice(:ssh, :type=>:boolean, :default=>false) { | command_line |
27
+ command_line.uses_switch("-s", "--ssh",
28
+ "Use ssh to open connection.")
29
+ }
30
+ end
31
+
32
+ def execute
33
+ puts format("SSH %s be used.", @user_choices[:ssh] ? "should" : "should not")
34
+ puts "There are #{@user_choices[:connections]} connections."
35
+ pp @user_choices
36
+ end
37
+ end
38
+
39
+
40
+ if $0 == __FILE__
41
+ S4tUtils.with_pleasant_exceptions do
42
+ TutorialExample.new.execute
43
+ end
44
+ end
@@ -0,0 +1,47 @@
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
+ require 'pp'
9
+ require 'user-choices'
10
+
11
+ class TutorialExample < UserChoices::Command
12
+ include UserChoices
13
+
14
+ def add_sources(builder)
15
+ builder.add_source(CommandLineSource, :usage,
16
+ "Usage: ruby #{$0} [options] file1 [file2]")
17
+ builder.add_source(EnvironmentSource, :with_prefix, "myprog_")
18
+ builder.add_source(YamlConfigFileSource, :from_file, ".myprog-config.yml")
19
+ end
20
+
21
+ def add_choices(builder)
22
+ builder.add_choice(:connections, :type=>:integer, :default=>0) { | command_line |
23
+ command_line.uses_option("-c", "--connections COUNT",
24
+ "Number of connections to open.")
25
+ }
26
+ builder.add_choice(:ssh, :type=>:boolean, :default=>false) { | command_line |
27
+ command_line.uses_switch("-s", "--ssh",
28
+ "Use ssh to open connection.")
29
+ }
30
+ builder.add_choice(:files) { | command_line |
31
+ command_line.uses_arglist
32
+ }
33
+ end
34
+
35
+ def execute
36
+ puts format("SSH %s be used.", @user_choices[:ssh] ? "should" : "should not")
37
+ puts "There are #{@user_choices[:connections]} connections."
38
+ pp @user_choices
39
+ end
40
+ end
41
+
42
+
43
+ if $0 == __FILE__
44
+ S4tUtils.with_pleasant_exceptions do
45
+ TutorialExample.new.execute
46
+ end
47
+ end
@@ -0,0 +1,47 @@
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
+ require 'pp'
9
+ require 'user-choices'
10
+
11
+ class TutorialExample < UserChoices::Command
12
+ include UserChoices
13
+
14
+ def add_sources(builder)
15
+ builder.add_source(CommandLineSource, :usage,
16
+ "Usage: ruby #{$0} [options] file1 [file2]")
17
+ builder.add_source(EnvironmentSource, :with_prefix, "myprog_")
18
+ builder.add_source(YamlConfigFileSource, :from_file, ".myprog-config.yml")
19
+ end
20
+
21
+ def add_choices(builder)
22
+ builder.add_choice(:connections, :type=>:integer, :default=>0) { | command_line |
23
+ command_line.uses_option("-c", "--connections COUNT",
24
+ "Number of connections to open.")
25
+ }
26
+ builder.add_choice(:ssh, :type=>:boolean, :default=>false) { | command_line |
27
+ command_line.uses_switch("-s", "--ssh",
28
+ "Use ssh to open connection.")
29
+ }
30
+ builder.add_choice(:files, :length => 1..2) { | command_line |
31
+ command_line.uses_arglist
32
+ }
33
+ end
34
+
35
+ def execute
36
+ puts format("SSH %s be used.", @user_choices[:ssh] ? "should" : "should not")
37
+ puts "There are #{@user_choices[:connections]} connections."
38
+ pp @user_choices
39
+ end
40
+ end
41
+
42
+
43
+ if $0 == __FILE__
44
+ S4tUtils.with_pleasant_exceptions do
45
+ TutorialExample.new.execute
46
+ end
47
+ end
@@ -0,0 +1,35 @@
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
+ require 'pp'
9
+ require 'user-choices'
10
+
11
+ class TutorialExample < UserChoices::Command
12
+ include UserChoices
13
+
14
+ def add_sources(builder)
15
+ builder.add_source(CommandLineSource, :usage,
16
+ "Usage: ruby #{$0} infile")
17
+ end
18
+
19
+ def add_choices(builder)
20
+ builder.add_choice(:infile) { | command_line |
21
+ command_line.uses_arg
22
+ }
23
+ end
24
+
25
+ def execute
26
+ pp @user_choices
27
+ end
28
+ end
29
+
30
+
31
+ if $0 == __FILE__
32
+ S4tUtils.with_pleasant_exceptions do
33
+ TutorialExample.new.execute
34
+ end
35
+ end
@@ -0,0 +1,35 @@
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
+ require 'pp'
9
+ require 'user-choices'
10
+
11
+ class TutorialExample < UserChoices::Command
12
+ include UserChoices
13
+
14
+ def add_sources(builder)
15
+ builder.add_source(CommandLineSource, :usage,
16
+ "Usage: ruby #{$0} infile")
17
+ end
18
+
19
+ def add_choices(builder)
20
+ builder.add_choice(:infile) { | command_line |
21
+ command_line.uses_optional_arg
22
+ }
23
+ end
24
+
25
+ def execute
26
+ pp @user_choices
27
+ end
28
+ end
29
+
30
+
31
+ if $0 == __FILE__
32
+ S4tUtils.with_pleasant_exceptions do
33
+ TutorialExample.new.execute
34
+ end
35
+ end
@@ -0,0 +1,41 @@
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
+ require 'pp'
9
+ require 'user-choices'
10
+
11
+ class TutorialExample < UserChoices::Command
12
+ include UserChoices
13
+
14
+ def add_sources(builder)
15
+ builder.add_source(CommandLineSource, :usage,
16
+ "Usage: ruby #{$0} infile outfile")
17
+ end
18
+
19
+ def add_choices(builder)
20
+ builder.add_choice(:files, :length => 2) { | command_line |
21
+ command_line.uses_arglist
22
+ }
23
+ end
24
+
25
+ def postprocess_user_choices
26
+ @user_choices[:infile] = @user_choices[:files][0]
27
+ @user_choices[:outfile] = @user_choices[:files][1]
28
+ end
29
+
30
+
31
+ def execute
32
+ pp @user_choices
33
+ end
34
+ end
35
+
36
+
37
+ if $0 == __FILE__
38
+ S4tUtils.with_pleasant_exceptions do
39
+ TutorialExample.new.execute
40
+ end
41
+ end
@@ -0,0 +1,131 @@
1
+ require 'user-choices/arglist-strategies'
2
+ require 'user-choices/builder'
3
+ require 'user-choices/command'
4
+ require 'user-choices/command-line-source'
5
+ require "user-choices/conversions"
6
+ require "user-choices/ruby-extensions"
7
+ require 'user-choices/sources'
8
+ require 'user-choices/version'
9
+
10
+ =begin rdoc
11
+
12
+ UserChoices provides a unified interface to more than one source of
13
+ user choices: the command line, environment variables, configuration
14
+ files, and the choice to use program defaults. A typical usage defines allowable choices
15
+ within the framework of a Command object:
16
+
17
+ class Example < Command
18
+
19
+ # The sources are the various places in which the user can
20
+ # describe her choices to the program.
21
+
22
+ def add_sources(builder)
23
+ builder.add_source(...)
24
+ ...
25
+ end
26
+
27
+ # Each individual choice is named with a symbol that is common
28
+ # to all sources.
29
+ def add_choices(builder)
30
+ builder.add_choice(:choice, ...) { | command_line | ... }
31
+ end
32
+
33
+ # Immediately after recording the choices, the program can
34
+ # add new (derived ones) or do any other once-per-program
35
+ # initialization.
36
+ def postprocess_user_choices
37
+ ... @user_choices ...
38
+ end
39
+
40
+ # Perform the command.
41
+ def execute
42
+ ...
43
+ end
44
+ end
45
+
46
+ ...
47
+ CommandLineExample.new.execute
48
+ ...
49
+
50
+ = Describing sources
51
+
52
+ Sources are described by ChoicesBuilder#add_source.
53
+
54
+ EnvironmentSource describes the use of environment variables as sources. The following says that all environment variables beginning with "amazon_" are choices about this program.
55
+
56
+ builder.add_source(EnvironmentSource, :with_prefix, "amazon_")
57
+
58
+ XmlConfigFileSource points to a configuration file with choices.
59
+
60
+ builder.add_source(XmlConfigFileSource, :from_file, "ms-config.xml")
61
+
62
+ CommandLineSource uses the command line options and
63
+ arguments as a source of choices. The following gives the usage line
64
+ for the script:
65
+
66
+ builder.add_source(CommandLineSource, :usage,
67
+ "Usage ruby #{$0} [options] names...")
68
+
69
+ = Describing choices
70
+
71
+ The end result of the process is a hash mapping choices to chosen
72
+ values. Choices are named by symbols. They are described by
73
+ ChoicesBuilder#add_choice. Here are simple examples that
74
+ don't involve the command line.
75
+
76
+ The first just names a choice.
77
+
78
+ builder.add_choice(:ordinary_choice)
79
+
80
+ The second gives a default value:
81
+
82
+ builder.add_choice(:ordinary_choice,
83
+ :default => "eaargh")
84
+
85
+ The second gives a default value and a type. The type is used to check the value and, if appropriate, to convert the value away from a string. Note that the default is always a string regardless of the type.
86
+
87
+ builder.add_choice(:on,
88
+ :default => "false",
89
+ :type => :boolean)
90
+
91
+ = Command line options
92
+
93
+ ChoicesBuilder#add_choice passes a
94
+ CommandLineSource object to a block. That can be used to
95
+ describe the command line. The syntax is the same as OptionParser.
96
+
97
+ In the following, <tt>ordinary_choice</tt> can be specified with either the <tt>-o</tt> or <tt>--ordinary-choice</tt> options. The strings also appear in help messages (automatically produced from <tt>ruby script --help</tt>).
98
+
99
+ builder.add_choice(:ordinary_choice,
100
+ :default => 'default') { | command_line |
101
+ command_line.uses_option("-o", "--ordinary-choice CHOICE",
102
+ "CHOICE can be any string.")
103
+ }
104
+
105
+ The command line's argument list (everything that's not an option) can
106
+ be bundled up into another choice. Here, the arguments become an array
107
+ named by <tt>:names</tt>:
108
+
109
+ builder.add_choice(:names) { | command_line |
110
+ command_line.uses_arglist
111
+ }
112
+
113
+ = Using choices
114
+
115
+ Most often, choices are used within the context of a Command object. They are stored in a hash named by instance variable <tt>@user_choices</tt> (or accessor +user_choices+).
116
+
117
+ class AffinityTripCommand < Command
118
+ ...
119
+ def execute
120
+ starting_url = @strategy.url_for(self.user_choices[:isbn])
121
+ take_trip(starting_url, self.user_choices[:trip_steps])
122
+ end
123
+
124
+ You can construct the hash directly with ChoicesBuilder#build. That's
125
+ not needed or used when using the Command object.
126
+
127
+ =end
128
+
129
+ module UserChoices
130
+ end
131
+
@@ -0,0 +1,179 @@
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
149
+ # This is not considered an error because another source might fill in the value.
150
+ when 1
151
+ @value_holder[@choice] = arglist[0]
152
+ else user_is_bewildered(arglist_arity_error(arglist.length, self.arity))
153
+ end
154
+ end
155
+
156
+ def claim_conversions(conversions_map)
157
+ claim_length_check(conversions_map)
158
+ user_denies(@length_check) {
159
+ "Don't specify the length of an argument list when it's not treated as an array."
160
+ }
161
+ end
162
+ end
163
+
164
+
165
+ class OneRequiredArg < NonListStrategy # :nodoc:
166
+ def arity; 1; end
167
+
168
+ def adjust(all_choices)
169
+ return if all_choices.has_key?(@choice)
170
+ user_is_bewildered(arglist_arity_error(0,1))
171
+ end
172
+
173
+ end
174
+
175
+ class OneOptionalArg < NonListStrategy # :nodoc:
176
+ def arity; 0..1; end
177
+ end
178
+
179
+ end