rake 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rake might be problematic. Click here for more details.
- data/CHANGES +26 -1
- data/README +17 -5
- data/Rakefile +27 -17
- data/bin/rake +2 -3
- data/doc/rakefile.rdoc +126 -0
- data/doc/release_notes/rake-0.7.0.rdoc +119 -0
- data/lib/rake.rb +439 -175
- data/lib/rake/rdoctask.rb +24 -5
- data/test/data/namespace/Rakefile +57 -0
- data/test/session_functional.rb +49 -0
- data/test/test_file_creation_task.rb +4 -4
- data/test/test_file_task.rb +4 -4
- data/test/test_multitask.rb +45 -0
- data/test/test_namespace.rb +24 -0
- data/test/test_task_manager.rb +148 -0
- data/test/test_tasks.rb +36 -7
- metadata +111 -102
data/lib/rake/rdoctask.rb
CHANGED
@@ -64,6 +64,9 @@ module Rake
|
|
64
64
|
# List of options to be passed rdoc. (default is [])
|
65
65
|
attr_accessor :options
|
66
66
|
|
67
|
+
# Run the rdoc process as an external shell (default is false)
|
68
|
+
attr_accessor :external
|
69
|
+
|
67
70
|
# Create an RDoc task named <em>rdoc</em>. Default task name is +rdoc+.
|
68
71
|
def initialize(name=:rdoc) # :yield: self
|
69
72
|
@name = name
|
@@ -72,6 +75,7 @@ module Rake
|
|
72
75
|
@main = nil
|
73
76
|
@title = nil
|
74
77
|
@template = 'html'
|
78
|
+
@external = false
|
75
79
|
@options = []
|
76
80
|
yield self if block_given?
|
77
81
|
define
|
@@ -100,20 +104,35 @@ module Rake
|
|
100
104
|
task name => [rdoc_target]
|
101
105
|
file rdoc_target => @rdoc_files + [$rakefile] do
|
102
106
|
rm_r @rdoc_dir rescue nil
|
103
|
-
|
104
|
-
|
107
|
+
args = option_list + @rdoc_files
|
108
|
+
if @external
|
109
|
+
argstring = args.join(' ')
|
110
|
+
sh %{ruby -Ivendor vender/rd #{argstring}}
|
111
|
+
else
|
112
|
+
require 'rdoc/rdoc'
|
113
|
+
RDoc::RDoc.new.document(args)
|
114
|
+
end
|
105
115
|
end
|
106
116
|
self
|
107
117
|
end
|
108
118
|
|
109
119
|
def option_list
|
110
120
|
result = @options.dup
|
111
|
-
result << "
|
112
|
-
result << "--
|
113
|
-
result << "
|
121
|
+
result << "-o" << @rdoc_dir
|
122
|
+
result << "--main" << quote(main) if main
|
123
|
+
result << "--title" << quote(title) if title
|
124
|
+
result << "-T" << quote(template) if template
|
114
125
|
result
|
115
126
|
end
|
116
127
|
|
128
|
+
def quote(str)
|
129
|
+
if @external
|
130
|
+
"'#{str}'"
|
131
|
+
else
|
132
|
+
str
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
117
136
|
def option_string
|
118
137
|
option_list.join(' ')
|
119
138
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
desc "copy"
|
4
|
+
task :copy do
|
5
|
+
puts "COPY"
|
6
|
+
end
|
7
|
+
|
8
|
+
namespace "nest" do
|
9
|
+
desc "nest copy"
|
10
|
+
task :copy do
|
11
|
+
puts "NEST COPY"
|
12
|
+
end
|
13
|
+
task :xx => :copy
|
14
|
+
end
|
15
|
+
|
16
|
+
anon_ns = namespace do
|
17
|
+
desc "anonymous copy task"
|
18
|
+
task :copy do
|
19
|
+
puts "ANON COPY"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Top level task to run the anonymous version of copy"
|
24
|
+
task :anon => anon_ns[:copy]
|
25
|
+
|
26
|
+
namespace "very" do
|
27
|
+
namespace "nested" do
|
28
|
+
task "run" => "rake:copy"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
namespace "a" do
|
33
|
+
desc "Run task in the 'a' namespace"
|
34
|
+
task "run" do
|
35
|
+
puts "IN A"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
namespace "b" do
|
40
|
+
desc "Run task in the 'b' namespace"
|
41
|
+
task "run" => "a:run" do
|
42
|
+
puts "IN B"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
namespace "file1" do
|
47
|
+
file "xyz.rb" do
|
48
|
+
puts "XYZ1"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
namespace "file2" do
|
53
|
+
file "xyz.rb" do
|
54
|
+
puts "XYZ2"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
data/test/session_functional.rb
CHANGED
@@ -130,6 +130,55 @@ class FunctionalTest < Test::Unit::TestCase
|
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
+
def test_can_invoke_task_in_toplevel_namespace
|
134
|
+
Dir.chdir("test/data/namespace") do
|
135
|
+
rake "copy"
|
136
|
+
assert_match(/^COPY$/, @out)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_can_invoke_task_in_nested_namespace
|
141
|
+
Dir.chdir("test/data/namespace") do
|
142
|
+
rake "nest:copy"
|
143
|
+
assert_match(/^NEST COPY$/, @out)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_tasks_can_reference_task_in_same_namespace
|
148
|
+
Dir.chdir("test/data/namespace") do
|
149
|
+
rake "nest:xx"
|
150
|
+
assert_match(/^NEST COPY$/m, @out)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_tasks_can_reference_task_in_other_namespaces
|
155
|
+
Dir.chdir("test/data/namespace") do
|
156
|
+
rake "b:run"
|
157
|
+
assert_match(/^IN A\nIN B$/m, @out)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_anonymous_tasks_can_be_invoked_indirectly
|
162
|
+
Dir.chdir("test/data/namespace") do
|
163
|
+
rake "anon"
|
164
|
+
assert_match(/^ANON COPY$/m, @out)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_rake_namespace_refers_to_toplevel
|
169
|
+
Dir.chdir("test/data/namespace") do
|
170
|
+
rake "very:nested:run"
|
171
|
+
assert_match(/^COPY$/m, @out)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_file_task_are_not_scoped_by_namespaces
|
176
|
+
Dir.chdir("test/data/namespace") do
|
177
|
+
rake "xyz.rb"
|
178
|
+
assert_match(/^XYZ1\nXYZ2$/m, @out)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
133
182
|
private
|
134
183
|
|
135
184
|
def remove_chaining_files
|
@@ -40,16 +40,16 @@ class TestFileCreationTask < Test::Unit::TestCase
|
|
40
40
|
|
41
41
|
def test_no_retriggers_on_filecreate_task
|
42
42
|
create_timed_files(OLDFILE, NEWFILE)
|
43
|
-
t1 =
|
44
|
-
t2 =
|
43
|
+
t1 = Rake.application.intern(FileCreationTask, OLDFILE).enhance([NEWFILE])
|
44
|
+
t2 = Rake.application.intern(FileCreationTask, NEWFILE)
|
45
45
|
assert ! t2.needed?, "Should not need to build new file"
|
46
46
|
assert ! t1.needed?, "Should not need to rebuild old file because of new"
|
47
47
|
end
|
48
48
|
|
49
49
|
def test_no_retriggers_on_file_task
|
50
50
|
create_timed_files(OLDFILE, NEWFILE)
|
51
|
-
t1 =
|
52
|
-
t2 =
|
51
|
+
t1 = Rake.application.intern(FileCreationTask, OLDFILE).enhance([NEWFILE])
|
52
|
+
t2 = Rake.application.intern(FileCreationTask, NEWFILE)
|
53
53
|
assert ! t2.needed?, "Should not need to build new file"
|
54
54
|
assert ! t1.needed?, "Should not need to rebuild old file because of new"
|
55
55
|
end
|
data/test/test_file_task.rb
CHANGED
@@ -33,8 +33,8 @@ class TestFileTask < Test::Unit::TestCase
|
|
33
33
|
def test_file_times_new_depends_on_old
|
34
34
|
create_timed_files(OLDFILE, NEWFILE)
|
35
35
|
|
36
|
-
t1 =
|
37
|
-
t2 =
|
36
|
+
t1 = Rake.application.intern(FileTask, NEWFILE).enhance([OLDFILE])
|
37
|
+
t2 = Rake.application.intern(FileTask, OLDFILE)
|
38
38
|
assert ! t2.needed?, "Should not need to build old file"
|
39
39
|
assert ! t1.needed?, "Should not need to rebuild new file because of old"
|
40
40
|
end
|
@@ -42,8 +42,8 @@ class TestFileTask < Test::Unit::TestCase
|
|
42
42
|
def test_file_times_old_depends_on_new
|
43
43
|
create_timed_files(OLDFILE, NEWFILE)
|
44
44
|
|
45
|
-
t1 =
|
46
|
-
t2 =
|
45
|
+
t1 = Rake.application.intern(FileTask,OLDFILE).enhance([NEWFILE])
|
46
|
+
t2 = Rake.application.intern(FileTask, NEWFILE)
|
47
47
|
assert ! t2.needed?, "Should not need to build new file"
|
48
48
|
preq_stamp = t1.prerequisites.collect{|t| Task[t].timestamp}.max
|
49
49
|
assert_equal t2.timestamp, preq_stamp
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rake'
|
5
|
+
|
6
|
+
######################################################################
|
7
|
+
class TestMultiTask < Test::Unit::TestCase
|
8
|
+
include Rake
|
9
|
+
|
10
|
+
def setup
|
11
|
+
Task.clear
|
12
|
+
@runs = Array.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_running_multitasks
|
16
|
+
task :a do 3.times do |i| @runs << "A#{i}"; sleep 0.01; end end
|
17
|
+
task :b do 3.times do |i| @runs << "B#{i}"; sleep 0.01; end end
|
18
|
+
multitask :both => [:a, :b]
|
19
|
+
Task[:both].invoke
|
20
|
+
assert_equal 6, @runs.size
|
21
|
+
assert @runs.index("A0") < @runs.index("A1")
|
22
|
+
assert @runs.index("A1") < @runs.index("A2")
|
23
|
+
assert @runs.index("B0") < @runs.index("B1")
|
24
|
+
assert @runs.index("B1") < @runs.index("B2")
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_all_multitasks_wait_on_slow_prerequisites
|
28
|
+
task :slow do 3.times do |i| @runs << "S#{i}"; sleep 0.05 end end
|
29
|
+
task :a => [:slow] do 3.times do |i| @runs << "A#{i}"; sleep 0.01 end end
|
30
|
+
task :b => [:slow] do 3.times do |i| @runs << "B#{i}"; sleep 0.01 end end
|
31
|
+
multitask :both => [:a, :b]
|
32
|
+
Task[:both].invoke
|
33
|
+
assert_equal 9, @runs.size
|
34
|
+
assert @runs.index("S0") < @runs.index("S1")
|
35
|
+
assert @runs.index("S1") < @runs.index("S2")
|
36
|
+
assert @runs.index("S2") < @runs.index("A0")
|
37
|
+
assert @runs.index("S2") < @runs.index("B0")
|
38
|
+
assert @runs.index("A0") < @runs.index("A1")
|
39
|
+
assert @runs.index("A1") < @runs.index("A2")
|
40
|
+
assert @runs.index("B0") < @runs.index("B1")
|
41
|
+
assert @runs.index("B1") < @runs.index("B2")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'test/unit'
|
5
|
+
require 'flexmock'
|
6
|
+
require 'rake'
|
7
|
+
|
8
|
+
class TestNameSpace < Test::Unit::TestCase
|
9
|
+
def test_namespace_creation
|
10
|
+
FlexMock.use("TaskManager") do |mgr|
|
11
|
+
ns = Rake::NameSpace.new(mgr, [])
|
12
|
+
assert_not_nil ns
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_namespace_lookup
|
17
|
+
FlexMock.use("TaskManager") do |mgr|
|
18
|
+
mgr.should_receive(:lookup).with(:t, ["a"]).
|
19
|
+
and_return(nil).once
|
20
|
+
ns = Rake::NameSpace.new(mgr, ["a"])
|
21
|
+
ns[:t]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rake'
|
5
|
+
|
6
|
+
class TaskManager
|
7
|
+
include Rake::TaskManager
|
8
|
+
end
|
9
|
+
|
10
|
+
class TestTaskManager < Test::Unit::TestCase
|
11
|
+
def setup
|
12
|
+
@tm = TaskManager.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_create_task_manager
|
16
|
+
assert_not_nil @tm
|
17
|
+
assert_equal [], @tm.tasks
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_define_task
|
21
|
+
t = @tm.define_task(Rake::Task, :t)
|
22
|
+
assert_equal "t", t.name
|
23
|
+
assert_equal @tm, t.application
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_name_lookup
|
27
|
+
t = @tm.define_task(Rake::Task, :t)
|
28
|
+
assert_equal t, @tm[:t]
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_namespace_task_create
|
32
|
+
@tm.in_namespace("x") do
|
33
|
+
t = @tm.define_task(Rake::Task, :t)
|
34
|
+
assert_equal "x:t", t.name
|
35
|
+
end
|
36
|
+
assert_equal ["x:t"], @tm.tasks.collect { |t| t.name }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_anonymous_namespace
|
40
|
+
anon_ns = @tm.in_namespace(nil) do
|
41
|
+
t = @tm.define_task(Rake::Task, :t)
|
42
|
+
assert_equal "_anon_1:t", t.name
|
43
|
+
end
|
44
|
+
task = anon_ns[:t]
|
45
|
+
assert_equal "_anon_1:t", task.name
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_create_filetask_in_namespace
|
49
|
+
@tm.in_namespace("x") do
|
50
|
+
t = @tm.define_task(Rake::FileTask, "fn")
|
51
|
+
assert_equal "fn", t.name
|
52
|
+
end
|
53
|
+
assert_equal ["fn"], @tm.tasks.collect { |t| t.name }
|
54
|
+
end
|
55
|
+
|
56
|
+
def testS_namespace_yields_same_namespace_as_returned
|
57
|
+
yielded_namespace = nil
|
58
|
+
returned_namespace = @tm.in_namespace("x") do |ns|
|
59
|
+
yielded_namespace = ns
|
60
|
+
end
|
61
|
+
assert_equal returned_namespace, yielded_namespace
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_name_lookup_with_implicit_file_tasks
|
65
|
+
t = @tm["README"]
|
66
|
+
assert_equal "README", t.name
|
67
|
+
assert Rake::FileTask === t
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_name_lookup_with_nonexistent_task
|
71
|
+
assert_raise(RuntimeError) {
|
72
|
+
t = @tm["DOES NOT EXIST"]
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_name_lookup_in_multiple_scopes
|
77
|
+
aa = nil
|
78
|
+
bb = nil
|
79
|
+
xx = @tm.define_task(Rake::Task, :xx)
|
80
|
+
top_z = @tm.define_task(Rake::Task, :z)
|
81
|
+
@tm.in_namespace("a") do
|
82
|
+
aa = @tm.define_task(Rake::Task, :aa)
|
83
|
+
mid_z = @tm.define_task(Rake::Task, :z)
|
84
|
+
@tm.in_namespace("b") do
|
85
|
+
bb = @tm.define_task(Rake::Task, :bb)
|
86
|
+
bot_z = @tm.define_task(Rake::Task, :z)
|
87
|
+
|
88
|
+
assert_equal ["a", "b"], @tm.current_scope
|
89
|
+
|
90
|
+
assert_equal bb, @tm["a:b:bb"]
|
91
|
+
assert_equal aa, @tm["a:aa"]
|
92
|
+
assert_equal xx, @tm["xx"]
|
93
|
+
assert_equal bot_z, @tm["z"]
|
94
|
+
assert_equal mid_z, @tm["^z"]
|
95
|
+
assert_equal top_z, @tm["^^z"]
|
96
|
+
assert_equal top_z, @tm["rake:z"]
|
97
|
+
end
|
98
|
+
|
99
|
+
assert_equal ["a"], @tm.current_scope
|
100
|
+
|
101
|
+
assert_equal bb, @tm["a:b:bb"]
|
102
|
+
assert_equal aa, @tm["a:aa"]
|
103
|
+
assert_equal xx, @tm["xx"]
|
104
|
+
assert_equal bb, @tm["b:bb"]
|
105
|
+
assert_equal aa, @tm["aa"]
|
106
|
+
assert_equal mid_z, @tm["z"]
|
107
|
+
assert_equal top_z, @tm["^z"]
|
108
|
+
assert_equal top_z, @tm["rake:z"]
|
109
|
+
end
|
110
|
+
|
111
|
+
assert_equal [], @tm.current_scope
|
112
|
+
|
113
|
+
assert_equal [], xx.scope
|
114
|
+
assert_equal ['a'], aa.scope
|
115
|
+
assert_equal ['a', 'b'], bb.scope
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_lookup_with_explicit_scopes
|
119
|
+
t1, t2, t3, s = (0...4).collect { nil }
|
120
|
+
t1 = @tm.define_task(Rake::Task, :t)
|
121
|
+
@tm.in_namespace("a") do
|
122
|
+
t2 = @tm.define_task(Rake::Task, :t)
|
123
|
+
s = @tm.define_task(Rake::Task, :s)
|
124
|
+
@tm.in_namespace("b") do
|
125
|
+
t3 = @tm.define_task(Rake::Task, :t)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
assert_equal t1, @tm[:t, []]
|
129
|
+
assert_equal t2, @tm[:t, ["a"]]
|
130
|
+
assert_equal t3, @tm[:t, ["a", "b"]]
|
131
|
+
assert_equal s, @tm[:s, ["a", "b"]]
|
132
|
+
assert_equal s, @tm[:s, ["a"]]
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_correctly_scoped_prerequisites_are_invoked
|
136
|
+
values = []
|
137
|
+
@tm = Rake::Application.new
|
138
|
+
@tm.define_task(Rake::Task, :z) do values << "top z" end
|
139
|
+
@tm.in_namespace("a") do
|
140
|
+
@tm.define_task(Rake::Task, :z) do values << "next z" end
|
141
|
+
@tm.define_task(Rake::Task, :x => :z)
|
142
|
+
end
|
143
|
+
|
144
|
+
@tm["a:x"].invoke
|
145
|
+
assert_equal ["next z"], values
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
data/test/test_tasks.rb
CHANGED
@@ -15,7 +15,7 @@ class TestTask < Test::Unit::TestCase
|
|
15
15
|
|
16
16
|
def test_create
|
17
17
|
arg = nil
|
18
|
-
t =
|
18
|
+
t = intern(:name).enhance { |task| arg = task; 1234 }
|
19
19
|
assert_equal "name", t.name
|
20
20
|
assert_equal [], t.prerequisites
|
21
21
|
assert t.prerequisites.is_a?(FileList)
|
@@ -27,19 +27,23 @@ class TestTask < Test::Unit::TestCase
|
|
27
27
|
|
28
28
|
def test_invoke
|
29
29
|
runlist = []
|
30
|
-
t1 =
|
31
|
-
t2 =
|
32
|
-
t3 =
|
30
|
+
t1 = intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
|
31
|
+
t2 = intern(:t2).enhance { |t| runlist << t.name }
|
32
|
+
t3 = intern(:t3).enhance { |t| runlist << t.name }
|
33
33
|
assert_equal [:t2, :t3], t1.prerequisites
|
34
34
|
t1.invoke
|
35
35
|
assert_equal ["t2", "t3", "t1"], runlist
|
36
36
|
end
|
37
37
|
|
38
|
+
def intern(name)
|
39
|
+
Rake.application.define_task(Rake::Task,name)
|
40
|
+
end
|
41
|
+
|
38
42
|
def test_no_double_invoke
|
39
43
|
runlist = []
|
40
|
-
t1 =
|
41
|
-
t2 =
|
42
|
-
t3 =
|
44
|
+
t1 = intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
|
45
|
+
t2 = intern(:t2).enhance([:t3]) { |t| runlist << t.name }
|
46
|
+
t3 = intern(:t3).enhance { |t| runlist << t.name }
|
43
47
|
t1.invoke
|
44
48
|
assert_equal ["t3", "t2", "t1"], runlist
|
45
49
|
end
|
@@ -73,5 +77,30 @@ class TestTask < Test::Unit::TestCase
|
|
73
77
|
assert_equal ["t1", "t2"], Task.tasks.collect {|t| t.name}
|
74
78
|
end
|
75
79
|
|
80
|
+
def test_task_gives_name_on_to_s
|
81
|
+
task :abc
|
82
|
+
assert_equal "abc", Task[:abc].to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_symbols_can_be_prerequisites
|
86
|
+
task :a => :b
|
87
|
+
assert_equal ["b"], Task[:a].prerequisites
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_strings_can_be_prerequisites
|
91
|
+
task :a => "b"
|
92
|
+
assert_equal ["b"], Task[:a].prerequisites
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_arrays_can_be_prerequisites
|
96
|
+
task :a => ["b", "c"]
|
97
|
+
assert_equal ["b", "c"], Task[:a].prerequisites
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_filelists_can_be_prerequisites
|
101
|
+
task :a => FileList.new.include("b", "c")
|
102
|
+
assert_equal ["b", "c"], Task[:a].prerequisites
|
103
|
+
end
|
104
|
+
|
76
105
|
end
|
77
106
|
|