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/pry_instance.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require "pry/command_processor.rb"
|
2
1
|
require "pry/indent"
|
3
2
|
|
4
3
|
class Pry
|
@@ -8,7 +7,6 @@ class Pry
|
|
8
7
|
attr_accessor :commands
|
9
8
|
attr_accessor :print
|
10
9
|
attr_accessor :exception_handler
|
11
|
-
attr_accessor :hooks
|
12
10
|
attr_accessor :input_stack
|
13
11
|
|
14
12
|
attr_accessor :custom_completions
|
@@ -16,13 +14,32 @@ class Pry
|
|
16
14
|
attr_accessor :binding_stack
|
17
15
|
|
18
16
|
attr_accessor :last_result
|
19
|
-
attr_accessor :last_exception
|
20
17
|
attr_accessor :last_file
|
21
18
|
attr_accessor :last_dir
|
22
19
|
|
20
|
+
attr_reader :last_exception
|
21
|
+
|
23
22
|
attr_reader :input_array
|
24
23
|
attr_reader :output_array
|
25
24
|
|
25
|
+
attr_accessor :backtrace
|
26
|
+
|
27
|
+
# Special treatment for hooks as we want to alert people of the
|
28
|
+
# changed API
|
29
|
+
attr_reader :hooks
|
30
|
+
|
31
|
+
# FIXME:
|
32
|
+
# This is a hack to alert people of the new API.
|
33
|
+
# @param [Pry::Hooks] v Only accept `Pry::Hooks` now!
|
34
|
+
def hooks=(v)
|
35
|
+
if v.is_a?(Hash)
|
36
|
+
warn "Hash-based hooks are now deprecated! Use a `Pry::Hooks` object instead! http://rubydoc.info/github/pry/pry/master/Pry/Hooks"
|
37
|
+
@hooks = Pry::Hooks.from_hash(v)
|
38
|
+
else
|
39
|
+
@hooks = v
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
26
43
|
# Create a new `Pry` object.
|
27
44
|
# @param [Hash] options The optional configuration parameters.
|
28
45
|
# @option options [#readline] :input The object to use for input.
|
@@ -35,7 +52,6 @@ class Pry
|
|
35
52
|
def initialize(options={})
|
36
53
|
refresh(options)
|
37
54
|
|
38
|
-
@command_processor = CommandProcessor.new(self)
|
39
55
|
@binding_stack = []
|
40
56
|
@indent = Pry::Indent.new
|
41
57
|
end
|
@@ -57,7 +73,7 @@ class Pry
|
|
57
73
|
end
|
58
74
|
|
59
75
|
defaults.merge!(options).each do |key, value|
|
60
|
-
send
|
76
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
61
77
|
end
|
62
78
|
|
63
79
|
true
|
@@ -106,21 +122,14 @@ class Pry
|
|
106
122
|
@output_array = Pry::HistoryArray.new(size)
|
107
123
|
end
|
108
124
|
|
109
|
-
# Execute the hook `hook_name`, if it is defined.
|
110
|
-
# @param [Symbol] hook_name The hook to execute
|
111
|
-
# @param [Array] args The arguments to pass to the hook.
|
112
|
-
def exec_hook(hook_name, *args, &block)
|
113
|
-
hooks[hook_name].call(*args, &block) if hooks[hook_name]
|
114
|
-
end
|
115
|
-
|
116
125
|
# Make sure special locals exist at start of session
|
117
126
|
def initialize_special_locals(target)
|
118
127
|
inject_local("_in_", @input_array, target)
|
119
128
|
inject_local("_out_", @output_array, target)
|
120
129
|
inject_local("_pry_", self, target)
|
121
|
-
inject_local("_ex_",
|
122
|
-
inject_local("_file_",
|
123
|
-
inject_local("_dir_",
|
130
|
+
inject_local("_ex_", last_exception, target)
|
131
|
+
inject_local("_file_", last_file, target)
|
132
|
+
inject_local("_dir_", last_dir, target)
|
124
133
|
|
125
134
|
# without this line we get 1 test failure, ask Mon_Ouie
|
126
135
|
set_last_result(nil, target)
|
@@ -136,13 +145,13 @@ class Pry
|
|
136
145
|
|
137
146
|
def special_locals
|
138
147
|
{
|
139
|
-
:_in_
|
140
|
-
:_out_
|
141
|
-
:_pry_
|
142
|
-
:_ex_
|
148
|
+
:_in_ => @input_array,
|
149
|
+
:_out_ => @output_array,
|
150
|
+
:_pry_ => self,
|
151
|
+
:_ex_ => last_exception,
|
143
152
|
:_file_ => last_file,
|
144
|
-
:_dir_
|
145
|
-
:_
|
153
|
+
:_dir_ => last_dir,
|
154
|
+
:_ => last_result
|
146
155
|
}
|
147
156
|
end
|
148
157
|
|
@@ -188,8 +197,9 @@ class Pry
|
|
188
197
|
end
|
189
198
|
end
|
190
199
|
|
200
|
+
break_data || nil
|
201
|
+
ensure
|
191
202
|
repl_epilogue(target)
|
192
|
-
break_data || target_self
|
193
203
|
end
|
194
204
|
|
195
205
|
# Perform a read-eval-print.
|
@@ -232,15 +242,16 @@ class Pry
|
|
232
242
|
|
233
243
|
code = r(target)
|
234
244
|
|
235
|
-
result =
|
245
|
+
result = target.eval(code, Pry.eval_path, Pry.current_line)
|
246
|
+
set_last_result(result, target, code)
|
247
|
+
|
236
248
|
result
|
237
|
-
rescue CommandError, Slop::InvalidOptionError => e
|
238
|
-
output.puts "Error: #{e.message}"
|
239
|
-
@suppress_output = true
|
240
249
|
rescue RescuableException => e
|
241
|
-
|
250
|
+
self.last_exception = e
|
251
|
+
e
|
242
252
|
ensure
|
243
253
|
update_input_history(code)
|
254
|
+
exec_hook :after_eval, result, self
|
244
255
|
end
|
245
256
|
|
246
257
|
# Perform a read.
|
@@ -259,16 +270,24 @@ class Pry
|
|
259
270
|
|
260
271
|
val = ""
|
261
272
|
loop do
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
273
|
+
begin
|
274
|
+
# eval_string will probably be mutated by this method
|
275
|
+
retrieve_line(eval_string, target)
|
276
|
+
rescue CommandError, Slop::InvalidOptionError => e
|
277
|
+
output.puts "Error: #{e.message}"
|
278
|
+
end
|
266
279
|
|
267
|
-
|
280
|
+
begin
|
281
|
+
break if complete_expression?(eval_string)
|
282
|
+
rescue SyntaxError => e
|
283
|
+
output.puts "SyntaxError: #{e.message.sub(/.*syntax error, */m, '')}"
|
284
|
+
eval_string = ""
|
285
|
+
end
|
268
286
|
end
|
269
287
|
|
270
288
|
@suppress_output = true if eval_string =~ /;\Z/ || eval_string.empty?
|
271
289
|
|
290
|
+
exec_hook :after_read, eval_string, self
|
272
291
|
eval_string
|
273
292
|
end
|
274
293
|
|
@@ -294,10 +313,15 @@ class Pry
|
|
294
313
|
end
|
295
314
|
end
|
296
315
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
316
|
+
def should_force_encoding?(eval_string, val)
|
317
|
+
eval_string.empty? && val.respond_to?(:encoding) && val.encoding != eval_string.encoding
|
318
|
+
end
|
319
|
+
private :should_force_encoding?
|
320
|
+
|
321
|
+
# Read and process a line of input -- check for ^D, determine which prompt to
|
322
|
+
# use, rewrite the indentation if `Pry.config.auto_indent` is enabled, and,
|
323
|
+
# if the line is a command, process it and alter the eval_string accordingly.
|
324
|
+
# This method should not need to be invoked directly.
|
301
325
|
#
|
302
326
|
# @param [String] eval_string The cumulative lines of input.
|
303
327
|
# @param [Binding] target The target of the session.
|
@@ -305,45 +329,60 @@ class Pry
|
|
305
329
|
def retrieve_line(eval_string, target)
|
306
330
|
@indent.reset if eval_string.empty?
|
307
331
|
|
308
|
-
current_prompt = select_prompt(eval_string
|
332
|
+
current_prompt = select_prompt(eval_string, target)
|
309
333
|
indentation = Pry.config.auto_indent ? @indent.indent_level : ''
|
310
334
|
|
311
|
-
val = readline(current_prompt
|
335
|
+
val = readline("#{current_prompt}#{indentation}")
|
312
336
|
|
313
337
|
# invoke handler if we receive EOF character (^D)
|
314
338
|
if !val
|
315
339
|
output.puts ""
|
316
340
|
Pry.config.control_d_handler.call(eval_string, self)
|
317
|
-
|
318
|
-
|
319
|
-
# Change the eval_string into the input encoding (Issue 284)
|
320
|
-
# TODO: This wouldn't be necessary if the eval_string was constructed from
|
321
|
-
# input strings only.
|
322
|
-
if eval_string.empty? && val.respond_to?(:encoding) && val.encoding != eval_string.encoding
|
323
|
-
eval_string.force_encoding(val.encoding)
|
324
|
-
end
|
341
|
+
return
|
342
|
+
end
|
325
343
|
|
326
|
-
|
327
|
-
|
328
|
-
|
344
|
+
# Change the eval_string into the input encoding (Issue 284)
|
345
|
+
# TODO: This wouldn't be necessary if the eval_string was constructed from
|
346
|
+
# input strings only.
|
347
|
+
if should_force_encoding?(eval_string, val)
|
348
|
+
eval_string.force_encoding(val.encoding)
|
349
|
+
end
|
329
350
|
|
330
|
-
|
331
|
-
|
332
|
-
|
351
|
+
if Pry.config.auto_indent && !input.is_a?(StringIO)
|
352
|
+
original_val = "#{indentation}#{val}"
|
353
|
+
indented_val = @indent.indent(val)
|
354
|
+
|
355
|
+
if original_val != indented_val && output.tty? && Pry::Helpers::BaseHelpers.use_ansi_codes? && Pry.config.correct_indent
|
356
|
+
output.print @indent.correct_indentation(current_prompt + indented_val, original_val.length - indented_val.length)
|
357
|
+
output.flush
|
333
358
|
end
|
359
|
+
else
|
360
|
+
indented_val = val
|
361
|
+
end
|
334
362
|
|
335
|
-
|
336
|
-
val
|
363
|
+
begin
|
364
|
+
if !process_command(val, eval_string, target)
|
365
|
+
eval_string << "#{indented_val.rstrip}\n" unless val.empty?
|
366
|
+
end
|
367
|
+
ensure
|
368
|
+
Pry.history << indented_val unless input.is_a?(StringIO)
|
337
369
|
end
|
338
370
|
end
|
339
371
|
|
340
|
-
#
|
372
|
+
# If the given line is a valid command, process it in the context of the
|
373
|
+
# current `eval_string` and context.
|
341
374
|
# This method should not need to be invoked directly.
|
342
375
|
# @param [String] val The line to process.
|
343
376
|
# @param [String] eval_string The cumulative lines of input.
|
344
377
|
# @param [Binding] target The target of the Pry session.
|
345
|
-
|
346
|
-
|
378
|
+
# @return [Boolean] `true` if `val` is a command, `false` otherwise
|
379
|
+
def process_command(val, eval_string, target)
|
380
|
+
result = commands.process_line(val, {
|
381
|
+
:target => target,
|
382
|
+
:output => output,
|
383
|
+
:eval_string => eval_string,
|
384
|
+
:pry_instance => self
|
385
|
+
})
|
347
386
|
|
348
387
|
# set a temporary (just so we can inject the value we want into eval_string)
|
349
388
|
Thread.current[:__pry_cmd_result__] = result
|
@@ -351,16 +390,16 @@ class Pry
|
|
351
390
|
# note that `result` wraps the result of command processing; if a
|
352
391
|
# command was matched and invoked then `result.command?` returns true,
|
353
392
|
# otherwise it returns false.
|
354
|
-
if result.command?
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
393
|
+
if result.command?
|
394
|
+
if !result.void_command?
|
395
|
+
# the command that was invoked was non-void (had a return value) and so we make
|
396
|
+
# the value of the current expression equal to the return value
|
397
|
+
# of the command.
|
398
|
+
eval_string.replace "Thread.current[:__pry_cmd_result__].retval\n"
|
399
|
+
end
|
400
|
+
true
|
360
401
|
else
|
361
|
-
|
362
|
-
# so this ignores their result
|
363
|
-
eval_string << "#{val.rstrip}\n" if !val.empty?
|
402
|
+
false
|
364
403
|
end
|
365
404
|
end
|
366
405
|
|
@@ -368,36 +407,63 @@ class Pry
|
|
368
407
|
# @param [String] val The command (and its params) to execute.
|
369
408
|
# @param [String] eval_string The current input buffer.
|
370
409
|
# @param [Binding] target The binding to use..
|
371
|
-
# @return [Pry::
|
410
|
+
# @return [Pry::Command::VOID_VALUE]
|
372
411
|
# @example
|
373
412
|
# pry_instance.run_command("ls -m")
|
374
413
|
def run_command(val, eval_string = "", target = binding_stack.last)
|
375
|
-
process_line(val,
|
376
|
-
|
414
|
+
commands.process_line(val,
|
415
|
+
:eval_string => eval_string,
|
416
|
+
:target => target,
|
417
|
+
:pry_instance => self,
|
418
|
+
:output => output
|
419
|
+
)
|
420
|
+
Pry::Command::VOID_VALUE
|
421
|
+
end
|
422
|
+
|
423
|
+
# Execute the specified hook.
|
424
|
+
# @param [Symbol] name The hook name to execute
|
425
|
+
# @param [*Object] args The arguments to pass to the hook
|
426
|
+
# @return [Object, Exception] The return value of the hook or the exception raised
|
427
|
+
#
|
428
|
+
# If executing a hook raises an exception, we log that and then continue sucessfully.
|
429
|
+
# To debug such errors, use the global variable $pry_hook_error, which is set as a
|
430
|
+
# result.
|
431
|
+
def exec_hook(name, *args, &block)
|
432
|
+
e_before = hooks.errors.size
|
433
|
+
hooks.exec_hook(name, *args, &block).tap do
|
434
|
+
hooks.errors[e_before..-1].each do |e|
|
435
|
+
output.puts "#{name} hook failed: #{e.class}: #{e.message}"
|
436
|
+
output.puts "#{e.backtrace.first}"
|
437
|
+
output.puts "(see _pry_.hooks.errors to debug)"
|
438
|
+
end
|
439
|
+
end
|
377
440
|
end
|
378
441
|
|
379
442
|
# Set the last result of an eval.
|
380
443
|
# This method should not need to be invoked directly.
|
381
444
|
# @param [Object] result The result.
|
382
445
|
# @param [Binding] target The binding to set `_` on.
|
383
|
-
|
446
|
+
# @param [String] code The code that was run.
|
447
|
+
def set_last_result(result, target, code="")
|
384
448
|
@last_result_is_exception = false
|
385
449
|
@output_array << result
|
386
450
|
|
387
|
-
self.last_result = result
|
451
|
+
self.last_result = result unless code =~ /\A\s*\z/
|
388
452
|
end
|
389
453
|
|
390
454
|
# Set the last exception for a session.
|
391
|
-
#
|
392
|
-
|
393
|
-
# @param [Binding] target The binding to set `_ex_` on.
|
394
|
-
def set_last_exception(ex, target)
|
455
|
+
# @param [Exception] ex
|
456
|
+
def last_exception=(ex)
|
395
457
|
class << ex
|
396
458
|
attr_accessor :file, :line, :bt_index
|
397
459
|
def bt_source_location_for(index)
|
398
460
|
backtrace[index] =~ /(.*):(\d+)/
|
399
461
|
[$1, $2.to_i]
|
400
462
|
end
|
463
|
+
|
464
|
+
def inc_bt_index
|
465
|
+
@bt_index = (@bt_index + 1) % backtrace.size
|
466
|
+
end
|
401
467
|
end
|
402
468
|
|
403
469
|
ex.bt_index = 0
|
@@ -405,8 +471,7 @@ class Pry
|
|
405
471
|
|
406
472
|
@last_result_is_exception = true
|
407
473
|
@output_array << ex
|
408
|
-
|
409
|
-
self.last_exception = ex
|
474
|
+
@last_exception = ex
|
410
475
|
end
|
411
476
|
|
412
477
|
# Update Pry's internal state after evalling code.
|
@@ -480,14 +545,17 @@ class Pry
|
|
480
545
|
|
481
546
|
# Returns the appropriate prompt to use.
|
482
547
|
# This method should not need to be invoked directly.
|
483
|
-
# @param [
|
484
|
-
#
|
485
|
-
# @param [Object] target_self The receiver of the Pry session.
|
548
|
+
# @param [String] eval_string The current input buffer.
|
549
|
+
# @param [Binding] target The target Binding of the Pry session.
|
486
550
|
# @return [String] The prompt.
|
487
|
-
def select_prompt(
|
551
|
+
def select_prompt(eval_string, target)
|
552
|
+
target_self = target.eval('self')
|
488
553
|
|
489
|
-
|
554
|
+
# If input buffer is empty then use normal prompt
|
555
|
+
if eval_string.empty?
|
490
556
|
Array(prompt).first.call(target_self, binding_stack.size - 1, self)
|
557
|
+
|
558
|
+
# Otherwise use the wait prompt (indicating multi-line expression)
|
491
559
|
else
|
492
560
|
Array(prompt).last.call(target_self, binding_stack.size - 1, self)
|
493
561
|
end
|
@@ -526,45 +594,49 @@ class Pry
|
|
526
594
|
prompt_stack.size > 1 ? prompt_stack.pop : prompt
|
527
595
|
end
|
528
596
|
|
529
|
-
if
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
597
|
+
# Determine if a string of code is a complete Ruby expression.
|
598
|
+
# @param [String] code The code to validate.
|
599
|
+
# @return [Boolean] Whether or not the code is a complete Ruby expression.
|
600
|
+
# @raise [SyntaxError] Any SyntaxError that does not represent incompleteness.
|
601
|
+
# @example
|
602
|
+
# complete_expression?("class Hello") #=> false
|
603
|
+
# complete_expression?("class Hello; end") #=> true
|
604
|
+
def complete_expression?(str)
|
605
|
+
if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/
|
606
|
+
Rubinius::Melbourne19.parse_string(str, Pry.eval_path)
|
607
|
+
elsif defined?(Rubinius::Melbourne)
|
608
|
+
Rubinius::Melbourne.parse_string(str, Pry.eval_path)
|
609
|
+
else
|
610
|
+
catch(:valid) do
|
611
|
+
Helpers::BaseHelpers.silence_warnings do
|
612
|
+
eval("BEGIN{throw :valid}\n#{str}", binding, Pry.eval_path)
|
613
|
+
end
|
614
|
+
end
|
541
615
|
end
|
542
616
|
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
JRuby.parse(code)
|
548
|
-
true
|
549
|
-
rescue SyntaxError
|
617
|
+
# Assert that a line which ends with a , is incomplete.
|
618
|
+
str !~ /[,]\z/
|
619
|
+
rescue SyntaxError => e
|
620
|
+
if incomplete_user_input_exception?(e)
|
550
621
|
false
|
622
|
+
else
|
623
|
+
raise e
|
551
624
|
end
|
625
|
+
end
|
552
626
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
# NOTE: we're using .dup because RubyParser mutates the input
|
565
|
-
RubyParser.new.parse(code.dup)
|
627
|
+
# Check whether the exception indicates that the user should input more.
|
628
|
+
#
|
629
|
+
# @param [SyntaxError] the exception object that was raised.
|
630
|
+
# @param [Array<String>] The stack frame of the function that executed eval.
|
631
|
+
# @return [Boolean]
|
632
|
+
#
|
633
|
+
def incomplete_user_input_exception?(ex)
|
634
|
+
case ex.message
|
635
|
+
when /unexpected (\$end|end-of-file|END_OF_FILE)/, # mri, jruby, ironruby
|
636
|
+
/unterminated (quoted string|string|regexp) meets end of file/, # "quoted string" is ironruby
|
637
|
+
/missing 'end' for/, /: expecting '[})\]]'$/, /can't find string ".*" anywhere before EOF/, /expecting keyword_end/ # rbx
|
566
638
|
true
|
567
|
-
|
639
|
+
else
|
568
640
|
false
|
569
641
|
end
|
570
642
|
end
|