pry 0.9.5 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +21 -0
- data/Gemfile +2 -0
- data/README.markdown +15 -17
- data/Rakefile +1 -1
- data/bin/pry +8 -6
- data/examples/example_command_override.rb +1 -1
- data/lib/pry.rb +19 -4
- data/lib/pry/command_context.rb +1 -0
- data/lib/pry/command_processor.rb +1 -0
- data/lib/pry/command_set.rb +2 -2
- data/lib/pry/config.rb +15 -0
- data/lib/pry/default_commands/context.rb +8 -4
- data/lib/pry/default_commands/documentation.rb +18 -12
- data/lib/pry/default_commands/input.rb +120 -67
- data/lib/pry/default_commands/introspection.rb +112 -67
- data/lib/pry/default_commands/shell.rb +28 -17
- data/lib/pry/helpers/command_helpers.rb +73 -40
- data/lib/pry/history_array.rb +4 -0
- data/lib/pry/pry_class.rb +10 -6
- data/lib/pry/pry_instance.rb +57 -25
- data/lib/pry/version.rb +1 -1
- data/pry.gemspec +8 -8
- data/test/helper.rb +35 -1
- data/test/test_command_set.rb +1 -1
- data/test/test_default_commands/test_input.rb +30 -17
- data/test/test_default_commands/test_introspection.rb +334 -1
- data/test/test_default_commands/test_shell.rb +100 -5
- data/test/test_exception_whitelist.rb +17 -0
- data/test/test_input_stack.rb +70 -0
- data/test/test_pry.rb +26 -22
- data/test/test_pry_output.rb +2 -6
- metadata +66 -67
@@ -9,9 +9,11 @@ class Pry
|
|
9
9
|
target = target()
|
10
10
|
|
11
11
|
opts = Slop.parse!(args) do |opt|
|
12
|
-
opt.banner
|
13
|
-
|
14
|
-
|
12
|
+
opt.banner unindent <<-USAGE
|
13
|
+
Usage: show-method [OPTIONS] [METH 1] [METH 2] [METH N]
|
14
|
+
Show the source for method METH. Tries instance methods first and then methods by default.
|
15
|
+
e.g: show-method hello_method
|
16
|
+
USAGE
|
15
17
|
|
16
18
|
opt.on :l, "line-numbers", "Show line numbers."
|
17
19
|
opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)."
|
@@ -58,16 +60,18 @@ class Pry
|
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
61
|
-
alias_command "show-source", "show-method"
|
62
|
-
alias_command "$", "show-method"
|
63
|
+
alias_command "show-source", "show-method"
|
64
|
+
alias_command "$", "show-method"
|
63
65
|
|
64
66
|
command "show-command", "Show the source for CMD. Type `show-command --help` for more info." do |*args|
|
65
67
|
target = target()
|
66
68
|
|
67
69
|
opts = Slop.parse!(args) do |opt|
|
68
|
-
opt.banner
|
69
|
-
|
70
|
-
|
70
|
+
opt.banner unindent <<-USAGE
|
71
|
+
Usage: show-command [OPTIONS] [CMD]
|
72
|
+
Show the source for command CMD.
|
73
|
+
e.g: show-command show-method
|
74
|
+
USAGE
|
71
75
|
|
72
76
|
opt.on :l, "line-numbers", "Show line numbers."
|
73
77
|
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
|
@@ -110,70 +114,86 @@ class Pry
|
|
110
114
|
|
111
115
|
command "edit", "Invoke the default editor on a file. Type `edit --help` for more info" do |*args|
|
112
116
|
opts = Slop.parse!(args) do |opt|
|
113
|
-
opt.banner
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
opt.on :ex, "Open
|
121
|
-
opt.on :t,
|
122
|
-
opt.on :
|
123
|
-
opt.on :
|
117
|
+
opt.banner unindent <<-USAGE
|
118
|
+
Usage: edit [--no-reload|--reload] [--line LINE] [--temp|--ex|FILE[:LINE]]
|
119
|
+
Open a text editor. When no FILE is given, edits the pry input buffer.
|
120
|
+
Ensure #{text.bold("Pry.config.editor")} is set to your editor of choice.
|
121
|
+
e.g: edit sample.rb
|
122
|
+
USAGE
|
123
|
+
|
124
|
+
opt.on :e, :ex, "Open the file that raised the most recent exception (_ex_.file)", :optional => true, :as => Integer
|
125
|
+
opt.on :t, :temp, "Open an empty temporary file"
|
126
|
+
opt.on :l, :line, "Jump to this line in the opened file", true, :as => Integer
|
127
|
+
opt.on :n, :"no-reload", "Don't automatically reload the edited code"
|
128
|
+
opt.on :r, :reload, "Reload the edited code immediately (default for ruby files)"
|
124
129
|
opt.on :h, :help, "This message." do
|
125
130
|
output.puts opt
|
126
131
|
end
|
127
132
|
end
|
128
133
|
next if opts.h?
|
129
134
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
if opts.ex?
|
134
|
-
next output.puts "No Exception found." if _pry_.last_exception.nil?
|
135
|
+
if [opts.ex? || nil, opts.t? || nil, !args.empty? || nil].compact.size > 1
|
136
|
+
next output.puts "Only one of --ex, --temp, and FILE may be specified"
|
137
|
+
end
|
135
138
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
139
|
+
# edit of local code, eval'd within pry.
|
140
|
+
if !opts.ex? && args.empty?
|
141
|
+
|
142
|
+
content = if opts.t?
|
143
|
+
""
|
144
|
+
elsif eval_string.strip != ""
|
145
|
+
eval_string
|
146
|
+
else
|
147
|
+
_pry_.input_array.reverse_each.find{ |x| x && x.strip != "" } || ""
|
148
|
+
end
|
149
|
+
|
150
|
+
line = content.lines.count
|
151
|
+
|
152
|
+
temp_file do |f|
|
153
|
+
f.puts(content)
|
154
|
+
f.flush
|
155
|
+
invoke_editor(f.path, line)
|
156
|
+
if !opts.n?
|
157
|
+
silence_warnings do
|
158
|
+
eval_string.replace(File.read(f.path))
|
159
|
+
end
|
160
|
+
end
|
140
161
|
end
|
141
162
|
|
142
|
-
|
143
|
-
|
144
|
-
|
163
|
+
# edit of remote code, eval'd at top-level
|
164
|
+
else
|
165
|
+
if opts.ex?
|
166
|
+
next output.puts "No Exception found." if _pry_.last_exception.nil?
|
167
|
+
ex = _pry_.last_exception
|
168
|
+
bt_index = opts[:ex].to_i
|
169
|
+
|
170
|
+
ex_file, ex_line = ex.bt_source_location_for(bt_index)
|
171
|
+
if ex_file && is_core_rbx_path?(ex_file)
|
172
|
+
file_name = rbx_convert_path_to_full(ex_file)
|
173
|
+
else
|
174
|
+
file_name = ex_file
|
175
|
+
end
|
176
|
+
|
177
|
+
line = ex_line
|
178
|
+
next output.puts "Exception has no associated file." if file_name.nil?
|
179
|
+
next output.puts "Cannot edit exceptions raised in REPL." if Pry.eval_path == file_name
|
145
180
|
|
146
|
-
|
181
|
+
else
|
182
|
+
# break up into file:line
|
183
|
+
file_name = File.expand_path(args.first)
|
147
184
|
|
148
|
-
|
149
|
-
file_name = temp_file do |f|
|
150
|
-
f.puts eval_string if !eval_string.empty?
|
185
|
+
line = file_name.sub!(/:(\d+)$/, "") ? $1.to_i : 1
|
151
186
|
end
|
152
|
-
line = eval_string.lines.count + 1
|
153
|
-
should_reload_locally = opts[:n] ? false : true
|
154
|
-
else
|
155
|
-
# break up into file:line
|
156
|
-
/(:(\d+))?$/ =~ File.expand_path(args.first)
|
157
187
|
|
158
|
-
|
159
|
-
file_name, line = [$`, $2]
|
160
|
-
line = line ? line.to_i : opts[:l].to_i
|
161
|
-
end
|
188
|
+
line = opts[:l].to_i if opts.l?
|
162
189
|
|
163
|
-
|
164
|
-
|
190
|
+
invoke_editor(file_name, line)
|
191
|
+
set_file_and_dir_locals(file_name)
|
165
192
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
elsif should_reload_locally
|
171
|
-
silence_warnings do
|
172
|
-
eval_string.replace(File.read(file_name))
|
173
|
-
end
|
174
|
-
elsif should_reload_at_top_level
|
175
|
-
silence_warnings do
|
176
|
-
TOPLEVEL_BINDING.eval(File.read(file_name), file_name)
|
193
|
+
if opts.r? || ((opts.ex? || file_name.end_with?(".rb")) && !opts.n?)
|
194
|
+
silence_warnings do
|
195
|
+
TOPLEVEL_BINDING.eval(File.read(file_name), file_name)
|
196
|
+
end
|
177
197
|
end
|
178
198
|
end
|
179
199
|
end
|
@@ -182,15 +202,18 @@ class Pry
|
|
182
202
|
target = target()
|
183
203
|
|
184
204
|
opts = Slop.parse!(args) do |opt|
|
185
|
-
opt.banner
|
186
|
-
|
187
|
-
|
188
|
-
|
205
|
+
opt.banner unindent <<-USAGE
|
206
|
+
Usage: edit-method [OPTIONS] [METH]
|
207
|
+
Edit the method METH in an editor.
|
208
|
+
Ensure #{text.bold("Pry.config.editor")} is set to your editor of choice.
|
209
|
+
e.g: edit-method hello_method
|
210
|
+
USAGE
|
189
211
|
|
190
212
|
opt.on :M, "instance-methods", "Operate on instance methods."
|
191
213
|
opt.on :m, :methods, "Operate on methods."
|
192
214
|
opt.on :n, "no-reload", "Do not automatically reload the method's file after editing."
|
193
215
|
opt.on "no-jump", "Do not fast forward editor to first line of method."
|
216
|
+
opt.on :p, :patch, "Instead of editing the method's file, try to edit in a tempfile and apply as a monkey patch."
|
194
217
|
opt.on :c, :context, "Select object context to run under.", true do |context|
|
195
218
|
target = Pry.binding_for(target.eval(context))
|
196
219
|
end
|
@@ -201,20 +224,42 @@ class Pry
|
|
201
224
|
|
202
225
|
next if opts.help?
|
203
226
|
|
227
|
+
if !Pry.config.editor
|
228
|
+
output.puts "Error: No editor set!"
|
229
|
+
output.puts "Ensure that #{text.bold("Pry.config.editor")} is set to your editor of choice."
|
230
|
+
next
|
231
|
+
end
|
232
|
+
|
204
233
|
meth_name = args.shift
|
205
|
-
|
234
|
+
meth_name, target, type = get_method_attributes(meth_name, target, opts.to_hash(true))
|
235
|
+
meth = get_method_object_from_target(meth_name, target, type)
|
236
|
+
|
237
|
+
if meth.nil?
|
206
238
|
output.puts "Invalid method name: #{meth_name}."
|
207
239
|
next
|
208
240
|
end
|
209
241
|
|
210
|
-
|
242
|
+
if opts.p? || is_a_dynamically_defined_method?(meth)
|
243
|
+
code, _ = code_and_code_type_for(meth)
|
244
|
+
|
245
|
+
lines = code.lines.to_a
|
246
|
+
if lines[0] =~ /^def [^( \n]+/
|
247
|
+
lines[0] = "def #{meth_name}#{$'}"
|
248
|
+
else
|
249
|
+
next output.puts "Error: Pry can only patch methods created with the `def` keyword."
|
250
|
+
end
|
251
|
+
|
252
|
+
temp_file do |f|
|
253
|
+
f.puts lines.join
|
254
|
+
f.flush
|
255
|
+
invoke_editor(f.path, 0)
|
256
|
+
Pry.new(:input => StringIO.new(File.read(f.path))).rep(meth.owner)
|
257
|
+
end
|
258
|
+
next
|
259
|
+
end
|
211
260
|
|
212
261
|
if is_a_c_method?(meth)
|
213
262
|
output.puts "Error: Can't edit a C method."
|
214
|
-
elsif is_a_dynamically_defined_method?(meth)
|
215
|
-
output.puts "Error: Can't edit an eval method."
|
216
|
-
|
217
|
-
# editor is invoked here
|
218
263
|
else
|
219
264
|
file, line = path_line_for(meth)
|
220
265
|
set_file_and_dir_locals(file)
|
@@ -11,11 +11,8 @@ class Pry
|
|
11
11
|
rescue Errno::ENOENT
|
12
12
|
output.puts "No such directory: #{dest}"
|
13
13
|
end
|
14
|
-
|
15
14
|
else
|
16
|
-
|
17
|
-
output.puts "Error: there was a problem executing system command: #{cmd}"
|
18
|
-
end
|
15
|
+
Pry.config.system.call(output, cmd, _pry_)
|
19
16
|
end
|
20
17
|
end
|
21
18
|
|
@@ -32,12 +29,13 @@ class Pry
|
|
32
29
|
end
|
33
30
|
end
|
34
31
|
|
35
|
-
alias_command "file-mode", "shell-mode"
|
32
|
+
alias_command "file-mode", "shell-mode"
|
36
33
|
|
37
34
|
command "cat", "Show output of file FILE. Type `cat --help` for more information." do |*args|
|
38
35
|
start_line = 0
|
39
36
|
end_line = -1
|
40
37
|
file_name = nil
|
38
|
+
bt_index = 0
|
41
39
|
|
42
40
|
opts = Slop.parse!(args) do |opt|
|
43
41
|
opt.on :s, :start, "Start line (defaults to start of file)Line 1 is the first line.", true, :as => Integer do |line|
|
@@ -48,17 +46,25 @@ class Pry
|
|
48
46
|
end_line = line - 1
|
49
47
|
end
|
50
48
|
|
51
|
-
opt.on :ex, "Show a window of N lines either side of the last exception (defaults to 5).", :optional => true, :as => Integer do |
|
52
|
-
window_size
|
49
|
+
opt.on :ex, "Show a window of N lines either side of the last exception (defaults to 5).", :optional => true, :as => Integer do |bt_index_arg|
|
50
|
+
window_size = Pry.config.exception_window_size || 5
|
53
51
|
ex = _pry_.last_exception
|
54
52
|
next if !ex
|
55
|
-
|
53
|
+
if bt_index_arg
|
54
|
+
bt_index = bt_index_arg
|
55
|
+
else
|
56
|
+
bt_index = ex.bt_index
|
57
|
+
end
|
58
|
+
ex.bt_index = (bt_index + 1) % ex.backtrace.size
|
59
|
+
|
60
|
+
ex_file, ex_line = ex.bt_source_location_for(bt_index)
|
61
|
+
start_line = (ex_line - 1) - window_size
|
56
62
|
start_line = start_line < 0 ? 0 : start_line
|
57
|
-
end_line = (
|
58
|
-
if is_core_rbx_path?(
|
59
|
-
file_name = rbx_convert_path_to_full(
|
63
|
+
end_line = (ex_line - 1) + window_size
|
64
|
+
if ex_file && is_core_rbx_path?(ex_file)
|
65
|
+
file_name = rbx_convert_path_to_full(ex_file)
|
60
66
|
else
|
61
|
-
file_name =
|
67
|
+
file_name = ex_file
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
@@ -74,7 +80,6 @@ class Pry
|
|
74
80
|
|
75
81
|
if opts.ex?
|
76
82
|
next output.puts "No Exception or Exception has no associated file." if file_name.nil?
|
77
|
-
next output.puts "Cannot cat exceptions raised in REPL." if Pry.eval_path == file_name
|
78
83
|
else
|
79
84
|
file_name = args.shift
|
80
85
|
end
|
@@ -84,8 +89,13 @@ class Pry
|
|
84
89
|
next
|
85
90
|
end
|
86
91
|
|
87
|
-
|
88
|
-
|
92
|
+
begin
|
93
|
+
contents, _, _ = read_between_the_lines(file_name, start_line, end_line)
|
94
|
+
contents = syntax_highlight_by_file_type_or_specified(contents, file_name, opts[:type])
|
95
|
+
rescue Errno::ENOENT
|
96
|
+
output.puts "Could not find file: #{file_name}"
|
97
|
+
next
|
98
|
+
end
|
89
99
|
|
90
100
|
if opts.l?
|
91
101
|
contents = text.with_line_numbers contents, start_line + 1
|
@@ -93,11 +103,12 @@ class Pry
|
|
93
103
|
|
94
104
|
# add the arrow pointing to line that caused the exception
|
95
105
|
if opts.ex?
|
106
|
+
ex_file, ex_line = _pry_.last_exception.bt_source_location_for(bt_index)
|
96
107
|
contents = text.with_line_numbers contents, start_line + 1, :bright_red
|
97
108
|
|
98
109
|
contents = contents.lines.each_with_index.map do |line, idx|
|
99
110
|
l = idx + start_line
|
100
|
-
if l == (
|
111
|
+
if l == (ex_line - 1)
|
101
112
|
" =>#{line}"
|
102
113
|
else
|
103
114
|
" #{line}"
|
@@ -106,7 +117,7 @@ class Pry
|
|
106
117
|
|
107
118
|
# header for exceptions
|
108
119
|
output.puts "\n#{Pry::Helpers::Text.bold('Exception:')} #{_pry_.last_exception.class}: #{_pry_.last_exception.message}\n--"
|
109
|
-
output.puts "#{Pry::Helpers::Text.bold('From:')} #{
|
120
|
+
output.puts "#{Pry::Helpers::Text.bold('From:')} #{ex_file} @ line #{ex_line} @ #{text.bold('level: ')} #{bt_index} of backtrace (of #{_pry_.last_exception.backtrace.size - 1}).\n\n"
|
110
121
|
end
|
111
122
|
|
112
123
|
set_file_and_dir_locals(file_name)
|
@@ -44,7 +44,6 @@ class Pry
|
|
44
44
|
def temp_file
|
45
45
|
file = Tempfile.new(["tmp", ".rb"])
|
46
46
|
yield file
|
47
|
-
file.path
|
48
47
|
ensure
|
49
48
|
file.close
|
50
49
|
end
|
@@ -178,37 +177,43 @@ class Pry
|
|
178
177
|
[doc, code_type]
|
179
178
|
end
|
180
179
|
|
181
|
-
def get_method_object(meth_name, target, options)
|
180
|
+
def get_method_object(meth_name, target=nil, options={})
|
181
|
+
get_method_object_from_target(*get_method_attributes(meth_name, target, options)) rescue nil
|
182
|
+
end
|
183
|
+
|
184
|
+
def get_method_attributes(meth_name, target=nil, options={})
|
182
185
|
if meth_name
|
183
186
|
if meth_name =~ /(\S+)\#(\S+)\Z/
|
184
187
|
context, meth_name = $1, $2
|
185
188
|
target = Pry.binding_for(target.eval(context))
|
186
|
-
|
187
|
-
options[:methods] = false
|
189
|
+
type = :instance
|
188
190
|
elsif meth_name =~ /(\S+)\.(\S+)\Z/
|
189
191
|
context, meth_name = $1, $2
|
190
192
|
target = Pry.binding_for(target.eval(context))
|
191
|
-
|
192
|
-
|
193
|
+
type = :singleton
|
194
|
+
elsif options["instance_methods"]
|
195
|
+
type = :instance
|
196
|
+
elsif options[:methods]
|
197
|
+
type = :singleton
|
198
|
+
else
|
199
|
+
type = nil
|
193
200
|
end
|
194
201
|
else
|
195
202
|
meth_name = meth_name_from_binding(target)
|
203
|
+
type = nil
|
196
204
|
end
|
205
|
+
[meth_name, target, type]
|
206
|
+
end
|
197
207
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
if options["instance-methods"]
|
208
|
+
def get_method_object_from_target(meth_name, target, type=nil)
|
209
|
+
case type
|
210
|
+
when :instance
|
203
211
|
target.eval("instance_method(:#{meth_name})") rescue nil
|
204
|
-
|
212
|
+
when :singleton
|
205
213
|
target.eval("method(:#{meth_name})") rescue nil
|
206
214
|
else
|
207
|
-
|
208
|
-
|
209
|
-
rescue
|
210
|
-
target.eval("method(:#{meth_name})") rescue nil
|
211
|
-
end
|
215
|
+
get_method_object_from_target(meth_name, target, :instance) ||
|
216
|
+
get_method_object_from_target(meth_name, target, :singleton)
|
212
217
|
end
|
213
218
|
end
|
214
219
|
|
@@ -305,7 +310,11 @@ class Pry
|
|
305
310
|
# returns the file content between the lines and the normalized
|
306
311
|
# start and end line numbers.
|
307
312
|
def read_between_the_lines(file_name, start_line, end_line)
|
308
|
-
|
313
|
+
if file_name == Pry.eval_path
|
314
|
+
content = Pry.line_buffer.drop(1).join
|
315
|
+
else
|
316
|
+
content = File.read(File.expand_path(file_name))
|
317
|
+
end
|
309
318
|
lines_array = content.each_line.to_a
|
310
319
|
|
311
320
|
[lines_array[start_line..end_line].join, normalized_line_number(start_line, lines_array.size),
|
@@ -372,6 +381,7 @@ class Pry
|
|
372
381
|
else
|
373
382
|
editor_invocation = "#{Pry.config.editor} #{start_line_syntax_for_editor(file, line)}"
|
374
383
|
end
|
384
|
+
return nil unless editor_invocation
|
375
385
|
|
376
386
|
if jruby?
|
377
387
|
begin
|
@@ -379,18 +389,23 @@ class Pry
|
|
379
389
|
pid = Spoon.spawnp(*editor_invocation.split)
|
380
390
|
Process.waitpid(pid)
|
381
391
|
rescue FFI::NotFoundError
|
382
|
-
|
392
|
+
system(editor_invocation)
|
383
393
|
end
|
384
394
|
else
|
385
|
-
|
395
|
+
# Note we dont want to use Pry.config.system here as that
|
396
|
+
# may be invoked non-interactively (i.e via Open4), whereas we want to
|
397
|
+
# ensure the editor is always interactive
|
398
|
+
system(editor_invocation)
|
386
399
|
end
|
387
400
|
end
|
388
401
|
|
402
|
+
# Return the syntax for a given editor for starting the editor
|
403
|
+
# and moving to a particular line within that file
|
389
404
|
def start_line_syntax_for_editor(file_name, line_number)
|
390
405
|
file_name = file_name.gsub(/\//, '\\') if RUBY_PLATFORM =~ /mswin|mingw/
|
391
406
|
|
392
|
-
# special case
|
393
|
-
return file_name if line_number <=
|
407
|
+
# special case for 1st line
|
408
|
+
return file_name if line_number <= 1
|
394
409
|
|
395
410
|
case Pry.config.editor
|
396
411
|
when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
|
@@ -410,27 +425,45 @@ class Pry
|
|
410
425
|
end
|
411
426
|
end
|
412
427
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
428
|
+
# Remove any common leading whitespace from every line in `text`.
|
429
|
+
#
|
430
|
+
# This can be used to make a HEREDOC line up with the left margin, without
|
431
|
+
# sacrificing the indentation level of the source code.
|
432
|
+
#
|
433
|
+
# e.g.
|
434
|
+
# opt.banner unindent <<-USAGE
|
435
|
+
# Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
436
|
+
# sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
437
|
+
# "Ut enim ad minim veniam."
|
438
|
+
# USAGE
|
439
|
+
#
|
440
|
+
# @param [String] The text from which to remove indentation
|
441
|
+
# @return [String], The text with indentation stripped.
|
442
|
+
#
|
443
|
+
# @copyright Heavily based on textwrap.dedent from Python, which is:
|
444
|
+
# Copyright (C) 1999-2001 Gregory P. Ward.
|
445
|
+
# Copyright (C) 2002, 2003 Python Software Foundation.
|
446
|
+
# Written by Greg Ward <gward@python.net>
|
447
|
+
#
|
448
|
+
# Licensed under <http://docs.python.org/license.html>
|
449
|
+
# From <http://hg.python.org/cpython/file/6b9f0a6efaeb/Lib/textwrap.py>
|
450
|
+
#
|
451
|
+
def unindent(text)
|
452
|
+
# Empty blank lines
|
453
|
+
text = text.sub(/^[ \t]+$/, '')
|
454
|
+
|
455
|
+
# Find the longest common whitespace to all indented lines
|
456
|
+
margin = text.scan(/^[ \t]*(?=[^ \t\n])/).inject do |current_margin, next_indent|
|
457
|
+
if next_indent.start_with?(current_margin)
|
458
|
+
current_margin
|
459
|
+
elsif current_margin.start_with?(next_indent)
|
460
|
+
next_indent
|
430
461
|
else
|
431
|
-
|
462
|
+
""
|
432
463
|
end
|
433
464
|
end
|
465
|
+
|
466
|
+
text.gsub(/^#{margin}/, '')
|
434
467
|
end
|
435
468
|
|
436
469
|
end
|