pry 0.9.5-i386-mingw32 → 0.9.6-i386-mingw32
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.
- 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 +73 -74
@@ -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
|