-
Notifications
You must be signed in to change notification settings - Fork 604
/
pry.rb
243 lines (205 loc) · 5.1 KB
/
pry.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# (C) John Mair (banisterfiend) 2010
# MIT License
direc = File.dirname(__FILE__)
require 'ruby_parser'
require 'method_source'
require 'stringio'
require "#{direc}/pry/version"
require "#{direc}/pry/input"
require "#{direc}/pry/output"
class Pry
def self.start(target)
new.repl(target)
end
def self.view(obj)
case obj
when String, Array, Hash, Symbol, nil
obj.inspect
else
obj.to_s
end
end
# class accessors
class << self
attr_reader :nesting
attr_accessor :last_result
end
attr_accessor :input, :output
attr_reader :default_prompt, :wait_prompt, :last_result
def initialize(input = Input.new, output = Output.new)
@input = input
@output = output
@default_prompt = proc do |v, nest|
if nest == 0
"pry(#{Pry.view(v)})> "
else
"pry(#{Pry.view(v)}):#{Pry.view(nest)}> "
end
end
@wait_prompt = proc do |v, nest|
if nest == 0
"pry(#{Pry.view(v)})* "
else
"pry(#{Pry.view(v)}):#{Pry.view(nest)}* "
end
end
end
@nesting = []
def @nesting.level
last.is_a?(Array) ? last.first : nil
end
def nesting
self.class.nesting
end
def nesting=(v)
self.class.nesting = v
end
# loop
def repl(target=TOPLEVEL_BINDING)
target = binding_for(target)
target_self = target.eval('self')
output.session_start(target_self)
nesting_level = nesting.size
# Make sure _ exists
target.eval("_ = Pry.last_result")
break_level = catch(:breakout) do
nesting << [nesting.size, target_self]
loop do
rep(target)
end
end
nesting.pop
output.session_end(target_self)
# we only enter here if :breakout has been thrown
if nesting_level != break_level
throw :breakout, break_level
end
target_self
end
# print
def rep(target=TOPLEVEL_BINDING)
target = binding_for(target)
output.print re(target)
end
# eval
def re(target=TOPLEVEL_BINDING)
target = binding_for(target)
Pry.last_result = target.eval r(target)
target.eval("_ = Pry.last_result")
rescue SystemExit => e
exit
rescue Exception => e
e
end
# read
def r(target=TOPLEVEL_BINDING)
target = binding_for(target)
eval_string = ""
loop do
val = input.read(prompt(eval_string, target, nesting.level))
eval_string += "#{val.chomp}\n"
process_commands(val, eval_string, target)
break eval_string if valid_expression?(eval_string)
end
end
def process_commands(val, eval_string, target)
def eval_string.clear() replace("") end
case val
when "exit_program", "quit_program"
output.exit_program
exit
when "!"
output.refresh
eval_string.clear
when "help"
output.show_help
eval_string.clear
when "nesting"
output.show_nesting(nesting)
eval_string.clear
when "status"
output.show_status(nesting, target)
eval_string.clear
when "exit_all"
throw(:breakout, 0)
when "exit", "quit", "back", /^cd\s*\.\./
output.exit
throw(:breakout, nesting.level)
when "ls"
output.ls(target)
eval_string.clear
when /^cd\s+(.+)/
obj = $~.captures.first
target.eval("#{obj}.pry")
eval_string.clear
when /^show_method\s*(.+)/
meth_name = ($~.captures).first
code = get_method_source(target, meth_name, :method)
output.show_method code
eval_string.clear
when /^show_instance_method\s*(.+)/
meth_name = ($~.captures).first
code = get_method_source(target, meth_name, :instance_method)
output.show_method code
eval_string.clear
when /^jump_to\s*(\d*)/
break_level = ($~.captures).first.to_i
output.jump_to(break_level)
case break_level
when nesting.level
output.warn_already_at_level(nesting.level)
eval_string.clear
when (0...nesting.level)
throw(:breakout, break_level + 1)
else
output.err_invalid_nest_level(break_level,
nesting.level - 1)
eval_string.clear
end
end
end
def get_method_source(target, meth_name, kind)
target.eval("#{kind}(:#{meth_name}).source")
end
def prompt(eval_string, target, nest)
target_self = target.eval('self')
if eval_string.empty?
default_prompt.call(target_self, nest)
else
wait_prompt.call(target_self, nest)
end
end
if RUBY_VERSION =~ /1.9/
require 'ripper'
def valid_expression?(code)
!!Ripper::SexpBuilder.new(code).parse
end
else
def valid_expression?(code)
RubyParser.new.parse(code)
rescue Racc::ParseError, SyntaxError
false
else
true
end
end
def binding_for(target)
if target.is_a?(Binding)
target
else
if target == TOPLEVEL_BINDING.eval('self')
TOPLEVEL_BINDING
else
target.instance_eval { binding }
end
end
end
module ObjectExtensions
def pry(target=self)
Pry.new.repl(target)
end
end
end
class Object
include Pry::ObjectExtensions
end