pry 0.8.3-i386-mingw32 → 0.8.4pre1-i386-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +2 -0
- data/.gitignore +8 -0
- data/.yardopts +1 -0
- data/README.markdown +10 -6
- data/Rakefile +15 -23
- data/TODO +62 -0
- data/bin/pry +3 -1
- data/lib/pry.rb +6 -7
- data/lib/pry/command_context.rb +29 -0
- data/lib/pry/command_processor.rb +15 -28
- data/lib/pry/command_set.rb +234 -0
- data/lib/pry/commands.rb +15 -861
- data/lib/pry/core_extensions.rb +40 -48
- data/lib/pry/default_commands/context.rb +127 -0
- data/lib/pry/default_commands/documentation.rb +145 -0
- data/lib/pry/default_commands/easter_eggs.rb +71 -0
- data/lib/pry/default_commands/gems.rb +59 -0
- data/lib/pry/default_commands/input.rb +38 -0
- data/lib/pry/default_commands/introspection.rb +190 -0
- data/lib/pry/default_commands/ls.rb +199 -0
- data/lib/pry/default_commands/shell.rb +90 -0
- data/lib/pry/helpers.rb +2 -0
- data/lib/pry/{command_base_helpers.rb → helpers/base_helpers.rb} +46 -21
- data/lib/pry/{command_helpers.rb → helpers/command_helpers.rb} +34 -36
- data/lib/pry/pry_class.rb +17 -11
- data/lib/pry/pry_instance.rb +59 -2
- data/lib/pry/version.rb +1 -1
- data/test/{test_helper.rb → helper.rb} +8 -2
- data/test/test_command_helpers.rb +77 -0
- data/test/test_commandset.rb +184 -0
- data/test/{test.rb → test_pry.rb} +164 -132
- data/wiki/Customizing-pry.md +397 -0
- data/wiki/Home.md +4 -0
- metadata +61 -41
- data/lib/pry/command_base.rb +0 -202
data/lib/pry/core_extensions.rb
CHANGED
@@ -1,55 +1,47 @@
|
|
1
|
-
class
|
2
|
-
|
1
|
+
class Object
|
2
|
+
# Start a Pry REPL.
|
3
|
+
# This method differs from `Pry.start` in that it does not
|
4
|
+
# support an options hash. Also, when no parameter is provided, the Pry
|
5
|
+
# session will start on the implied receiver rather than on
|
6
|
+
# top-level (as in the case of `Pry.start`).
|
7
|
+
# It has two forms of invocation. In the first form no parameter
|
8
|
+
# should be provided and it will start a pry session on the
|
9
|
+
# receiver. In the second form it should be invoked without an
|
10
|
+
# explicit receiver and one parameter; this will start a Pry
|
11
|
+
# session on the parameter.
|
12
|
+
# @param [Object, Binding] target The receiver of the Pry session.
|
13
|
+
# @example First form
|
14
|
+
# "dummy".pry
|
15
|
+
# @example Second form
|
16
|
+
# pry "dummy"
|
17
|
+
# @example Start a Pry session on current self (whatever that is)
|
18
|
+
# pry
|
19
|
+
def pry(target=self)
|
20
|
+
Pry.start(target)
|
21
|
+
end
|
3
22
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# top-level (as in the case of `Pry.start`).
|
9
|
-
# It has two forms of invocation. In the first form no parameter
|
10
|
-
# should be provided and it will start a pry session on the
|
11
|
-
# receiver. In the second form it should be invoked without an
|
12
|
-
# explicit receiver and one parameter; this will start a Pry
|
13
|
-
# session on the parameter.
|
14
|
-
# @param [Object, Binding] target The receiver of the Pry session.
|
15
|
-
# @example First form
|
16
|
-
# "dummy".pry
|
17
|
-
# @example Second form
|
18
|
-
# pry "dummy"
|
19
|
-
# @example Start a Pry session on current self (whatever that is)
|
20
|
-
# pry
|
21
|
-
def pry(target=self)
|
22
|
-
Pry.start(target)
|
23
|
+
# Return a binding object for the receiver.
|
24
|
+
def __binding__
|
25
|
+
if is_a?(Module)
|
26
|
+
return class_eval "binding"
|
23
27
|
end
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
unless respond_to? :__binding_impl__
|
30
|
+
begin
|
31
|
+
instance_eval %{
|
32
|
+
def __binding_impl__
|
33
|
+
binding
|
34
|
+
end
|
35
|
+
}
|
36
|
+
rescue TypeError
|
37
|
+
self.class.class_eval %{
|
38
|
+
def __binding_impl__
|
39
|
+
binding
|
40
|
+
end
|
41
|
+
}
|
29
42
|
end
|
30
|
-
|
31
|
-
unless respond_to? :__binding_impl__
|
32
|
-
begin
|
33
|
-
instance_eval %{
|
34
|
-
def __binding_impl__
|
35
|
-
binding
|
36
|
-
end
|
37
|
-
}
|
38
|
-
rescue TypeError
|
39
|
-
self.class.class_eval %{
|
40
|
-
def __binding_impl__
|
41
|
-
binding
|
42
|
-
end
|
43
|
-
}
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
__binding_impl__
|
48
43
|
end
|
49
|
-
end
|
50
|
-
end
|
51
44
|
|
52
|
-
|
53
|
-
|
54
|
-
include Pry::ObjectExtensions
|
45
|
+
__binding_impl__
|
46
|
+
end
|
55
47
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "pry/default_commands/ls"
|
2
|
+
|
3
|
+
class Pry
|
4
|
+
module DefaultCommands
|
5
|
+
|
6
|
+
Context = Pry::CommandSet.new :context do
|
7
|
+
import Ls
|
8
|
+
|
9
|
+
command "cd", "Start a Pry session on VAR (use `cd ..` to go back and `cd /` to return to Pry top-level)", :keep_retval => true do |obj|
|
10
|
+
if !obj
|
11
|
+
output.puts "Must provide an object."
|
12
|
+
next
|
13
|
+
end
|
14
|
+
|
15
|
+
throw(:breakout, opts[:nesting].level) if obj == ".."
|
16
|
+
|
17
|
+
if obj == "/"
|
18
|
+
throw(:breakout, 1) if opts[:nesting].level > 0
|
19
|
+
next
|
20
|
+
end
|
21
|
+
|
22
|
+
Pry.start target.eval("#{obj}")
|
23
|
+
end
|
24
|
+
|
25
|
+
command "nesting", "Show nesting information." do
|
26
|
+
nesting = opts[:nesting]
|
27
|
+
|
28
|
+
output.puts "Nesting status:"
|
29
|
+
output.puts "--"
|
30
|
+
nesting.each do |level, obj|
|
31
|
+
if level == 0
|
32
|
+
output.puts "#{level}. #{Pry.view_clip(obj)} (Pry top level)"
|
33
|
+
else
|
34
|
+
output.puts "#{level}. #{Pry.view_clip(obj)}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
command "jump-to", "Jump to a Pry session further up the stack, exiting all sessions below." do |break_level|
|
40
|
+
break_level = break_level.to_i
|
41
|
+
nesting = opts[:nesting]
|
42
|
+
|
43
|
+
case break_level
|
44
|
+
when nesting.level
|
45
|
+
output.puts "Already at nesting level #{nesting.level}"
|
46
|
+
when (0...nesting.level)
|
47
|
+
throw(:breakout, break_level + 1)
|
48
|
+
else
|
49
|
+
max_nest_level = nesting.level - 1
|
50
|
+
output.puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
command "exit", "End the current Pry session. Accepts optional return value. Aliases: quit, back" do
|
55
|
+
str = remove_first_word(opts[:val])
|
56
|
+
throw(:breakout, [opts[:nesting].level, target.eval(str)])
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_command "quit", "exit", ""
|
60
|
+
alias_command "back", "exit", ""
|
61
|
+
|
62
|
+
command "exit-all", "End all nested Pry sessions. Accepts optional return value. Aliases: !!@" do
|
63
|
+
str = remove_first_word(opts[:val])
|
64
|
+
throw(:breakout, [0, target.eval(str)])
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_command "!!@", "exit-all", ""
|
68
|
+
|
69
|
+
command "exit-program", "End the current program. Aliases: quit-program, !!!" do
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
alias_command "quit-program", "exit-program", ""
|
74
|
+
alias_command "!!!", "exit-program", ""
|
75
|
+
|
76
|
+
command "!pry", "Start a Pry session on current self; this even works mid-expression." do
|
77
|
+
Pry.start(target)
|
78
|
+
end
|
79
|
+
|
80
|
+
command "whereami", "Show the code context for the session. (whereami <n> shows <n> extra lines of code around the invocation line. Default: 5)" do |num|
|
81
|
+
file = target.eval('__FILE__')
|
82
|
+
line_num = target.eval('__LINE__')
|
83
|
+
klass = target.eval('self.class')
|
84
|
+
|
85
|
+
if num
|
86
|
+
i_num = num.to_i
|
87
|
+
else
|
88
|
+
i_num = 5
|
89
|
+
end
|
90
|
+
|
91
|
+
meth_name = meth_name_from_binding(target)
|
92
|
+
meth_name = "N/A" if !meth_name
|
93
|
+
|
94
|
+
if file =~ /(\(.*\))|<.*>/ || file == "" || file == "-e"
|
95
|
+
output.puts "Cannot find local context. Did you use `binding.pry` ?"
|
96
|
+
next
|
97
|
+
end
|
98
|
+
|
99
|
+
set_file_and_dir_locals(file)
|
100
|
+
output.puts "\n#{bold('From:')} #{file} @ line #{line_num} in #{klass}##{meth_name}:\n\n"
|
101
|
+
|
102
|
+
# This method inspired by http://rubygems.org/gems/ir_b
|
103
|
+
File.open(file).each_with_index do |line, index|
|
104
|
+
line_n = index + 1
|
105
|
+
next unless line_n > (line_num - i_num - 1)
|
106
|
+
break if line_n > (line_num + i_num)
|
107
|
+
if line_n == line_num
|
108
|
+
code =" =>#{line_n.to_s.rjust(3)}: #{line.chomp}"
|
109
|
+
if Pry.color
|
110
|
+
code = CodeRay.scan(code, :ruby).term
|
111
|
+
end
|
112
|
+
output.puts code
|
113
|
+
code
|
114
|
+
else
|
115
|
+
code = "#{line_n.to_s.rjust(6)}: #{line.chomp}"
|
116
|
+
if Pry.color
|
117
|
+
code = CodeRay.scan(code, :ruby).term
|
118
|
+
end
|
119
|
+
output.puts code
|
120
|
+
code
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
class Pry
|
2
|
+
module DefaultCommands
|
3
|
+
|
4
|
+
Documentation = Pry::CommandSet.new :gems do
|
5
|
+
|
6
|
+
command "ri", "View ri documentation. e.g `ri Array#each`" do |*args|
|
7
|
+
run ".ri", *args
|
8
|
+
end
|
9
|
+
|
10
|
+
command "show-doc", "Show the comments above METH. Type `show-doc --help` for more info. Aliases: \?" do |*args|
|
11
|
+
target = target()
|
12
|
+
|
13
|
+
opts = Slop.parse!(args) do |opts|
|
14
|
+
opts.banner %{Usage: show-doc [OPTIONS] [METH]
|
15
|
+
Show the comments above method METH. Tries instance methods first and then methods by default.
|
16
|
+
e.g show-doc hello_method
|
17
|
+
--
|
18
|
+
}
|
19
|
+
opts.on :M, "instance-methods", "Operate on instance methods."
|
20
|
+
opts.on :m, :methods, "Operate on methods."
|
21
|
+
opts.on :c, :context, "Select object context to run under.", true do |context|
|
22
|
+
target = Pry.binding_for(target.eval(context))
|
23
|
+
end
|
24
|
+
opts.on :f, :flood, "Do not use a pager to view text longer than one screen."
|
25
|
+
opts.on :h, :help, "This message." do
|
26
|
+
output.puts opts
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
next if opts.help?
|
31
|
+
|
32
|
+
meth_name = args.shift
|
33
|
+
if (meth = get_method_object(meth_name, target, opts.to_hash(true))).nil?
|
34
|
+
output.puts "Invalid method name: #{meth_name}. Type `show-doc --help` for help"
|
35
|
+
next
|
36
|
+
end
|
37
|
+
|
38
|
+
doc, code_type = doc_and_code_type_for(meth)
|
39
|
+
next if !doc
|
40
|
+
|
41
|
+
next output.puts("No documentation found.") if doc.empty?
|
42
|
+
doc = process_comment_markup(doc, code_type)
|
43
|
+
output.puts make_header(meth, code_type, doc)
|
44
|
+
render_output(opts.flood?, false, doc)
|
45
|
+
doc
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_command "?", "show-doc", ""
|
49
|
+
|
50
|
+
|
51
|
+
command "stat", "View method information and set _file_ and _dir_ locals. Type `stat --help` for more info." do |*args|
|
52
|
+
target = target()
|
53
|
+
|
54
|
+
opts = Slop.parse!(args) do |opts|
|
55
|
+
opts.banner %{Usage: stat [OPTIONS] [METH]
|
56
|
+
Show method information for method METH and set _file_ and _dir_ locals.
|
57
|
+
e.g: stat hello_method
|
58
|
+
--
|
59
|
+
}
|
60
|
+
opts.on :M, "instance-methods", "Operate on instance methods."
|
61
|
+
opts.on :m, :methods, "Operate on methods."
|
62
|
+
opts.on :c, :context, "Select object context to run under.", true do |context|
|
63
|
+
target = Pry.binding_for(target.eval(context))
|
64
|
+
end
|
65
|
+
opts.on :h, :help, "This message" do
|
66
|
+
output.puts opts
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
next if opts.help?
|
71
|
+
|
72
|
+
meth_name = args.shift
|
73
|
+
if (meth = get_method_object(meth_name, target, opts.to_hash(true))).nil?
|
74
|
+
output.puts "Invalid method name: #{meth_name}. Type `stat --help` for help"
|
75
|
+
next
|
76
|
+
end
|
77
|
+
|
78
|
+
code, code_type = code_and_code_type_for(meth)
|
79
|
+
next if !code
|
80
|
+
doc, code_type = doc_and_code_type_for(meth)
|
81
|
+
|
82
|
+
output.puts make_header(meth, code_type, code)
|
83
|
+
output.puts bold("Method Name: ") + meth_name
|
84
|
+
output.puts bold("Method Owner: ") + (meth.owner.to_s ? meth.owner.to_s : "Unknown")
|
85
|
+
output.puts bold("Method Language: ") + code_type.to_s.capitalize
|
86
|
+
output.puts bold("Method Type: ") + (meth.is_a?(Method) ? "Bound" : "Unbound")
|
87
|
+
output.puts bold("Method Arity: ") + meth.arity.to_s
|
88
|
+
|
89
|
+
name_map = { :req => "Required:", :opt => "Optional:", :rest => "Rest:" }
|
90
|
+
if meth.respond_to?(:parameters)
|
91
|
+
output.puts bold("Method Parameters: ") + meth.parameters.group_by(&:first).
|
92
|
+
map { |k, v| "#{name_map[k]} #{v.map { |kk, vv| vv ? vv.to_s : "noname" }.join(", ")}" }.join(". ")
|
93
|
+
end
|
94
|
+
output.puts bold("Comment length: ") + (doc.empty? ? 'No comment.' : (doc.lines.count.to_s + ' lines.'))
|
95
|
+
end
|
96
|
+
|
97
|
+
command "gist-method", "Gist a method to github. Type `gist-method --help` for more info.", :requires_gem => "gist" do |*args|
|
98
|
+
target = target()
|
99
|
+
|
100
|
+
opts = Slop.parse!(args) do |opts|
|
101
|
+
opts.banner = %{Usage: gist-method [OPTIONS] [METH]
|
102
|
+
Gist the method (doc or source) to github.
|
103
|
+
Ensure the `gist` gem is properly working before use. http://github.com/defunkt/gist for instructions.
|
104
|
+
e.g: gist -m my_method
|
105
|
+
e.g: gist -d my_method
|
106
|
+
--
|
107
|
+
}
|
108
|
+
opts.on :m, :method, "Gist a method's source."
|
109
|
+
opts.on :d, :doc, "Gist a method's documentation."
|
110
|
+
opts.on :p, :private, "Create a private gist (default: true)", :default => true
|
111
|
+
opts.on :h, :help, "This message" do
|
112
|
+
output.puts opts
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
next if opts.help?
|
117
|
+
|
118
|
+
# This needs to be extracted into its own method as it's shared
|
119
|
+
# by show-method and show-doc and stat commands
|
120
|
+
meth_name = args.shift
|
121
|
+
if (meth = get_method_object(meth_name, target, opts.to_hash(true))).nil?
|
122
|
+
output.puts "Invalid method name: #{meth_name}. Type `gist-method --help` for help"
|
123
|
+
next
|
124
|
+
end
|
125
|
+
|
126
|
+
type_map = { :ruby => "rb", :c => "c", :plain => "plain" }
|
127
|
+
if !opts.doc?
|
128
|
+
content, code_type = code_and_code_type_for(meth)
|
129
|
+
else
|
130
|
+
content, code_type = doc_and_code_type_for(meth)
|
131
|
+
no_color do
|
132
|
+
content = process_comment_markup(content, code_type)
|
133
|
+
end
|
134
|
+
code_type = :plain
|
135
|
+
end
|
136
|
+
|
137
|
+
IO.popen("gist#{' -p' if opts.p?} -t #{type_map[code_type]} -", "w") do |gist|
|
138
|
+
gist.puts content
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Pry
|
2
|
+
module DefaultCommands
|
3
|
+
|
4
|
+
EasterEggs = Pry::CommandSet.new :easter_eggs do
|
5
|
+
|
6
|
+
command "game", "" do |highest|
|
7
|
+
highest = highest ? highest.to_i : 100
|
8
|
+
num = rand(highest)
|
9
|
+
output.puts "Guess the number between 0-#{highest}: ('.' to quit)"
|
10
|
+
count = 0
|
11
|
+
while(true)
|
12
|
+
count += 1
|
13
|
+
str = Readline.readline("game > ", true)
|
14
|
+
break if str == "." || !str
|
15
|
+
val = str.to_i
|
16
|
+
output.puts "Too large!" if val > num
|
17
|
+
output.puts "Too small!" if val < num
|
18
|
+
if val == num
|
19
|
+
output.puts "Well done! You guessed right! It took you #{count} guesses."
|
20
|
+
break
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
command "east-coker", "" do
|
26
|
+
text = %{
|
27
|
+
--
|
28
|
+
Now the light falls
|
29
|
+
Across the open field, leaving the deep lane
|
30
|
+
Shuttered with branches, dark in the afternoon,
|
31
|
+
Where you lean against a bank while a van passes,
|
32
|
+
And the deep lane insists on the direction
|
33
|
+
Into the village, in the electric heat
|
34
|
+
Hypnotised. In a warm haze the sultry light
|
35
|
+
Is absorbed, not refracted, by grey stone.
|
36
|
+
The dahlias sleep in the empty silence.
|
37
|
+
Wait for the early owl.
|
38
|
+
-- T.S Eliot
|
39
|
+
}
|
40
|
+
output.puts text
|
41
|
+
text
|
42
|
+
end
|
43
|
+
|
44
|
+
command "cohen-poem", "" do
|
45
|
+
text = %{
|
46
|
+
--
|
47
|
+
When this American woman,
|
48
|
+
whose thighs are bound in casual red cloth,
|
49
|
+
comes thundering past my sitting place
|
50
|
+
like a forest-burning Mongol tribe,
|
51
|
+
the city is ravished
|
52
|
+
and brittle buildings of a hundred years
|
53
|
+
splash into the street;
|
54
|
+
and my eyes are burnt
|
55
|
+
for the embroidered Chinese girls,
|
56
|
+
already old,
|
57
|
+
and so small between the thin pines
|
58
|
+
on these enormous landscapes,
|
59
|
+
that if you turn your head
|
60
|
+
they are lost for hours.
|
61
|
+
-- Leonard Cohen
|
62
|
+
}
|
63
|
+
output.puts text
|
64
|
+
text
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Pry
|
2
|
+
module DefaultCommands
|
3
|
+
|
4
|
+
Gems = Pry::CommandSet.new :gems do
|
5
|
+
|
6
|
+
command "gem-install", "Install a gem and refresh the gem cache." do |gem_name|
|
7
|
+
gem_home = Gem.instance_variable_get(:@gem_home)
|
8
|
+
output.puts "Attempting to install gem: #{bold(gem_name)}"
|
9
|
+
|
10
|
+
begin
|
11
|
+
if File.writable?(gem_home)
|
12
|
+
Gem::DependencyInstaller.new.install(gem_name)
|
13
|
+
output.puts "Gem #{bold(gem_name)} successfully installed."
|
14
|
+
else
|
15
|
+
if system("sudo gem install #{gem_name}")
|
16
|
+
output.puts "Gem #{bold(gem_name)} successfully installed."
|
17
|
+
else
|
18
|
+
output.puts "Gem #{bold(gem_name)} could not be installed."
|
19
|
+
next
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue Gem::GemNotFoundException
|
23
|
+
output.puts "Required Gem: #{bold(gem_name)} not found."
|
24
|
+
next
|
25
|
+
end
|
26
|
+
|
27
|
+
Gem.refresh
|
28
|
+
output.puts "Refreshed gem cache."
|
29
|
+
end
|
30
|
+
|
31
|
+
command "gem-cd", "Change working directory to specified gem's directory." do |gem_name|
|
32
|
+
require 'rubygems'
|
33
|
+
gem_spec = Gem.source_index.find_name(gem_name).first
|
34
|
+
next output.puts("Gem `#{gem_name}` not found.") if !gem_spec
|
35
|
+
Dir.chdir(File.expand_path(gem_spec.full_gem_path))
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
command "gem-list", "List/search installed gems. (Optional parameter: a regexp to limit the search)" do |arg|
|
40
|
+
gems = Gem.source_index.gems.values.group_by(&:name)
|
41
|
+
if arg
|
42
|
+
query = Regexp.new(arg, Regexp::IGNORECASE)
|
43
|
+
gems = gems.select { |gemname, specs| gemname =~ query }
|
44
|
+
end
|
45
|
+
|
46
|
+
gems.each do |gemname, specs|
|
47
|
+
versions = specs.map(&:version).sort.reverse.map(&:to_s)
|
48
|
+
versions = ["<bright_green>#{versions.first}</bright_green>"] +
|
49
|
+
versions[1..-1].map{|v| "<green>#{v}</green>" }
|
50
|
+
|
51
|
+
gemname = highlight(gemname, query) if query
|
52
|
+
result = "<white>#{gemname} <grey>(#{versions.join ', '})</grey>"
|
53
|
+
output.puts colorize(result)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|