peeek 1.0.1 → 1.0.2
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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/Rakefile +6 -1
- data/bin/peeek +6 -14
- data/lib/peeek.rb +10 -9
- data/lib/peeek/cli.rb +99 -0
- data/lib/peeek/cli/options.rb +212 -0
- data/lib/peeek/hook.rb +1 -1
- data/lib/peeek/hook/linker.rb +1 -0
- data/lib/peeek/hook/specifier.rb +99 -0
- data/lib/peeek/version.rb +1 -1
- data/peeek.gemspec +0 -1
- data/spec/peeek/call_spec.rb +27 -48
- data/spec/peeek/cli/options_spec.rb +331 -0
- data/spec/peeek/cli_sample.rb +2 -0
- data/spec/peeek/cli_spec.rb +254 -0
- data/spec/peeek/hook/specifier_spec.rb +107 -0
- data/spec/peeek_spec.rb +1 -1
- metadata +21 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90ae3ee8f84b1d47140d8ebd28943835875550be
|
4
|
+
data.tar.gz: 8a7bdc5de0df596be2e309ee950112e89ff94166
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6ffdc7048653dc766511b66272c730a1f37ac04a1aa7efe35efa4a834916f758d5766ad5c07da75954cb5a63c4a067788014514ca2edcd94756654f5b13dc75
|
7
|
+
data.tar.gz: d32b0ca28b0520cf773aa1f1f22c678168cdf6211dcac657796d1dc73fd85819db714df49e7e2ca8c4a9cd8fa78b1a62f80818afbddd3fb9c786c44649ef55e3
|
data/.travis.yml
ADDED
data/Rakefile
CHANGED
data/bin/peeek
CHANGED
@@ -1,16 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require '
|
3
|
-
require 'peeek'
|
2
|
+
require 'peeek/cli'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
calls = begin
|
11
|
-
Peeek.capture(String => :%) { load path } # want to be you can specify
|
12
|
-
ensure
|
13
|
-
$stdout = original_stdout
|
14
|
-
end
|
15
|
-
|
16
|
-
puts calls
|
4
|
+
begin
|
5
|
+
puts Peeek::CLI.new($stdin, $stdout, ARGV).run(binding)
|
6
|
+
rescue => e
|
7
|
+
puts "peeek: #{e.message} (#{e.class})"
|
8
|
+
end
|
data/lib/peeek.rb
CHANGED
@@ -7,12 +7,14 @@ require 'peeek/calls'
|
|
7
7
|
class Peeek
|
8
8
|
|
9
9
|
# @attribute [r] global
|
10
|
+
# @scope class
|
10
11
|
# @return [Peeek] the global Peeek object
|
11
12
|
def self.global
|
12
13
|
@global ||= new
|
13
14
|
end
|
14
15
|
|
15
16
|
# @attribute [r] current
|
17
|
+
# @scope class
|
16
18
|
# @return [Peeek] the current Peeek object
|
17
19
|
#
|
18
20
|
# @see Peeek.local
|
@@ -44,16 +46,15 @@ class Peeek
|
|
44
46
|
|
45
47
|
# Capture all calls to hook targets.
|
46
48
|
#
|
47
|
-
# @param [Hash{Module, Class, Object => String, Array<String>, Symbol, Array<Symbol>}]
|
48
|
-
#
|
49
|
-
# target of hook
|
49
|
+
# @param [Hash{Module, Class, Object => String, Array<String>, Symbol, Array<Symbol>}] hook_targets
|
50
|
+
# an object and method specifier(s) that be target of hook
|
50
51
|
# @yield any process that want to run to capture
|
51
52
|
# @return [Peeek::Calls] calls that were captured in the block
|
52
|
-
def self.capture(
|
53
|
+
def self.capture(hook_targets)
|
53
54
|
raise ArgumentError, 'block not supplied' unless block_given?
|
54
55
|
|
55
56
|
local do
|
56
|
-
|
57
|
+
hook_targets.each { |object, method_specs| current.hook(object, *method_specs) }
|
57
58
|
yield
|
58
59
|
current.calls
|
59
60
|
end
|
@@ -80,8 +81,8 @@ class Peeek
|
|
80
81
|
# Register a hook to methods of an object.
|
81
82
|
#
|
82
83
|
# @param [Module, Class, Object] object a target object that hook
|
83
|
-
# @param [Array<String>, Array<Symbol>] method_specs method
|
84
|
-
#
|
84
|
+
# @param [Array<String>, Array<Symbol>] method_specs method specifiers of the
|
85
|
+
# object. see also examples of {Peeek::Hook.create}
|
85
86
|
# @yield [call] process a call to the methods. give optionally
|
86
87
|
# @yieldparam [Peeek::Call] call a call to the methods
|
87
88
|
# @return [Peeek::Hooks] registered hooks at calling
|
@@ -130,8 +131,8 @@ class Peeek
|
|
130
131
|
|
131
132
|
# Register a hook to methods of self to the current Peeek object.
|
132
133
|
#
|
133
|
-
# @param [Array<String>, Array<Symbol>] method_specs method
|
134
|
-
#
|
134
|
+
# @param [Array<String>, Array<Symbol>] method_specs method specifiers of
|
135
|
+
# the object. see also examples of {Peeek::Hook.create}
|
135
136
|
# @yield [call] process a call to the methods. give optionally
|
136
137
|
# @yieldparam [Peeek::Call] call a call to the methods
|
137
138
|
#
|
data/lib/peeek/cli.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'peeek/cli/options'
|
2
|
+
require 'peeek'
|
3
|
+
|
4
|
+
class Peeek
|
5
|
+
class CLI
|
6
|
+
|
7
|
+
# Initialize the CLI object.
|
8
|
+
#
|
9
|
+
# @param [IO] input source to input
|
10
|
+
# @param [IO] output destination to output the execution result
|
11
|
+
# @param [Array<String>] argv arguments that is given from command line
|
12
|
+
def initialize(input, output, argv)
|
13
|
+
@input = input
|
14
|
+
@output = output
|
15
|
+
@options = Options.new(argv)
|
16
|
+
rescue Help => e
|
17
|
+
output.puts(e.message)
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
|
21
|
+
# @attribute [r] options
|
22
|
+
# @return [Peeek::CLI::Options] options to run from CLI
|
23
|
+
attr_reader :options
|
24
|
+
|
25
|
+
# Run the command or the program file with a binding. And capture calls
|
26
|
+
# that raised when running it.
|
27
|
+
#
|
28
|
+
# @param [Binding] binding context that runs the command or the program file
|
29
|
+
# @return [Peeek::Calls] captured calls
|
30
|
+
def run(binding)
|
31
|
+
if Options.encoding_options_enabled?
|
32
|
+
Encoding.default_external = @options.external_encoding
|
33
|
+
Encoding.default_internal = @options.internal_encoding
|
34
|
+
end
|
35
|
+
|
36
|
+
$DEBUG = @options.debug
|
37
|
+
$VERBOSE = @options.verbose
|
38
|
+
|
39
|
+
Dir.chdir(@options.working_directory) if @options.working_directory
|
40
|
+
$LOAD_PATH.unshift(*@options.directories_to_load)
|
41
|
+
@options.required_libraries.each(&method(:require))
|
42
|
+
|
43
|
+
hook_targets = materialize_hook_targets(binding)
|
44
|
+
process = setup_to_execute(binding)
|
45
|
+
|
46
|
+
@output.puts("peeek-#{VERSION}") if @options.version_requested?
|
47
|
+
|
48
|
+
original_stdout = $stdout
|
49
|
+
$stdout = @output
|
50
|
+
|
51
|
+
begin
|
52
|
+
Peeek.capture(hook_targets, &process)
|
53
|
+
rescue => e
|
54
|
+
e.set_backtrace(e.backtrace.reject { |line| line =~ %r{lib/peeek} })
|
55
|
+
raise e
|
56
|
+
ensure
|
57
|
+
$stdout = original_stdout
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def materialize_hook_targets(binding)
|
64
|
+
hook_targets = @options.hook_targets.inject({}) do |hook_targets, hook_spec|
|
65
|
+
hook_targets[hook_spec.object_name] ||= []
|
66
|
+
hook_targets[hook_spec.object_name] << hook_spec.method_specifier
|
67
|
+
hook_targets
|
68
|
+
end
|
69
|
+
|
70
|
+
hook_targets = hook_targets.map do |object_name, method_specs|
|
71
|
+
type = binding.eval("defined? #{object_name}")
|
72
|
+
raise "#{object_name} is undefined" if type.nil?
|
73
|
+
raise "#{object_name} isn't a constant or a global variable" unless type == 'constant' || type == 'global-variable'
|
74
|
+
[binding.eval(object_name), method_specs]
|
75
|
+
end
|
76
|
+
|
77
|
+
Hash[hook_targets]
|
78
|
+
end
|
79
|
+
|
80
|
+
def setup_to_execute(binding)
|
81
|
+
if @options.command_given? and @options.continued?
|
82
|
+
process_for { binding.eval(@options.command, '-e', 1) }
|
83
|
+
elsif @options.arguments_given? and @options.continued?
|
84
|
+
path, *argv = @options.arguments
|
85
|
+
process_for(argv) { load path }
|
86
|
+
elsif @options.version_requested?
|
87
|
+
process_for { }
|
88
|
+
else
|
89
|
+
process_for { binding.eval(@input.read, '-', 1) }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_for(argv = @options.arguments.dup, &process)
|
94
|
+
ARGV[0, ARGV.length] = argv
|
95
|
+
process
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'stringio'
|
3
|
+
require 'peeek/hook/specifier'
|
4
|
+
|
5
|
+
class Peeek
|
6
|
+
class CLI
|
7
|
+
class Help < StandardError; end
|
8
|
+
|
9
|
+
module EncodingOptions
|
10
|
+
|
11
|
+
# @attribute external_encoding
|
12
|
+
# @return [Encoding] external character encoding
|
13
|
+
attr_reader :external_encoding
|
14
|
+
|
15
|
+
def external_encoding=(encoding)
|
16
|
+
@external_encoding = Encoding.find(encoding)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @attribute internal_encoding
|
20
|
+
# @return [Encoding] internal character encoding
|
21
|
+
attr_reader :internal_encoding
|
22
|
+
|
23
|
+
def internal_encoding=(encoding)
|
24
|
+
@internal_encoding = Encoding.find(encoding)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class Options
|
30
|
+
|
31
|
+
SILENCE = 0
|
32
|
+
MEDIUM = 1
|
33
|
+
VERBOSE = 2
|
34
|
+
|
35
|
+
include EncodingOptions if defined? Encoding
|
36
|
+
|
37
|
+
# Determine if CLI options class has enable encoding options.
|
38
|
+
#
|
39
|
+
# @return whether CLI options class has enable encoding options
|
40
|
+
def self.encoding_options_enabled?
|
41
|
+
include?(EncodingOptions)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Initialize the CLI options.
|
45
|
+
#
|
46
|
+
# @param [Array<String>] argv arguments that is given from command line
|
47
|
+
def initialize(argv = [])
|
48
|
+
@debug = $DEBUG
|
49
|
+
@verbose = $VERBOSE
|
50
|
+
@version_requested = false
|
51
|
+
|
52
|
+
opt = OptionParser.new
|
53
|
+
opt.banner = 'Usage: peeek [switches] [--] [programfile] [arguments]'
|
54
|
+
opt.summary_indent = ' ' * 2
|
55
|
+
opt.summary_width = 15
|
56
|
+
|
57
|
+
@working_directory = nil
|
58
|
+
|
59
|
+
opt.on('-Cdirectory', 'cd to directory before executing your script') do |directory|
|
60
|
+
@working_directory = directory
|
61
|
+
end
|
62
|
+
|
63
|
+
opt.on('-d', '--debug', 'set debugging flags (set $DEBUG to true)') do
|
64
|
+
@debug = true
|
65
|
+
@verbose = verbose_by(VERBOSE)
|
66
|
+
end
|
67
|
+
|
68
|
+
commands = []
|
69
|
+
|
70
|
+
opt.on("-e 'command'", "one line of script. Several -e's allowed. Omit [programfile]") do |command|
|
71
|
+
commands << command
|
72
|
+
end
|
73
|
+
|
74
|
+
if self.class.encoding_options_enabled?
|
75
|
+
@external_encoding = Encoding.default_external
|
76
|
+
@internal_encoding = Encoding.default_internal
|
77
|
+
|
78
|
+
opt.on('-Eex[:in]', '--encoding=ex[:in]', 'specify the default external and internal character encodings') do |encodings|
|
79
|
+
external_encoding, internal_encoding = parse_encodings(encodings)
|
80
|
+
@external_encoding = external_encoding if external_encoding
|
81
|
+
@internal_encoding = internal_encoding
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@hook_targets = []
|
86
|
+
|
87
|
+
opt.on('-Hstring', 'object and method name that is target of hook') do |string|
|
88
|
+
hook_spec = Hook::Specifier.parse(string)
|
89
|
+
@hook_targets << hook_spec unless @hook_targets.include?(hook_spec)
|
90
|
+
end
|
91
|
+
|
92
|
+
@directories_to_load = []
|
93
|
+
|
94
|
+
opt.on('-Idirectory', 'specify $LOAD_PATH directory (may be used more than once)') do |directory|
|
95
|
+
@directories_to_load << directory unless @directories_to_load.include?(directory)
|
96
|
+
end
|
97
|
+
|
98
|
+
@required_libraries = []
|
99
|
+
|
100
|
+
opt.on('-rlibrary', 'require the library before executing your script') do |library|
|
101
|
+
@required_libraries << library unless @required_libraries.include?(library)
|
102
|
+
end
|
103
|
+
|
104
|
+
opt.on('-v', 'print version number, then turn on verbose mode') do
|
105
|
+
@version_requested = true
|
106
|
+
@verbose = verbose_by(VERBOSE)
|
107
|
+
end
|
108
|
+
|
109
|
+
opt.on('-w', '--verbose', 'turn warnings on for your script') do
|
110
|
+
@verbose = verbose_by(VERBOSE)
|
111
|
+
end
|
112
|
+
|
113
|
+
level_strings = [:SILENCE, :MEDIUM, :VERBOSE].map do |const_name|
|
114
|
+
"#{self.class.const_get(const_name)}=#{const_name.to_s.downcase}"
|
115
|
+
end
|
116
|
+
|
117
|
+
opt.on("-W[level=#{VERBOSE}]", "set warning level; #{level_strings * ', '}", Integer) do |level|
|
118
|
+
@verbose = verbose_by(level || VERBOSE)
|
119
|
+
end
|
120
|
+
|
121
|
+
@continued = true
|
122
|
+
|
123
|
+
opt.on('--version', 'print the version') do
|
124
|
+
@version_requested = true
|
125
|
+
@continued = false
|
126
|
+
end
|
127
|
+
|
128
|
+
opt.on_tail('-h', '--help', 'show this message') do
|
129
|
+
raise Help, opt.help
|
130
|
+
end
|
131
|
+
|
132
|
+
@arguments = opt.order(argv)
|
133
|
+
@command = commands * '; '
|
134
|
+
end
|
135
|
+
|
136
|
+
# @attribute debug
|
137
|
+
# @return [Boolean] debugging flags
|
138
|
+
attr_accessor :debug
|
139
|
+
|
140
|
+
# @attribute verbose
|
141
|
+
# @return [Boolean, nil] verbose mode
|
142
|
+
attr_accessor :verbose
|
143
|
+
|
144
|
+
# @attribute working_directory
|
145
|
+
# @return [String] current directory at executing
|
146
|
+
attr_accessor :working_directory
|
147
|
+
|
148
|
+
# @attribute directories_to_load
|
149
|
+
# @return [Array<String>] directories that adds to $LOAD_PATH
|
150
|
+
attr_accessor :directories_to_load
|
151
|
+
|
152
|
+
# @attribute required_libraries
|
153
|
+
# @return [Array<String>] libraries to require
|
154
|
+
attr_accessor :required_libraries
|
155
|
+
|
156
|
+
# @attribute hook_targets
|
157
|
+
# @return [Array<Peeek::Hook::Specifier>] targets to hook
|
158
|
+
attr_accessor :hook_targets
|
159
|
+
|
160
|
+
# @attribute command
|
161
|
+
# @return [String] Ruby code to execute
|
162
|
+
attr_accessor :command
|
163
|
+
|
164
|
+
# @attribute arguments
|
165
|
+
# @return [Array<String>] arguments at executing
|
166
|
+
attr_accessor :arguments
|
167
|
+
|
168
|
+
# Determine if print of the version requested.
|
169
|
+
#
|
170
|
+
# @return whether print of the version requested
|
171
|
+
def version_requested?
|
172
|
+
@version_requested
|
173
|
+
end
|
174
|
+
|
175
|
+
# Determine if a command given.
|
176
|
+
#
|
177
|
+
# @return whether a command given
|
178
|
+
def command_given?
|
179
|
+
!@command.empty?
|
180
|
+
end
|
181
|
+
|
182
|
+
# Determine if arguments given.
|
183
|
+
#
|
184
|
+
# @return whether arguments given
|
185
|
+
def arguments_given?
|
186
|
+
!@arguments.empty?
|
187
|
+
end
|
188
|
+
|
189
|
+
# Determine if continue process.
|
190
|
+
#
|
191
|
+
# @return whether continue process
|
192
|
+
def continued?
|
193
|
+
@continued
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def parse_encodings(encodings)
|
199
|
+
encodings = encodings.split(':')
|
200
|
+
encodings.map { |encoding| encoding.empty? ? nil : Encoding.find(encoding) }
|
201
|
+
end
|
202
|
+
|
203
|
+
def verbose_by(level)
|
204
|
+
verbose = {0 => nil, 1 => false, 2 => true}
|
205
|
+
raise ArgumentError, "invalid warning level - #{level}" unless verbose.key?(level)
|
206
|
+
verbose[level]
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
end
|
data/lib/peeek/hook.rb
CHANGED
@@ -33,7 +33,7 @@ class Peeek
|
|
33
33
|
# # => #<Peeek::Hook #<IO:<STDOUT>>.puts>
|
34
34
|
#
|
35
35
|
# @param [Module, Class, Object] object a target object that hook
|
36
|
-
# @param [String, Symbol] method_spec method
|
36
|
+
# @param [String, Symbol] method_spec method specifier of the object
|
37
37
|
# @yield [call] process a call to the method. give optionally
|
38
38
|
# @yieldparam [Peeek::Call] call a call to the method
|
39
39
|
# @return [Peeek::Hook] a hook to the method of the object
|
data/lib/peeek/hook/linker.rb
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'peeek/hook'
|
2
|
+
|
3
|
+
class Peeek
|
4
|
+
class Hook
|
5
|
+
class Specifier
|
6
|
+
|
7
|
+
# Parse a string as hook specifier.
|
8
|
+
#
|
9
|
+
# @param [String] string string to parse as hook specifier
|
10
|
+
# @return [Peeek::Hook::Specifier] a hook specifier
|
11
|
+
def self.parse(string)
|
12
|
+
method_prefixes = METHOD_PREFIXES.sort_by(&:length).reverse.map do |method_prefix|
|
13
|
+
index = string.rindex(method_prefix)
|
14
|
+
priority = index ? [index + method_prefix.length, method_prefix.length] : nil
|
15
|
+
[method_prefix, index, priority]
|
16
|
+
end
|
17
|
+
|
18
|
+
method_prefixes = method_prefixes.select(&:last)
|
19
|
+
raise ArgumentError, "method name that is target of hook isn't specified in #{string.inspect}" if method_prefixes.empty?
|
20
|
+
method_prefix, index = method_prefixes.max_by(&:last)
|
21
|
+
method_prefix_range = index..(index + method_prefix.length - 1)
|
22
|
+
string_range = 0..(string.length - 1)
|
23
|
+
raise ArgumentError, "object name should not be empty for #{string.inspect}" unless string_range.begin < method_prefix_range.begin
|
24
|
+
raise ArgumentError, "method name should not be empty for #{string.inspect}" unless method_prefix_range.end < string_range.end
|
25
|
+
|
26
|
+
object_name = string[string_range.begin..(method_prefix_range.begin - 1)]
|
27
|
+
method_name = string[(method_prefix_range.end + 1)..string_range.end].to_sym
|
28
|
+
new(object_name, method_prefix, method_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Initialize the hook specifier.
|
32
|
+
#
|
33
|
+
# @param [String] object_name object name
|
34
|
+
# @param [String] method_prefix method prefix
|
35
|
+
# @param [Symbol] method_name method name in the object
|
36
|
+
def initialize(object_name, method_prefix, method_name)
|
37
|
+
@object_name = object_name
|
38
|
+
@method_prefix = normalize_method_prefix(method_prefix)
|
39
|
+
@method_name = method_name
|
40
|
+
end
|
41
|
+
|
42
|
+
# @attribute [r] object_name
|
43
|
+
# @return [String] object name
|
44
|
+
attr_reader :object_name
|
45
|
+
|
46
|
+
# @attribute [r] method_prefix
|
47
|
+
# @return [String] method prefix
|
48
|
+
attr_reader :method_prefix
|
49
|
+
|
50
|
+
# @attribute [r] method_name
|
51
|
+
# @return [Symbol] method name in the object
|
52
|
+
attr_reader :method_name
|
53
|
+
|
54
|
+
# @attribute [r] method_specifier
|
55
|
+
# @return [String] method specifier in the object
|
56
|
+
def method_specifier
|
57
|
+
@method_prefix + @method_name.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
@object_name + method_specifier
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
"#<#{self.class} #{self}>"
|
66
|
+
end
|
67
|
+
|
68
|
+
def ==(other)
|
69
|
+
self.class == other.class &&
|
70
|
+
@object_name == other.object_name &&
|
71
|
+
@method_prefix == other.method_prefix &&
|
72
|
+
@method_name == other.method_name
|
73
|
+
end
|
74
|
+
alias eql? ==
|
75
|
+
|
76
|
+
def hash
|
77
|
+
values = [@object_name, @method_prefix, @method_name]
|
78
|
+
values.inject(self.class.hash) { |hash, value| (hash << 32) + value.hash }
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def normalize_method_prefix(method_prefix)
|
84
|
+
case method_prefix
|
85
|
+
when *INSTANCE_METHOD_PREFIXES
|
86
|
+
Instance::METHOD_PREFIX
|
87
|
+
when *SINGLETON_METHOD_PREFIXES
|
88
|
+
Singleton::METHOD_PREFIX
|
89
|
+
else
|
90
|
+
init = METHOD_PREFIXES.map(&:inspect)
|
91
|
+
last = init.pop
|
92
|
+
method_prefixes = [init * ', ', last] * ' or '
|
93
|
+
raise ArgumentError, "invalid method prefix, #{method_prefixes} are valid"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|