bitclust-core 0.6.0 → 0.7.0
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.
- checksums.yaml +7 -0
- data/ChangeLog +7 -0
- data/Gemfile +1 -0
- data/bitclust.gemspec +1 -0
- data/data/bitclust/template.offline/class +1 -1
- data/lib/bitclust/crossrubyutils.rb +2 -2
- data/lib/bitclust/methodentry.rb +1 -2
- data/lib/bitclust/progress_bar.rb +7 -0
- data/lib/bitclust/rdcompiler.rb +1 -1
- data/lib/bitclust/runner.rb +46 -32
- data/lib/bitclust/searcher.rb +14 -13
- data/lib/bitclust/silent_progress_bar.rb +17 -0
- data/lib/bitclust/subcommand.rb +27 -0
- data/lib/bitclust/subcommands/ancestors_command.rb +145 -0
- data/lib/bitclust/subcommands/chm_command.rb +268 -0
- data/lib/bitclust/subcommands/classes_command.rb +73 -0
- data/lib/bitclust/subcommands/extract_command.rb +55 -0
- data/lib/bitclust/subcommands/htmlfile_command.rb +105 -0
- data/lib/bitclust/subcommands/init_command.rb +29 -30
- data/lib/bitclust/subcommands/list_command.rb +39 -41
- data/lib/bitclust/subcommands/lookup_command.rb +71 -73
- data/lib/bitclust/subcommands/methods_command.rb +159 -0
- data/lib/bitclust/subcommands/preproc_command.rb +35 -0
- data/lib/bitclust/subcommands/property_command.rb +47 -48
- data/lib/bitclust/subcommands/query_command.rb +12 -18
- data/lib/bitclust/subcommands/server_command.rb +180 -182
- data/lib/bitclust/subcommands/setup_command.rb +85 -89
- data/lib/bitclust/subcommands/statichtml_command.rb +276 -0
- data/lib/bitclust/subcommands/update_command.rb +39 -41
- data/lib/bitclust/version.rb +1 -1
- data/test/test_bitclust.rb +1 -1
- data/test/test_rdcompiler.rb +1 -1
- data/test/test_runner.rb +7 -14
- metadata +120 -114
@@ -0,0 +1,268 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'bitclust'
|
4
|
+
require 'bitclust/subcommand'
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
# TODO Remove this line when we drop 1.8 support
|
8
|
+
require 'kconv'
|
9
|
+
require 'bitclust/progress_bar'
|
10
|
+
|
11
|
+
module BitClust
|
12
|
+
module Subcommands
|
13
|
+
class ChmCommand < Subcommand
|
14
|
+
|
15
|
+
HHP_SKEL = <<EOS
|
16
|
+
[OPTIONS]
|
17
|
+
Compatibility=1.1 or later
|
18
|
+
Compiled file=refm.chm
|
19
|
+
Contents file=refm.hhc
|
20
|
+
Default Window=titlewindow
|
21
|
+
Default topic=doc/index.html
|
22
|
+
Display compile progress=No
|
23
|
+
Error log file=refm.log
|
24
|
+
Full-text search=Yes
|
25
|
+
Index file=refm.hhk
|
26
|
+
Language=0x411 日本語 (日本)
|
27
|
+
Title=Rubyリファレンスマニュアル
|
28
|
+
|
29
|
+
[WINDOWS]
|
30
|
+
titlewindow="Rubyリファレンスマニュアル","refm.hhc","refm.hhk","doc/index.html","doc/index.html",,,,,0x21420,,0x387e,,,,,,,,0
|
31
|
+
|
32
|
+
[FILES]
|
33
|
+
<%= @html_files.join("\n") %>
|
34
|
+
EOS
|
35
|
+
|
36
|
+
HHC_SKEL = <<EOS
|
37
|
+
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
38
|
+
<HTML>
|
39
|
+
<HEAD>
|
40
|
+
</HEAD>
|
41
|
+
<BODY>
|
42
|
+
<UL><% [:library].each do |k| %>
|
43
|
+
<%= @sitemap[k].to_html %>
|
44
|
+
<% end %></UL>
|
45
|
+
</BODY>
|
46
|
+
</HTML>
|
47
|
+
EOS
|
48
|
+
|
49
|
+
HHK_SKEL = <<EOS
|
50
|
+
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
51
|
+
<HTML>
|
52
|
+
<HEAD>
|
53
|
+
</HEAD>
|
54
|
+
<BODY>
|
55
|
+
<UL><% @index_contents.sort.each do |content| %>
|
56
|
+
<%= content.to_html %>
|
57
|
+
<% end %></UL>
|
58
|
+
</BODY>
|
59
|
+
</HTML>
|
60
|
+
EOS
|
61
|
+
|
62
|
+
class Sitemap
|
63
|
+
def initialize(name, local = nil)
|
64
|
+
@name = name
|
65
|
+
@contents = Content.new(name, local)
|
66
|
+
end
|
67
|
+
|
68
|
+
def method_missing(name, *args, &block)
|
69
|
+
@contents.send(name, *args, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
class Content
|
73
|
+
include Enumerable
|
74
|
+
include ERB::Util
|
75
|
+
|
76
|
+
def initialize(name, local = nil)
|
77
|
+
@name = name
|
78
|
+
@local = local
|
79
|
+
@contents = []
|
80
|
+
end
|
81
|
+
attr_reader :name, :local, :contents
|
82
|
+
|
83
|
+
def [](index)
|
84
|
+
@contents[index]
|
85
|
+
end
|
86
|
+
|
87
|
+
def <<(content)
|
88
|
+
@contents << content
|
89
|
+
end
|
90
|
+
|
91
|
+
def <=>(other)
|
92
|
+
@name <=> other.name
|
93
|
+
end
|
94
|
+
|
95
|
+
def each
|
96
|
+
@contents.each do |content|
|
97
|
+
yield content
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_html
|
102
|
+
str = "<LI> <OBJECT type=\"text/sitemap\">\n"
|
103
|
+
str << " <param name=\"Name\" value=\"<%=h @name%>\">\n"
|
104
|
+
if @local
|
105
|
+
str << " <param name=\"Local\" value=\"<%=@local%>\">\n"
|
106
|
+
end
|
107
|
+
str << " </OBJECT>\n"
|
108
|
+
unless contents.empty?
|
109
|
+
str << "<UL>\n"
|
110
|
+
@contents.each do |content|
|
111
|
+
str << content.to_html
|
112
|
+
end
|
113
|
+
str << "</UL>\n"
|
114
|
+
end
|
115
|
+
ERB.new(str).result(binding)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class URLMapperEx < URLMapper
|
121
|
+
def library_url(name)
|
122
|
+
if name == '/'
|
123
|
+
"/library/index.html"
|
124
|
+
else
|
125
|
+
"/library/#{encodename_fs(name)}.html"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def class_url(name)
|
130
|
+
"/class/#{encodename_fs(name)}.html"
|
131
|
+
end
|
132
|
+
|
133
|
+
def method_url(spec)
|
134
|
+
cname, tmark, mname = *split_method_spec(spec)
|
135
|
+
"/method/#{encodename_fs(cname)}/#{typemark2char(tmark)}/#{encodename_fs(mname)}.html"
|
136
|
+
end
|
137
|
+
|
138
|
+
def document_url(name)
|
139
|
+
"/doc/#{encodename_fs(name)}.html"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def initialize
|
144
|
+
super
|
145
|
+
@sitemap = {
|
146
|
+
:library => Sitemap.new('ライブラリ', 'library/index.html'),
|
147
|
+
}
|
148
|
+
@sitemap[:library] << Sitemap::Content.new('標準ライブラリ', 'library/_builtin.html')
|
149
|
+
@sitemap[:library] << Sitemap::Content.new('添付ライブラリ')
|
150
|
+
@stdlibs = {}
|
151
|
+
@index_contents = []
|
152
|
+
@parser.banner = "Usage: #{File.basename($0, '.*')} chm [options]"
|
153
|
+
@parser.on('-o', '--outputdir=PATH', 'Output directory') do |path|
|
154
|
+
begin
|
155
|
+
@outputdir = Pathname.new(path).realpath
|
156
|
+
rescue Errno::ENOENT
|
157
|
+
FileUtils.mkdir_p(path, :verbose => true)
|
158
|
+
retry
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def exec(argv, options)
|
164
|
+
create_manager_config
|
165
|
+
prefix = options[:prefix]
|
166
|
+
db = MethodDatabase.new(prefix.to_s)
|
167
|
+
manager = ScreenManager.new(@manager_config)
|
168
|
+
@html_files = []
|
169
|
+
db.transaction do
|
170
|
+
methods = {}
|
171
|
+
db.methods.each do |entry|
|
172
|
+
method_name = entry.klass.name + entry.typemark + entry.name
|
173
|
+
(methods[method_name] ||= []) << entry
|
174
|
+
end
|
175
|
+
|
176
|
+
entries = db.docs + db.libraries.sort + db.classes.sort + methods.values.sort
|
177
|
+
pb = ProgressBar.new('entry', entries.size)
|
178
|
+
entries.each do |c|
|
179
|
+
filename = create_html_file(c, manager, @outputdir, db)
|
180
|
+
@html_files << filename
|
181
|
+
e = c.is_a?(Array) ? c.sort.first : c
|
182
|
+
case e.type_id
|
183
|
+
when :library
|
184
|
+
content = Sitemap::Content.new(e.name.to_s, filename)
|
185
|
+
if e.name.to_s != '_builtin'
|
186
|
+
@sitemap[:library][1] << content
|
187
|
+
@stdlibs[e.name.to_s] = content
|
188
|
+
end
|
189
|
+
@index_contents << Sitemap::Content.new(e.name.to_s, filename)
|
190
|
+
when :class
|
191
|
+
content = Sitemap::Content.new(e.name.to_s, filename)
|
192
|
+
if e.library.name.to_s == '_builtin'
|
193
|
+
@sitemap[:library][0] << content
|
194
|
+
else
|
195
|
+
@stdlibs[e.library.name.to_s] << content
|
196
|
+
end
|
197
|
+
@index_contents << Sitemap::Content.new("#{e.name} (#{e.library.name})", filename)
|
198
|
+
when :method
|
199
|
+
e.names.each do |e_name|
|
200
|
+
name = e.typename == :special_variable ? "$#{e_name}" : e_name
|
201
|
+
@index_contents <<
|
202
|
+
Sitemap::Content.new("#{name} (#{e.library.name} - #{e.klass.name})", filename)
|
203
|
+
@index_contents <<
|
204
|
+
Sitemap::Content.new("#{e.klass.name}#{e.typemark}#{name} (#{e.library.name})", filename)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
pb.title.replace(e.name)
|
208
|
+
pb.inc
|
209
|
+
end
|
210
|
+
pb.finish
|
211
|
+
end
|
212
|
+
@html_files.sort!
|
213
|
+
create_file(@outputdir + 'refm.hhp', HHP_SKEL, true)
|
214
|
+
create_file(@outputdir + 'refm.hhc', HHC_SKEL, true)
|
215
|
+
create_file(@outputdir + 'refm.hhk', HHK_SKEL, true)
|
216
|
+
create_file(@outputdir + 'library/index.html', manager.library_index_screen(db.libraries.sort, {:database => db}).body)
|
217
|
+
create_file(@outputdir + 'class/index.html', manager.class_index_screen(db.classes.sort, {:database => db}).body)
|
218
|
+
FileUtils.cp(@manager_config[:themedir] + @manager_config[:css_url],
|
219
|
+
@outputdir.to_s, {:verbose => true, :preserve => true})
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def create_manager_config
|
225
|
+
@manager_config = {
|
226
|
+
:baseurl => 'http://example.com/',
|
227
|
+
:suffix => '.html',
|
228
|
+
:templatedir => srcdir_root + 'data'+ 'bitclust' + 'template',
|
229
|
+
:themedir => srcdir_root + 'theme' + 'default',
|
230
|
+
:css_url => 'style.css',
|
231
|
+
:cgi_url => '',
|
232
|
+
:tochm_mode => true
|
233
|
+
}
|
234
|
+
@manager_config[:urlmapper] = URLMapperEx.new(@manager_config)
|
235
|
+
end
|
236
|
+
|
237
|
+
def create_html_file(entry, manager, outputdir, db)
|
238
|
+
html = manager.entry_screen(entry, {:database => db}).body
|
239
|
+
e = entry.is_a?(Array) ? entry.sort.first : entry
|
240
|
+
path = case e.type_id
|
241
|
+
when :library, :class, :doc
|
242
|
+
outputdir + e.type_id.to_s + (NameUtils.encodename_fs(e.name) + '.html')
|
243
|
+
when :method
|
244
|
+
outputdir + e.type_id.to_s + NameUtils.encodename_fs(e.klass.name) +
|
245
|
+
e.typechar + (NameUtils.encodename_fs(e.name) + '.html')
|
246
|
+
else
|
247
|
+
raise
|
248
|
+
end
|
249
|
+
FileUtils.mkdir_p(path.dirname) unless path.dirname.directory?
|
250
|
+
path.open('w') do |f|
|
251
|
+
f.write(html)
|
252
|
+
end
|
253
|
+
path.relative_path_from(outputdir).to_s
|
254
|
+
end
|
255
|
+
|
256
|
+
def create_file(path, skel, sjis_flag = false)
|
257
|
+
$stderr.print("creating #{path} ...")
|
258
|
+
str = ERB.new(skel).result(binding)
|
259
|
+
# TODO Use String#encode when we drop 1.8 support
|
260
|
+
str = str.tosjis if sjis_flag
|
261
|
+
path.open('w') do |f|
|
262
|
+
f.write(str)
|
263
|
+
end
|
264
|
+
$stderr.puts(" done.")
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
require 'bitclust/crossrubyutils'
|
5
|
+
|
6
|
+
module BitClust
|
7
|
+
module Subcommands
|
8
|
+
class ClassesCommand < Subcommand
|
9
|
+
include CrossRubyUtils
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super
|
13
|
+
@rejects = []
|
14
|
+
@verbose = false
|
15
|
+
@parser.banner = "Usage: #{File.basename($0, '.*')} [-r<lib>] <lib>"
|
16
|
+
@parser.on('-r', '--reject=LIB', 'Reject library LIB') {|lib|
|
17
|
+
@rejects.concat lib.split(',')
|
18
|
+
}
|
19
|
+
@parser.on('-v', '--verbose', 'Show all ruby version.') {
|
20
|
+
@verbose = true
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse(argv)
|
25
|
+
super
|
26
|
+
option_error('wrong number of arguments') unless argv.size == 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def exec(argv, options)
|
30
|
+
lib = argv[0]
|
31
|
+
print_crossruby_table {|ruby| defined_classes(ruby, lib, @rejects) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def defined_classes(ruby, lib, rejects)
|
35
|
+
script = <<-SCRIPT
|
36
|
+
def class_extent
|
37
|
+
result = []
|
38
|
+
ObjectSpace.each_object(Module) do |c|
|
39
|
+
result.push c
|
40
|
+
end
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
%w(#{rejects.join(" ")}).each do |lib|
|
45
|
+
begin
|
46
|
+
require lib
|
47
|
+
rescue LoadError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
if "#{lib}" == "_builtin"
|
51
|
+
class_extent().each do |c|
|
52
|
+
puts c
|
53
|
+
end
|
54
|
+
else
|
55
|
+
before = class_extent()
|
56
|
+
begin
|
57
|
+
require "#{lib}"
|
58
|
+
rescue LoadError
|
59
|
+
$stderr.puts "\#{RUBY_VERSION} (\#{RUBY_RELEASE_DATE}): library not exist: #{lib}"
|
60
|
+
exit
|
61
|
+
end
|
62
|
+
after = class_extent()
|
63
|
+
(after - before).each do |c|
|
64
|
+
puts c
|
65
|
+
end
|
66
|
+
end
|
67
|
+
SCRIPT
|
68
|
+
output = `#{ruby} -e '#{script}'`
|
69
|
+
output.split
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
require 'bitclust'
|
5
|
+
require 'bitclust/subcommand'
|
6
|
+
|
7
|
+
module BitClust
|
8
|
+
module Subcommands
|
9
|
+
class ExtractCommand < Subcommand
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super
|
13
|
+
@parser.banner = "Usage: #{File.basename($0, '.*')} <file>..."
|
14
|
+
@parser.on('-c', '--check-only', 'Check syntax and output status.') {
|
15
|
+
@check_only = true
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def exec(argv, options)
|
20
|
+
success = true
|
21
|
+
argv.each do |path|
|
22
|
+
begin
|
23
|
+
lib = RRDParser.parse_stdlib_file(path)
|
24
|
+
if @check_only
|
25
|
+
$stderr.puts "#{path}: OK"
|
26
|
+
else
|
27
|
+
show_library lib
|
28
|
+
end
|
29
|
+
rescue WriterError => err
|
30
|
+
raise if $DEBUG
|
31
|
+
$stderr.puts "#{File.basename($0, '.*')}: FAIL: #{err.message}"
|
32
|
+
success = false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
exit success
|
36
|
+
end
|
37
|
+
|
38
|
+
def show_library(lib)
|
39
|
+
puts "= Library #{lib.name}"
|
40
|
+
lib.classes.each do |c|
|
41
|
+
puts "#{c.type} #{c.name}"
|
42
|
+
c.each do |m|
|
43
|
+
puts "\t* #{m.klass.name}#{m.typemark}#{m.names.join(',')}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
unless lib.methods.empty?
|
47
|
+
puts "Additional Methods:"
|
48
|
+
lib.methods.each do |m|
|
49
|
+
puts "\t* #{m.klass.name}#{m.typemark}#{m.names.join(',')}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# Copyright (c) 2006-2007 Minero Aoki
|
2
|
+
#
|
3
|
+
# This program is free software.
|
4
|
+
# You can distribute/modify this program under the Ruby License.
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'bitclust'
|
8
|
+
require 'bitclust/subcommand'
|
9
|
+
|
10
|
+
module BitClust
|
11
|
+
module Subcommands
|
12
|
+
class HtmlfileCommand < Subcommand
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
@target = nil
|
16
|
+
@templatedir = srcdir_root + "data/bitclust/template.offline"
|
17
|
+
@baseurl = "file://" + srcdir_root.to_s
|
18
|
+
@version = "2.0.0"
|
19
|
+
@parser.banner = "Usage: #{File.basename($0, '.*')} htmlfile [options] rdfile"
|
20
|
+
@parser.on('--target=NAME', 'Compile NAME to HTML.') {|name|
|
21
|
+
@target = name
|
22
|
+
}
|
23
|
+
@parser.on('--force', '-f', 'Force to use rd_file template.') {|name|
|
24
|
+
@rd_file = true
|
25
|
+
}
|
26
|
+
@parser.on('--ruby_version=VER', '--ruby=VER', 'Set Ruby version') {|version|
|
27
|
+
@version = version
|
28
|
+
}
|
29
|
+
@parser.on('--baseurl=URL', 'Base URL of generated HTML') {|url|
|
30
|
+
@baseurl = url
|
31
|
+
}
|
32
|
+
@parser.on('--templatedir=PATH', 'Template directory') {|path|
|
33
|
+
@templatedir = path
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def exec(argv, options)
|
38
|
+
db = MethodDatabase.dummy({'version' => @version})
|
39
|
+
if options[:prefix]
|
40
|
+
db = MethodDatabase.new(options[:prefix])
|
41
|
+
end
|
42
|
+
@capi = options[:capi]
|
43
|
+
target_file = argv[0]
|
44
|
+
options = { 'version' => @version }
|
45
|
+
manager = ScreenManager.new(:templatedir => @templatedir,
|
46
|
+
:base_url => @baseurl,
|
47
|
+
:cgi_url => @baseurl,
|
48
|
+
:default_encoding => 'utf-8')
|
49
|
+
|
50
|
+
unless @rd_file
|
51
|
+
begin
|
52
|
+
if @capi
|
53
|
+
lib = FunctionReferenceParser.parse_file(target_file, options)
|
54
|
+
unless @target
|
55
|
+
raise NotImplementedError, "generating a C API html without --target=NAME is not implemented yet."
|
56
|
+
end
|
57
|
+
else
|
58
|
+
lib = RRDParser.parse_stdlib_file(target_file, options)
|
59
|
+
end
|
60
|
+
entry = @target ? lookup(lib, @target) : lib
|
61
|
+
puts manager.entry_screen(entry, { :database => db }).body
|
62
|
+
return
|
63
|
+
rescue ParseError => ex
|
64
|
+
$stderr.puts ex.message
|
65
|
+
$stderr.puts ex.backtrace[0], ex.backtrace[1..-1].map{|s| "\tfrom " + s}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
begin
|
70
|
+
entry = DocEntry.new(db, target_file)
|
71
|
+
source = Preprocessor.read(target_file, options)
|
72
|
+
entry.source = source
|
73
|
+
puts manager.doc_screen(entry, { :database => db }).body
|
74
|
+
rescue WriterError => ex
|
75
|
+
$stderr.puts ex.message
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def lookup(lib, key)
|
83
|
+
case
|
84
|
+
when @capi && NameUtils.functionname?(key)
|
85
|
+
lib.find {|func| func.name == key}
|
86
|
+
when NameUtils.method_spec?(key)
|
87
|
+
spec = MethodSpec.parse(key)
|
88
|
+
if spec.constant?
|
89
|
+
begin
|
90
|
+
lib.fetch_class(key)
|
91
|
+
rescue UserError
|
92
|
+
lib.fetch_methods(spec)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
lib.fetch_methods(spec)
|
96
|
+
end
|
97
|
+
when NameUtils.classname?(key)
|
98
|
+
lib.fetch_class(key)
|
99
|
+
else
|
100
|
+
raise InvalidKey, "wrong search key: #{key.inspect}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|