pry 0.9.7.4-i386-mswin32 → 0.9.8-i386-mswin32
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -3
- data/CHANGELOG +43 -0
- data/README.markdown +3 -1
- data/Rakefile +51 -32
- data/bin/pry +2 -80
- data/lib/pry.rb +33 -26
- data/lib/pry/cli.rb +152 -0
- data/lib/pry/code.rb +351 -0
- data/lib/pry/command.rb +422 -0
- data/lib/pry/command_set.rb +259 -129
- data/lib/pry/commands.rb +0 -1
- data/lib/pry/config.rb +43 -9
- data/lib/pry/default_commands/context.rb +109 -92
- data/lib/pry/default_commands/documentation.rb +174 -63
- data/lib/pry/default_commands/easter_eggs.rb +26 -2
- data/lib/pry/default_commands/gems.rb +65 -37
- data/lib/pry/default_commands/input.rb +175 -243
- data/lib/pry/default_commands/introspection.rb +173 -112
- data/lib/pry/default_commands/ls.rb +96 -114
- data/lib/pry/default_commands/shell.rb +175 -70
- data/lib/pry/helpers/base_helpers.rb +7 -2
- data/lib/pry/helpers/command_helpers.rb +71 -77
- data/lib/pry/helpers/options_helpers.rb +10 -41
- data/lib/pry/helpers/text.rb +24 -4
- data/lib/pry/history.rb +55 -17
- data/lib/pry/history_array.rb +2 -0
- data/lib/pry/hooks.rb +252 -0
- data/lib/pry/indent.rb +9 -5
- data/lib/pry/method.rb +149 -50
- data/lib/pry/plugins.rb +12 -4
- data/lib/pry/pry_class.rb +69 -26
- data/lib/pry/pry_instance.rb +187 -115
- data/lib/pry/version.rb +1 -1
- data/lib/pry/wrapped_module.rb +73 -0
- data/man/pry.1 +195 -0
- data/man/pry.1.html +204 -0
- data/man/pry.1.ronn +141 -0
- data/pry.gemspec +29 -32
- data/test/helper.rb +32 -36
- data/test/test_cli.rb +78 -0
- data/test/test_code.rb +201 -0
- data/test/test_command.rb +327 -0
- data/test/test_command_integration.rb +512 -0
- data/test/test_command_set.rb +338 -12
- data/test/test_completion.rb +1 -1
- data/test/test_default_commands.rb +1 -2
- data/test/test_default_commands/test_context.rb +27 -5
- data/test/test_default_commands/test_documentation.rb +20 -8
- data/test/test_default_commands/test_input.rb +84 -45
- data/test/test_default_commands/test_introspection.rb +74 -17
- data/test/test_default_commands/test_ls.rb +9 -36
- data/test/test_default_commands/test_shell.rb +240 -13
- data/test/test_hooks.rb +490 -0
- data/test/test_indent.rb +2 -0
- data/test/test_method.rb +60 -0
- data/test/test_pry.rb +29 -904
- data/test/test_pry_defaults.rb +380 -0
- data/test/test_pry_history.rb +24 -24
- data/test/test_syntax_checking.rb +63 -0
- data/test/test_wrapped_module.rb +71 -0
- metadata +50 -39
- data/lib/pry/command_context.rb +0 -53
- data/lib/pry/command_processor.rb +0 -181
- data/lib/pry/extended_commands/user_command_api.rb +0 -65
- data/test/test_command_processor.rb +0 -176
data/lib/pry/code.rb
ADDED
@@ -0,0 +1,351 @@
|
|
1
|
+
class Pry
|
2
|
+
class << self
|
3
|
+
# Convert the given object into an instance of `Pry::Code`, if it isn't
|
4
|
+
# already one.
|
5
|
+
#
|
6
|
+
# @param [Code, Method, UnboundMethod, Proc, Pry::Method, String, Array,
|
7
|
+
# IO] obj
|
8
|
+
def Code(obj)
|
9
|
+
case obj
|
10
|
+
when Code
|
11
|
+
obj
|
12
|
+
when ::Method, UnboundMethod, Proc, Pry::Method
|
13
|
+
Code.from_method(obj)
|
14
|
+
else
|
15
|
+
Code.new(obj)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# `Pry::Code` is a class that encapsulates lines of source code and their
|
21
|
+
# line numbers and formats them for terminal output. It can read from a file
|
22
|
+
# or method definition or be instantiated with a `String` or an `Array`.
|
23
|
+
#
|
24
|
+
# In general, the formatting methods in `Code` return a new `Code` object
|
25
|
+
# which will format the text as specified when `#to_s` is called. This allows
|
26
|
+
# arbitrary chaining of formatting methods without mutating the original
|
27
|
+
# object.
|
28
|
+
class Code
|
29
|
+
class << self
|
30
|
+
# Instantiate a `Code` object containing code loaded from a file or
|
31
|
+
# Pry's line buffer.
|
32
|
+
#
|
33
|
+
# @param [String] fn The name of a file, or "(pry)".
|
34
|
+
# @param [Symbol] code_type (:ruby) The type of code the file contains.
|
35
|
+
# @return [Code]
|
36
|
+
def from_file(fn, code_type=nil)
|
37
|
+
if fn == Pry.eval_path
|
38
|
+
f = Pry.line_buffer.drop(1)
|
39
|
+
else
|
40
|
+
if File.readable?(fn)
|
41
|
+
f = File.open(fn, 'r')
|
42
|
+
code_type = type_from_filename(fn)
|
43
|
+
else
|
44
|
+
raise CommandError, "Cannot open #{fn.inspect} for reading."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
new(f, 1, code_type || :ruby)
|
48
|
+
ensure
|
49
|
+
f.close if f.respond_to?(:close)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Instantiate a `Code` object containing code extracted from a
|
53
|
+
# `::Method`, `UnboundMethod`, `Proc`, or `Pry::Method` object.
|
54
|
+
#
|
55
|
+
# @param [::Method, UnboundMethod, Proc, Pry::Method] meth The method
|
56
|
+
# object.
|
57
|
+
# @param [Fixnum, nil] The line number to start on, or nil to use the
|
58
|
+
# method's original line numbers.
|
59
|
+
# @return [Code]
|
60
|
+
def from_method(meth, start_line=nil)
|
61
|
+
meth = Pry::Method(meth)
|
62
|
+
start_line ||= meth.source_line || 1
|
63
|
+
new(meth.source, start_line, meth.source_type)
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
# Guess the CodeRay type of a file from its extension, or nil if
|
68
|
+
# unknown.
|
69
|
+
#
|
70
|
+
# @param [String] filename
|
71
|
+
# @return [Symbol, nil]
|
72
|
+
def type_from_filename(filename)
|
73
|
+
map = {
|
74
|
+
%w(.c .h) => :c,
|
75
|
+
%w(.cpp .hpp .cc .h cxx) => :cpp,
|
76
|
+
%w(.rb .ru .irbrc .gemspec .pryrc) => :ruby,
|
77
|
+
%w(.py) => :python,
|
78
|
+
%w(.diff) => :diff,
|
79
|
+
%w(.css) => :css,
|
80
|
+
%w(.html) => :html,
|
81
|
+
%w(.yaml .yml) => :yaml,
|
82
|
+
%w(.xml) => :xml,
|
83
|
+
%w(.php) => :php,
|
84
|
+
%w(.js) => :javascript,
|
85
|
+
%w(.java) => :java,
|
86
|
+
%w(.rhtml) => :rhtml,
|
87
|
+
%w(.json) => :json
|
88
|
+
}
|
89
|
+
|
90
|
+
_, type = map.find do |k, _|
|
91
|
+
k.any? { |ext| ext == File.extname(filename) }
|
92
|
+
end
|
93
|
+
|
94
|
+
type
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_accessor :code_type
|
99
|
+
|
100
|
+
# Instantiate a `Code` object containing code from the given `Array`,
|
101
|
+
# `String`, or `IO`. The first line will be line 1 unless specified
|
102
|
+
# otherwise. If you need non-contiguous line numbers, you can create an
|
103
|
+
# empty `Code` object and then use `#push` to insert the lines.
|
104
|
+
#
|
105
|
+
# @param [Array<String>, String, IO] lines
|
106
|
+
# @param [Fixnum?] (1) start_line
|
107
|
+
# @param [Symbol?] (:ruby) code_type
|
108
|
+
def initialize(lines=[], start_line=1, code_type=:ruby)
|
109
|
+
if lines.is_a? String
|
110
|
+
lines = lines.lines
|
111
|
+
end
|
112
|
+
|
113
|
+
@lines = lines.each_with_index.map { |l, i| [l.chomp, i + start_line] }
|
114
|
+
@code_type = code_type
|
115
|
+
end
|
116
|
+
|
117
|
+
# Append the given line. `line_num` is one more than the last existing
|
118
|
+
# line, unless specified otherwise.
|
119
|
+
#
|
120
|
+
# @param [String] line
|
121
|
+
# @param [Fixnum?] line_num
|
122
|
+
# @return [String] The inserted line.
|
123
|
+
def push(line, line_num=nil)
|
124
|
+
line_num = @lines.last.last + 1 unless line_num
|
125
|
+
@lines.push([line.chomp, line_num])
|
126
|
+
line
|
127
|
+
end
|
128
|
+
alias << push
|
129
|
+
|
130
|
+
# Filter the lines using the given block.
|
131
|
+
#
|
132
|
+
# @yield [line]
|
133
|
+
# @return [Code]
|
134
|
+
def select(&blk)
|
135
|
+
alter do
|
136
|
+
@lines = @lines.select(&blk)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Remove all lines that aren't in the given range, expressed either as a
|
141
|
+
# `Range` object or a first and last line number (inclusive). Negative
|
142
|
+
# indices count from the end of the array of lines.
|
143
|
+
#
|
144
|
+
# @param [Range, Fixnum] start_line
|
145
|
+
# @param [Fixnum?] end_line
|
146
|
+
# @return [Code]
|
147
|
+
def between(start_line, end_line=nil)
|
148
|
+
return self unless start_line
|
149
|
+
|
150
|
+
if start_line.is_a? Range
|
151
|
+
end_line = start_line.last
|
152
|
+
end_line -= 1 if start_line.exclude_end?
|
153
|
+
|
154
|
+
start_line = start_line.first
|
155
|
+
else
|
156
|
+
end_line ||= start_line
|
157
|
+
end
|
158
|
+
|
159
|
+
if start_line > 0
|
160
|
+
start_idx = @lines.index { |l| l.last >= start_line } || @lines.length
|
161
|
+
else
|
162
|
+
start_idx = start_line
|
163
|
+
end
|
164
|
+
|
165
|
+
if end_line > 0
|
166
|
+
end_idx = (@lines.index { |l| l.last > end_line } || 0) - 1
|
167
|
+
else
|
168
|
+
end_idx = end_line
|
169
|
+
end
|
170
|
+
|
171
|
+
alter do
|
172
|
+
@lines = @lines[start_idx..end_idx] || []
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Remove all lines except for the `lines` up to and excluding `line_num`.
|
177
|
+
#
|
178
|
+
# @param [Fixnum] line_num
|
179
|
+
# @param [Fixnum] (1) lines
|
180
|
+
# @return [Code]
|
181
|
+
def before(line_num, lines=1)
|
182
|
+
return self unless line_num
|
183
|
+
|
184
|
+
select do |l, ln|
|
185
|
+
ln >= line_num - lines && ln < line_num
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Remove all lines except for the `lines` on either side of and including
|
190
|
+
# `line_num`.
|
191
|
+
#
|
192
|
+
# @param [Fixnum] line_num
|
193
|
+
# @param [Fixnum] (1) lines
|
194
|
+
# @return [Code]
|
195
|
+
def around(line_num, lines=1)
|
196
|
+
return self unless line_num
|
197
|
+
|
198
|
+
select do |l, ln|
|
199
|
+
ln >= line_num - lines && ln <= line_num + lines
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Remove all lines except for the `lines` after and excluding `line_num`.
|
204
|
+
#
|
205
|
+
# @param [Fixnum] line_num
|
206
|
+
# @param [Fixnum] (1) lines
|
207
|
+
# @return [Code]
|
208
|
+
def after(line_num, lines=1)
|
209
|
+
return self unless line_num
|
210
|
+
|
211
|
+
select do |l, ln|
|
212
|
+
ln > line_num && ln <= line_num + lines
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Remove all lines that don't match the given `pattern`.
|
217
|
+
#
|
218
|
+
# @param [Regexp] pattern
|
219
|
+
# @return [Code]
|
220
|
+
def grep(pattern)
|
221
|
+
return self unless pattern
|
222
|
+
pattern = Regexp.new(pattern)
|
223
|
+
|
224
|
+
select do |l, ln|
|
225
|
+
l =~ pattern
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Format output with line numbers next to it, unless `y_n` is falsy.
|
230
|
+
#
|
231
|
+
# @param [Boolean?] (true) y_n
|
232
|
+
# @return [Code]
|
233
|
+
def with_line_numbers(y_n=true)
|
234
|
+
alter do
|
235
|
+
@with_line_numbers = y_n
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Format output with a marker next to the given `line_num`, unless `line_num`
|
240
|
+
# is falsy.
|
241
|
+
#
|
242
|
+
# @param [Fixnum?] (1) line_num
|
243
|
+
# @return [Code]
|
244
|
+
def with_marker(line_num=1)
|
245
|
+
alter do
|
246
|
+
@with_marker = !!line_num
|
247
|
+
@marker_line_num = line_num
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Format output with the specified number of spaces in front of every line,
|
252
|
+
# unless `spaces` is falsy.
|
253
|
+
#
|
254
|
+
# @param [Fixnum?] (0) spaces
|
255
|
+
# @return [Code]
|
256
|
+
def with_indentation(spaces=0)
|
257
|
+
alter do
|
258
|
+
@with_indentation = !!spaces
|
259
|
+
@indentation_num = spaces
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# @return [String]
|
264
|
+
def inspect
|
265
|
+
Object.instance_method(:to_s).bind(self).call
|
266
|
+
end
|
267
|
+
|
268
|
+
# Based on the configuration of the object, return a formatted String
|
269
|
+
# representation.
|
270
|
+
#
|
271
|
+
# @return [String]
|
272
|
+
def to_s
|
273
|
+
lines = @lines.map(&:dup)
|
274
|
+
|
275
|
+
if Pry.color
|
276
|
+
lines.each do |l|
|
277
|
+
l[0] = CodeRay.scan(l[0], @code_type).term
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
if @with_line_numbers
|
282
|
+
max_width = lines.last.last.to_s.length if lines.length > 0
|
283
|
+
lines.each do |l|
|
284
|
+
padded_line_num = l[1].to_s.rjust(max_width)
|
285
|
+
l[0] = "#{Pry::Helpers::Text.blue(padded_line_num)}: #{l[0]}"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
if @with_marker
|
290
|
+
lines.each do |l|
|
291
|
+
if l[1] == @marker_line_num
|
292
|
+
l[0] = " => #{l[0]}"
|
293
|
+
else
|
294
|
+
l[0] = " #{l[0]}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
if @with_indentation
|
300
|
+
lines.each do |l|
|
301
|
+
l[0] = "#{' ' * @indentation_num}#{l[0]}"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
lines.map { |l| "#{l.first}\n" }.join
|
306
|
+
end
|
307
|
+
|
308
|
+
# Return an unformatted String of the code.
|
309
|
+
#
|
310
|
+
# @return [String]
|
311
|
+
def raw
|
312
|
+
@lines.map(&:first).join("\n")
|
313
|
+
end
|
314
|
+
|
315
|
+
# Return the number of lines stored.
|
316
|
+
#
|
317
|
+
# @return [Fixnum]
|
318
|
+
def length
|
319
|
+
@lines ? @lines.length : 0
|
320
|
+
end
|
321
|
+
|
322
|
+
# Two `Code` objects are equal if they contain the same lines with the same
|
323
|
+
# numbers. Otherwise, call `to_s` and `chomp` and compare as Strings.
|
324
|
+
#
|
325
|
+
# @param [Code, Object] other
|
326
|
+
# @return [Boolean]
|
327
|
+
def ==(other)
|
328
|
+
if other.is_a?(Code)
|
329
|
+
@other_lines = other.instance_variable_get(:@lines)
|
330
|
+
@lines.each_with_index.all? do |(l, ln), i|
|
331
|
+
l == @other_lines[i].first && ln == @other_lines[i].last
|
332
|
+
end
|
333
|
+
else
|
334
|
+
to_s.chomp == other.to_s.chomp
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# Forward any missing methods to the output of `#to_s`.
|
339
|
+
def method_missing(name, *args, &blk)
|
340
|
+
to_s.send(name, *args, &blk)
|
341
|
+
end
|
342
|
+
undef =~
|
343
|
+
|
344
|
+
protected
|
345
|
+
# An abstraction of the `dup.instance_eval` pattern used throughout this
|
346
|
+
# class.
|
347
|
+
def alter(&blk)
|
348
|
+
dup.tap { |o| o.instance_eval(&blk) }
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
data/lib/pry/command.rb
ADDED
@@ -0,0 +1,422 @@
|
|
1
|
+
class Pry
|
2
|
+
|
3
|
+
# The super-class of all commands, new commands should be created by calling
|
4
|
+
# {Pry::CommandSet#command} which creates a BlockCommand or {Pry::CommandSet#create_command}
|
5
|
+
# which creates a ClassCommand. Please don't use this class directly.
|
6
|
+
class Command
|
7
|
+
|
8
|
+
# represents a void return value for a command
|
9
|
+
VOID_VALUE = Object.new
|
10
|
+
|
11
|
+
# give it a nice inspect
|
12
|
+
def VOID_VALUE.inspect() "void" end
|
13
|
+
|
14
|
+
# Properties of the command itself (as passed as arguments to
|
15
|
+
# {CommandSet#command} or {CommandSet#create_command}).
|
16
|
+
class << self
|
17
|
+
attr_accessor :block
|
18
|
+
attr_accessor :name
|
19
|
+
attr_writer :description
|
20
|
+
attr_writer :command_options
|
21
|
+
|
22
|
+
# Define or get the command's description
|
23
|
+
def description(arg=nil)
|
24
|
+
@description = arg if arg
|
25
|
+
@description
|
26
|
+
end
|
27
|
+
|
28
|
+
# Define or get the command's options
|
29
|
+
def command_options(arg=nil)
|
30
|
+
@command_options ||= {}
|
31
|
+
@command_options.merge!(arg) if arg
|
32
|
+
@command_options
|
33
|
+
end
|
34
|
+
# backward compatibility
|
35
|
+
alias_method :options, :command_options
|
36
|
+
alias_method :options=, :command_options=
|
37
|
+
|
38
|
+
# Define or get the command's banner
|
39
|
+
def banner(arg=nil)
|
40
|
+
@banner = arg if arg
|
41
|
+
@banner || description
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Make those properties accessible to instances
|
47
|
+
def name; self.class.name; end
|
48
|
+
def description; self.class.description; end
|
49
|
+
def block; self.class.block; end
|
50
|
+
def command_options; self.class.options; end
|
51
|
+
def command_name; command_options[:listing]; end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def inspect
|
55
|
+
"#<class(Pry::Command #{name.inspect})>"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create a new command with the given properties.
|
59
|
+
#
|
60
|
+
# @param String name the name of the command
|
61
|
+
# @param String description the description to appear in {help}
|
62
|
+
# @param Hash options behavioural options (@see {Pry::CommandSet#command})
|
63
|
+
# @param Module helpers a module of helper functions to be included.
|
64
|
+
# @param Proc &block (optional, a block, used for BlockCommands)
|
65
|
+
#
|
66
|
+
# @return Class (a subclass of Pry::Command)
|
67
|
+
#
|
68
|
+
def subclass(name, description, options, helpers, &block)
|
69
|
+
klass = Class.new(self)
|
70
|
+
klass.send(:include, helpers)
|
71
|
+
klass.name = name
|
72
|
+
klass.description = description
|
73
|
+
klass.command_options = options
|
74
|
+
klass.block = block
|
75
|
+
klass
|
76
|
+
end
|
77
|
+
|
78
|
+
# Should this command be called for the given line?
|
79
|
+
#
|
80
|
+
# @param String a line input at the REPL
|
81
|
+
# @return Boolean
|
82
|
+
def matches?(val)
|
83
|
+
command_regex =~ val
|
84
|
+
end
|
85
|
+
|
86
|
+
# Store hooks to be run before or after the command body.
|
87
|
+
# @see {Pry::CommandSet#before_command}
|
88
|
+
# @see {Pry::CommandSet#after_command}
|
89
|
+
def hooks
|
90
|
+
@hooks ||= {:before => [], :after => []}
|
91
|
+
end
|
92
|
+
|
93
|
+
def command_regex
|
94
|
+
prefix = convert_to_regex(Pry.config.command_prefix)
|
95
|
+
prefix = "(?:#{prefix})?" unless options[:use_prefix]
|
96
|
+
|
97
|
+
/^#{prefix}#{convert_to_regex(name)}(?!\S)/
|
98
|
+
end
|
99
|
+
|
100
|
+
def convert_to_regex(obj)
|
101
|
+
case obj
|
102
|
+
when String
|
103
|
+
Regexp.escape(obj)
|
104
|
+
else
|
105
|
+
obj
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Properties of one execution of a command (passed by {Pry#run_command} as a hash of
|
112
|
+
# context and expanded in {#initialize}
|
113
|
+
attr_accessor :output
|
114
|
+
attr_accessor :target
|
115
|
+
attr_accessor :captures
|
116
|
+
attr_accessor :eval_string
|
117
|
+
attr_accessor :arg_string
|
118
|
+
attr_accessor :context
|
119
|
+
attr_accessor :command_set
|
120
|
+
attr_accessor :_pry_
|
121
|
+
|
122
|
+
# Run a command from another command.
|
123
|
+
# @param [String] command_string The string that invokes the command
|
124
|
+
# @param [Array] args Further arguments to pass to the command
|
125
|
+
# @example
|
126
|
+
# run "show-input"
|
127
|
+
# @example
|
128
|
+
# run ".ls"
|
129
|
+
# @example
|
130
|
+
# run "amend-line", "5", 'puts "hello world"'
|
131
|
+
def run(command_string, *args)
|
132
|
+
complete_string = "#{command_string} #{args.join(" ")}"
|
133
|
+
command_set.process_line(complete_string, context)
|
134
|
+
end
|
135
|
+
|
136
|
+
def commands
|
137
|
+
command_set.commands
|
138
|
+
end
|
139
|
+
|
140
|
+
def text
|
141
|
+
Pry::Helpers::Text
|
142
|
+
end
|
143
|
+
|
144
|
+
def void
|
145
|
+
VOID_VALUE
|
146
|
+
end
|
147
|
+
|
148
|
+
include Pry::Helpers::BaseHelpers
|
149
|
+
include Pry::Helpers::CommandHelpers
|
150
|
+
|
151
|
+
|
152
|
+
# Instantiate a command, in preparation for calling it.
|
153
|
+
#
|
154
|
+
# @param Hash context The runtime context to use with this command.
|
155
|
+
def initialize(context={})
|
156
|
+
self.context = context
|
157
|
+
self.target = context[:target]
|
158
|
+
self.output = context[:output]
|
159
|
+
self.eval_string = context[:eval_string]
|
160
|
+
self.command_set = context[:command_set]
|
161
|
+
self._pry_ = context[:pry_instance]
|
162
|
+
end
|
163
|
+
|
164
|
+
# The value of {self} inside the {target} binding.
|
165
|
+
def target_self; target.eval('self'); end
|
166
|
+
|
167
|
+
# Revaluate the string (str) and perform interpolation.
|
168
|
+
# @param [String] str The string to reevaluate with interpolation.
|
169
|
+
#
|
170
|
+
# @return [String] The reevaluated string with interpolations
|
171
|
+
# applied (if any).
|
172
|
+
def interpolate_string(str)
|
173
|
+
dumped_str = str.dump
|
174
|
+
if dumped_str.gsub!(/\\\#\{/, '#{')
|
175
|
+
target.eval(dumped_str)
|
176
|
+
else
|
177
|
+
str
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Display a warning if a command collides with a local/method in
|
182
|
+
# the current scope.
|
183
|
+
# @param [String] command_name_match The name of the colliding command.
|
184
|
+
# @param [Binding] target The current binding context.
|
185
|
+
def check_for_command_name_collision(command_name_match)
|
186
|
+
if collision_type = target.eval("defined?(#{command_name_match})")
|
187
|
+
output.puts "#{Pry::Helpers::Text.bold('WARNING:')} Command name collision with a #{collision_type}: '#{command_name_match}'\n\n"
|
188
|
+
end
|
189
|
+
rescue Pry::RescuableException
|
190
|
+
end
|
191
|
+
|
192
|
+
# Extract necessary information from a line that Command.matches? this command.
|
193
|
+
#
|
194
|
+
# @param String the line of input
|
195
|
+
# @return [
|
196
|
+
# String the command name used, or portion of line that matched the command_regex
|
197
|
+
# String a string of all the arguments (i.e. everything but the name)
|
198
|
+
# Array the captures caught by the command_regex
|
199
|
+
# Array args the arguments got by splitting the arg_string
|
200
|
+
# ]
|
201
|
+
def tokenize(val)
|
202
|
+
val.replace(interpolate_string(val)) if command_options[:interpolate]
|
203
|
+
|
204
|
+
self.class.command_regex =~ val
|
205
|
+
|
206
|
+
# please call Command.matches? before Command#call_safely
|
207
|
+
raise CommandError, "fatal: called a command which didn't match?!" unless Regexp.last_match
|
208
|
+
captures = Regexp.last_match.captures
|
209
|
+
pos = Regexp.last_match.end(0)
|
210
|
+
|
211
|
+
arg_string = val[pos..-1]
|
212
|
+
|
213
|
+
# remove the one leading space if it exists
|
214
|
+
arg_string.slice!(0) if arg_string.start_with?(" ")
|
215
|
+
|
216
|
+
if arg_string
|
217
|
+
args = command_options[:shellwords] ? Shellwords.shellwords(arg_string) : arg_string.split(" ")
|
218
|
+
else
|
219
|
+
args = []
|
220
|
+
end
|
221
|
+
|
222
|
+
[val[0..pos].rstrip, arg_string, captures, args]
|
223
|
+
end
|
224
|
+
|
225
|
+
# Process a line that Command.matches? this command.
|
226
|
+
#
|
227
|
+
# @param String the line to process
|
228
|
+
# @return Object or Command::VOID_VALUE
|
229
|
+
def process_line(line)
|
230
|
+
command_name, arg_string, captures, args = tokenize(line)
|
231
|
+
|
232
|
+
check_for_command_name_collision(command_name) if Pry.config.collision_warning
|
233
|
+
|
234
|
+
self.arg_string = arg_string
|
235
|
+
self.captures = captures
|
236
|
+
|
237
|
+
call_safely(*(captures + args))
|
238
|
+
end
|
239
|
+
|
240
|
+
# Run the command with the given {args}.
|
241
|
+
#
|
242
|
+
# This is a public wrapper around {#call} which ensures all preconditions are met.
|
243
|
+
#
|
244
|
+
# @param *[String] the arguments to pass to this command.
|
245
|
+
# @return Object the return value of the {#call} method, or Command::VOID_VALUE
|
246
|
+
def call_safely(*args)
|
247
|
+
unless dependencies_met?
|
248
|
+
gems_needed = Array(command_options[:requires_gem])
|
249
|
+
gems_not_installed = gems_needed.select { |g| !gem_installed?(g) }
|
250
|
+
output.puts "\nThe command '#{name}' is #{Helpers::Text.bold("unavailable")} because it requires the following gems to be installed: #{(gems_not_installed.join(", "))}"
|
251
|
+
output.puts "-"
|
252
|
+
output.puts "Type `install-command #{name}` to install the required gems and activate this command."
|
253
|
+
return void
|
254
|
+
end
|
255
|
+
|
256
|
+
if command_options[:argument_required] && args.empty?
|
257
|
+
raise CommandError, "The command '#{name}' requires an argument."
|
258
|
+
end
|
259
|
+
|
260
|
+
ret = call_with_hooks(*args)
|
261
|
+
command_options[:keep_retval] ? ret : void
|
262
|
+
end
|
263
|
+
|
264
|
+
# Are all the gems required to use this command installed?
|
265
|
+
#
|
266
|
+
# @return Boolean
|
267
|
+
def dependencies_met?
|
268
|
+
@dependencies_met ||= command_dependencies_met?(command_options)
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
|
273
|
+
# Run the {#call} method and all the registered hooks.
|
274
|
+
#
|
275
|
+
# @param *String the arguments to #{call}
|
276
|
+
# @return Object the return value from #{call}
|
277
|
+
def call_with_hooks(*args)
|
278
|
+
self.class.hooks[:before].each do |block|
|
279
|
+
instance_exec(*args, &block)
|
280
|
+
end
|
281
|
+
|
282
|
+
ret = call(*args)
|
283
|
+
|
284
|
+
self.class.hooks[:after].each do |block|
|
285
|
+
ret = instance_exec(*args, &block)
|
286
|
+
end
|
287
|
+
|
288
|
+
ret
|
289
|
+
end
|
290
|
+
|
291
|
+
# Fix the number of arguments we pass to a block to avoid arity warnings.
|
292
|
+
#
|
293
|
+
# @param Number the arity of the block
|
294
|
+
# @param Array the arguments to pass
|
295
|
+
# @return Array a (possibly shorter) array of the arguments to pass
|
296
|
+
def correct_arg_arity(arity, args)
|
297
|
+
case
|
298
|
+
when arity < 0
|
299
|
+
args
|
300
|
+
when arity == 0
|
301
|
+
[]
|
302
|
+
when arity > 0
|
303
|
+
args.values_at(*(0..(arity - 1)).to_a)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# A super-class for Commands that are created with a single block.
|
309
|
+
#
|
310
|
+
# This class ensures that the block is called with the correct number of arguments
|
311
|
+
# and the right context.
|
312
|
+
#
|
313
|
+
# Create subclasses using {Pry::CommandSet#command}.
|
314
|
+
class BlockCommand < Command
|
315
|
+
# backwards compatibility
|
316
|
+
alias_method :opts, :context
|
317
|
+
|
318
|
+
# Call the block that was registered with this command.
|
319
|
+
#
|
320
|
+
# @param *String the arguments passed
|
321
|
+
# @return Object the return value of the block
|
322
|
+
def call(*args)
|
323
|
+
instance_exec(*correct_arg_arity(block.arity, args), &block)
|
324
|
+
end
|
325
|
+
|
326
|
+
def help; description; end
|
327
|
+
end
|
328
|
+
|
329
|
+
# A super-class ofr Commands with structure.
|
330
|
+
#
|
331
|
+
# This class implements the bare-minimum functionality that a command should have,
|
332
|
+
# namely a --help switch, and then delegates actual processing to its subclasses.
|
333
|
+
#
|
334
|
+
# Create subclasses using {Pry::CommandSet#create_command}, and override the {options(opt)} method
|
335
|
+
# to set up an instance of Slop, and the {process} method to actually run the command. If
|
336
|
+
# necessary, you can also override {setup} which will be called before {options}, for example to
|
337
|
+
# require any gems your command needs to run, or to set up state.
|
338
|
+
class ClassCommand < Command
|
339
|
+
|
340
|
+
attr_accessor :opts
|
341
|
+
attr_accessor :args
|
342
|
+
|
343
|
+
# Set up {opts} and {args}, and then call {process}
|
344
|
+
#
|
345
|
+
# This function will display help if necessary.
|
346
|
+
#
|
347
|
+
# @param *String the arguments passed
|
348
|
+
# @return Object the return value of {process} or VOID_VALUE
|
349
|
+
def call(*args)
|
350
|
+
setup
|
351
|
+
|
352
|
+
self.opts = slop
|
353
|
+
self.args = self.opts.parse!(args)
|
354
|
+
|
355
|
+
if opts.present?(:help)
|
356
|
+
output.puts slop.help
|
357
|
+
void
|
358
|
+
else
|
359
|
+
process(*correct_arg_arity(method(:process).arity, args))
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Return the help generated by Slop for this command.
|
364
|
+
def help
|
365
|
+
slop.help
|
366
|
+
end
|
367
|
+
|
368
|
+
# Return an instance of Slop that can parse the options that this command accepts.
|
369
|
+
def slop
|
370
|
+
Slop.new do |opt|
|
371
|
+
opt.banner(unindent(self.class.banner))
|
372
|
+
options(opt)
|
373
|
+
opt.on(:h, :help, "Show this message.")
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# A function called just before {options(opt)} as part of {call}.
|
378
|
+
#
|
379
|
+
# This function can be used to set up any context your command needs to run, for example
|
380
|
+
# requiring gems, or setting default values for options.
|
381
|
+
#
|
382
|
+
# @example
|
383
|
+
# def setup;
|
384
|
+
# require 'gist'
|
385
|
+
# @action = :method
|
386
|
+
# end
|
387
|
+
def setup; end
|
388
|
+
|
389
|
+
# A function to setup Slop so it can parse the options your command expects.
|
390
|
+
#
|
391
|
+
# NOTE: please don't do anything side-effecty in the main part of this method,
|
392
|
+
# as it may be called by Pry at any time for introspection reasons. If you need
|
393
|
+
# to set up default values, use {setup} instead.
|
394
|
+
#
|
395
|
+
# @example
|
396
|
+
# def options(opt)
|
397
|
+
# opt.banner "Gists methods or classes"
|
398
|
+
# opt.on(:c, :class, "gist a class") do
|
399
|
+
# @action = :class
|
400
|
+
# end
|
401
|
+
# end
|
402
|
+
def options(opt); end
|
403
|
+
|
404
|
+
# The actual body of your command should go here.
|
405
|
+
#
|
406
|
+
# The {opts} mehod can be called to get the options that Slop has passed,
|
407
|
+
# and {args} gives the remaining, unparsed arguments.
|
408
|
+
#
|
409
|
+
# The return value of this method is discarded unless the command was created
|
410
|
+
# with :keep_retval => true, in which case it is returned to the repl.
|
411
|
+
#
|
412
|
+
# @example
|
413
|
+
# def process
|
414
|
+
# if opts.present?(:class)
|
415
|
+
# gist_class
|
416
|
+
# else
|
417
|
+
# gist_method
|
418
|
+
# end
|
419
|
+
# end
|
420
|
+
def process; raise CommandError, "command '#{name}' not implemented" end
|
421
|
+
end
|
422
|
+
end
|