user-choices 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,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