bitclust-core 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. data/ChangeLog +2907 -0
  2. data/Gemfile +7 -0
  3. data/README +21 -0
  4. data/Rakefile +20 -0
  5. data/bin/bitclust +14 -0
  6. data/bin/refe +36 -0
  7. data/bitclust-dev.gemspec +33 -0
  8. data/bitclust.gemspec +30 -0
  9. data/config.in +23 -0
  10. data/config.ru +48 -0
  11. data/config.ru.sample +31 -0
  12. data/data/bitclust/catalog/ja_JP.EUC-JP +78 -0
  13. data/data/bitclust/catalog/ja_JP.UTF-8 +78 -0
  14. data/data/bitclust/template.lillia/class +98 -0
  15. data/data/bitclust/template.lillia/class-index +28 -0
  16. data/data/bitclust/template.lillia/doc +48 -0
  17. data/data/bitclust/template.lillia/layout +19 -0
  18. data/data/bitclust/template.lillia/library +129 -0
  19. data/data/bitclust/template.lillia/library-index +32 -0
  20. data/data/bitclust/template.lillia/method +20 -0
  21. data/data/bitclust/template.lillia/rd_file +6 -0
  22. data/data/bitclust/template.offline/class +67 -0
  23. data/data/bitclust/template.offline/class-index +28 -0
  24. data/data/bitclust/template.offline/doc +13 -0
  25. data/data/bitclust/template.offline/function +22 -0
  26. data/data/bitclust/template.offline/function-index +24 -0
  27. data/data/bitclust/template.offline/layout +18 -0
  28. data/data/bitclust/template.offline/library +87 -0
  29. data/data/bitclust/template.offline/library-index +32 -0
  30. data/data/bitclust/template.offline/method +21 -0
  31. data/data/bitclust/template.offline/rd_file +6 -0
  32. data/data/bitclust/template/class +133 -0
  33. data/data/bitclust/template/class-index +30 -0
  34. data/data/bitclust/template/doc +14 -0
  35. data/data/bitclust/template/function +21 -0
  36. data/data/bitclust/template/function-index +25 -0
  37. data/data/bitclust/template/layout +19 -0
  38. data/data/bitclust/template/library +89 -0
  39. data/data/bitclust/template/library-index +35 -0
  40. data/data/bitclust/template/method +24 -0
  41. data/data/bitclust/template/opensearchdescription +10 -0
  42. data/data/bitclust/template/search +57 -0
  43. data/lib/bitclust.rb +9 -0
  44. data/lib/bitclust/app.rb +129 -0
  45. data/lib/bitclust/classentry.rb +425 -0
  46. data/lib/bitclust/compat.rb +39 -0
  47. data/lib/bitclust/completion.rb +531 -0
  48. data/lib/bitclust/crossrubyutils.rb +91 -0
  49. data/lib/bitclust/database.rb +181 -0
  50. data/lib/bitclust/docentry.rb +83 -0
  51. data/lib/bitclust/entry.rb +223 -0
  52. data/lib/bitclust/exception.rb +38 -0
  53. data/lib/bitclust/functiondatabase.rb +115 -0
  54. data/lib/bitclust/functionentry.rb +81 -0
  55. data/lib/bitclust/functionreferenceparser.rb +76 -0
  56. data/lib/bitclust/htmlutils.rb +80 -0
  57. data/lib/bitclust/interface.rb +87 -0
  58. data/lib/bitclust/libraryentry.rb +211 -0
  59. data/lib/bitclust/lineinput.rb +165 -0
  60. data/lib/bitclust/messagecatalog.rb +95 -0
  61. data/lib/bitclust/methoddatabase.rb +401 -0
  62. data/lib/bitclust/methodentry.rb +202 -0
  63. data/lib/bitclust/methodid.rb +209 -0
  64. data/lib/bitclust/methodsignature.rb +82 -0
  65. data/lib/bitclust/nameutils.rb +236 -0
  66. data/lib/bitclust/parseutils.rb +60 -0
  67. data/lib/bitclust/preprocessor.rb +273 -0
  68. data/lib/bitclust/rdcompiler.rb +507 -0
  69. data/lib/bitclust/refsdatabase.rb +66 -0
  70. data/lib/bitclust/requesthandler.rb +330 -0
  71. data/lib/bitclust/ridatabase.rb +349 -0
  72. data/lib/bitclust/rrdparser.rb +522 -0
  73. data/lib/bitclust/runner.rb +143 -0
  74. data/lib/bitclust/screen.rb +554 -0
  75. data/lib/bitclust/searcher.rb +518 -0
  76. data/lib/bitclust/server.rb +59 -0
  77. data/lib/bitclust/simplesearcher.rb +84 -0
  78. data/lib/bitclust/subcommand.rb +746 -0
  79. data/lib/bitclust/textutils.rb +51 -0
  80. data/lib/bitclust/version.rb +3 -0
  81. data/packer.rb +224 -0
  82. data/refe2.gemspec +29 -0
  83. data/server.exe +0 -0
  84. data/server.exy +159 -0
  85. data/server.rb +10 -0
  86. data/setup.rb +1596 -0
  87. data/standalone.rb +193 -0
  88. data/test/run_test.rb +15 -0
  89. data/test/test_bitclust.rb +81 -0
  90. data/test/test_entry.rb +39 -0
  91. data/test/test_functiondatabase.rb +55 -0
  92. data/test/test_libraryentry.rb +31 -0
  93. data/test/test_methoddatabase.rb +81 -0
  94. data/test/test_methodsignature.rb +14 -0
  95. data/test/test_nameutils.rb +324 -0
  96. data/test/test_preprocessor.rb +84 -0
  97. data/test/test_rdcompiler.rb +534 -0
  98. data/test/test_refsdatabase.rb +76 -0
  99. data/test/test_rrdparser.rb +26 -0
  100. data/test/test_runner.rb +102 -0
  101. data/test/test_simplesearcher.rb +48 -0
  102. data/theme/default/images/external.png +0 -0
  103. data/theme/default/rurema.png +0 -0
  104. data/theme/default/style.css +288 -0
  105. data/theme/default/test.css +254 -0
  106. data/theme/lillia/rurema.png +0 -0
  107. data/theme/lillia/style.css +331 -0
  108. data/theme/lillia/test.css +254 -0
  109. data/tools/bc-ancestors.rb +153 -0
  110. data/tools/bc-checkparams.rb +246 -0
  111. data/tools/bc-classes.rb +80 -0
  112. data/tools/bc-convert.rb +165 -0
  113. data/tools/bc-list.rb +63 -0
  114. data/tools/bc-methods.rb +171 -0
  115. data/tools/bc-preproc.rb +42 -0
  116. data/tools/bc-rdoc.rb +343 -0
  117. data/tools/bc-tochm.rb +301 -0
  118. data/tools/bc-tohtml.rb +125 -0
  119. data/tools/bc-tohtmlpackage.rb +241 -0
  120. data/tools/check-signature.rb +19 -0
  121. data/tools/forall-ruby.rb +20 -0
  122. data/tools/gencatalog.rb +69 -0
  123. data/tools/statrefm.rb +98 -0
  124. data/tools/stattodo.rb +150 -0
  125. data/tools/update-database.rb +146 -0
  126. data/view.cgi +6 -0
  127. metadata +222 -0
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # bc-tohtml.rb
4
+ #
5
+ # Copyright (c) 2006-2007 Minero Aoki
6
+ #
7
+ # This program is free software.
8
+ # You can distribute/modify this program under the Ruby License.
9
+ #
10
+
11
+ require 'pathname'
12
+
13
+ def srcdir_root
14
+ (Pathname.new(__FILE__).realpath.dirname + '..').cleanpath
15
+ end
16
+
17
+ $LOAD_PATH.unshift srcdir_root() + 'lib'
18
+
19
+ $KCODE = 'UTF-8' unless Object.const_defined?(:Encoding)
20
+
21
+ require 'bitclust'
22
+ require 'optparse'
23
+
24
+ def main
25
+ templatedir = srcdir_root() + 'data' + 'bitclust' + 'template.offline'
26
+ target = nil
27
+ baseurl = 'file://' + srcdir_root.to_s
28
+ parser = OptionParser.new
29
+ ver = '1.9.0'
30
+ parser.banner = "Usage: #{File.basename($0, '.*')} rdfile"
31
+ parser.on('--target=NAME', 'Compile NAME to HTML.') {|name|
32
+ target = name
33
+ }
34
+ parser.on('--force', '-f', 'Force to use rd_file template.') {|name|
35
+ @rd_file = true
36
+ }
37
+ parser.on('--ruby_version=VER', '--ruby=VER', 'Set Ruby version') {|v|
38
+ ver = v
39
+ }
40
+ parser.on('--db=DB', '--database=DB', 'Set database path') {|path|
41
+ db = BitClust::Database.new(path)
42
+ }
43
+ parser.on('--baseurl=URL', 'Base URL of generated HTML') {|url|
44
+ baseurl = url
45
+ }
46
+ parser.on('--templatedir=PATH', 'Template directory') {|path|
47
+ templatedir = path
48
+ }
49
+ parser.on('--help', 'Prints this message and quit.') {
50
+ puts parser.help
51
+ exit 0
52
+ }
53
+ parser.on('--capi', 'C API mode.') {
54
+ @capi = true
55
+ }
56
+ begin
57
+ parser.parse!
58
+ rescue OptionParser::ParseError => err
59
+ $stderr.puts err.message
60
+ $stderr.puts parser.help
61
+ exit 1
62
+ end
63
+ if ARGV.size > 1
64
+ $stderr.puts "too many arguments (expected 1)"
65
+ exit 1
66
+ end
67
+
68
+ db ||= BitClust::Database.dummy({'version' => ver})
69
+ manager = BitClust::ScreenManager.new(
70
+ :templatedir => templatedir,
71
+ :base_url => baseurl,
72
+ :cgi_url => baseurl,
73
+ :default_encoding => 'utf-8')
74
+ unless @rd_file
75
+ begin
76
+ if @capi
77
+ lib = BitClust::FunctionReferenceParser.parse_file(ARGV[0], {'version' => ver})
78
+ unless target
79
+ raise NotImplementedError, "generating a C API html without --target=NAME is not implemented yet."
80
+ end
81
+ else
82
+ lib = BitClust::RRDParser.parse_stdlib_file(ARGV[0], {'version' => ver})
83
+ end
84
+ entry = target ? lookup(lib, target) : lib
85
+ puts manager.entry_screen(entry, {:database => db}).body
86
+ return
87
+ rescue BitClust::ParseError => ex
88
+ $stderr.puts ex.message
89
+ $stderr.puts ex.backtrace[0], ex.backtrace[1..-1].map{|s| "\tfrom " + s}
90
+ end
91
+ end
92
+
93
+ ent = BitClust::DocEntry.new(db, ARGV[0])
94
+ ret = BitClust::Preprocessor.read(ARGV[0], {'version' => ver})
95
+ ent.source = ret
96
+ puts manager.doc_screen(ent, {:database => db} ).body
97
+ return
98
+ rescue BitClust::WriterError => err
99
+ $stderr.puts err.message
100
+ exit 1
101
+ end
102
+
103
+ def lookup(lib, key)
104
+ case
105
+ when @capi && BitClust::NameUtils.functionname?(key)
106
+ lib.find {|func| func.name == key}
107
+ when BitClust::NameUtils.method_spec?(key)
108
+ spec = BitClust::MethodSpec.parse(key)
109
+ if spec.constant?
110
+ begin
111
+ lib.fetch_class(key)
112
+ rescue BitClust::UserError
113
+ lib.fetch_methods(spec)
114
+ end
115
+ else
116
+ lib.fetch_methods(spec)
117
+ end
118
+ when BitClust::NameUtils.classname?(key)
119
+ lib.fetch_class(key)
120
+ else
121
+ raise BitClust::InvalidKey, "wrong search key: #{key.inspect}"
122
+ end
123
+ end
124
+
125
+ main
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ require 'pathname'
4
+
5
+ def srcdir_root
6
+ (Pathname.new(__FILE__).realpath.dirname + '..').cleanpath
7
+ end
8
+ $LOAD_PATH.unshift srcdir_root() + 'lib'
9
+
10
+ require 'bitclust'
11
+ require 'fileutils'
12
+ require 'optparse'
13
+
14
+ if Object.const_defined?(:Encoding)
15
+ Encoding.default_external = 'utf-8'
16
+ end
17
+
18
+ module BitClust
19
+
20
+ class URLMapperEx < URLMapper
21
+ def library_url(name)
22
+ if name == '/'
23
+ $bitclust_html_base + "/library/index.html"
24
+ else
25
+ $bitclust_html_base + "/library/#{encodename_package(name)}.html"
26
+ end
27
+ end
28
+
29
+ def class_url(name)
30
+ $bitclust_html_base + "/class/#{encodename_package(name)}.html"
31
+ end
32
+
33
+ def method_url(spec)
34
+ cname, tmark, mname = *split_method_spec(spec)
35
+ $bitclust_html_base +
36
+ "/method/#{encodename_package(cname)}/#{typemark2char(tmark)}/#{encodename_package(mname)}.html"
37
+ end
38
+
39
+ def function_url(name)
40
+ $bitclust_html_base + "/function/#{name.empty? ? 'index' : name}.html"
41
+ end
42
+
43
+ def document_url(name)
44
+ $bitclust_html_base + "/doc/#{encodename_package(name)}.html"
45
+ end
46
+
47
+ def css_url
48
+ $bitclust_html_base + "/" + @css_url
49
+ end
50
+
51
+ def favicon_url
52
+ $bitclust_html_base + "/" + @favicon_url
53
+ end
54
+
55
+ def library_index_url
56
+ $bitclust_html_base + "/library/index.html"
57
+ end
58
+
59
+ def function_index_url
60
+ $bitclust_html_base + "/function/index.html"
61
+ end
62
+
63
+ end
64
+ end
65
+
66
+ def main
67
+ prefix = Pathname.new('./db')
68
+ outputdir = Pathname.new('./doc')
69
+ templatedir = srcdir_root + 'data'+ 'bitclust' + 'template'
70
+ catalogdir = nil
71
+ verbose = true
72
+ parser = OptionParser.new
73
+ parser.on('-d', '--database=PATH', 'Database prefix') do |path|
74
+ prefix = Pathname.new(path).realpath
75
+ end
76
+ parser.on('-o', '--outputdir=PATH', 'Output directory') do |path|
77
+ begin
78
+ outputdir = Pathname.new(path).realpath
79
+ rescue Errno::ENOENT
80
+ FileUtils.mkdir_p(path, :verbose => verbose)
81
+ retry
82
+ end
83
+ end
84
+ parser.on('--catalog=PATH', 'Catalog directory') do |path|
85
+ catalogdir = Pathname.new(path).realpath
86
+ end
87
+ parser.on('--templatedir=PATH', 'Template directory') do |path|
88
+ templatedir = Pathname.new(path).realpath
89
+ end
90
+ parser.on('--fs-casesensitive', 'Filesystem is case-sensitive') do
91
+ $fs_casesensitive = true
92
+ end
93
+ parser.on('--[no-]quiet', 'Be quiet') do |quiet|
94
+ verbose = !quiet
95
+ end
96
+ parser.on('--help', 'Prints this message and quit') do
97
+ puts(parser.help)
98
+ exit(0)
99
+ end
100
+ begin
101
+ parser.parse!
102
+ rescue OptionParser::ParseError => err
103
+ STDERR.puts(err.message)
104
+ STDERR.puts(parser.help)
105
+ exit(1)
106
+ end
107
+
108
+ manager_config = {
109
+ :catalogdir => catalogdir,
110
+ :suffix => '.html',
111
+ :templatedir => templatedir,
112
+ :themedir => srcdir_root + 'theme' + 'default',
113
+ :css_url => 'style.css',
114
+ :favicon_url => 'rurema.png',
115
+ :cgi_url => '',
116
+ :tochm_mode => true
117
+ }
118
+ manager_config[:urlmapper] = BitClust::URLMapperEx.new(manager_config)
119
+
120
+ db = BitClust::MethodDatabase.new(prefix.to_s)
121
+ fdb = BitClust::FunctionDatabase.new(prefix.to_s)
122
+ manager = BitClust::ScreenManager.new(manager_config)
123
+ db.transaction do
124
+ methods = {}
125
+ db.methods.each_with_index do |entry, i|
126
+ method_name = entry.klass.name + entry.typemark + entry.name
127
+ (methods[method_name] ||= []) << entry
128
+ end
129
+ entries = db.docs + db.libraries.sort + db.classes.sort + methods.values
130
+ entries.each_with_index do |c, i|
131
+ create_html_file(c, manager, outputdir, db)
132
+ $stderr.puts("#{i}/#{entries.size} done") if i % 100 == 0 and verbose
133
+ end
134
+ end
135
+ fdb.transaction do
136
+ functions = {}
137
+ fdb.functions.each_with_index do |entry, i|
138
+ create_html_file(entry, manager, outputdir, fdb)
139
+ $stderr.puts("#{i} done") if i % 100 == 0 and verbose
140
+ end
141
+ end
142
+ $bitclust_html_base = '..'
143
+ create_file(outputdir + 'library/index.html',
144
+ manager.library_index_screen(db.libraries.sort, {:database => db}).body,
145
+ :verbose => verbose)
146
+ create_file(outputdir + 'class/index.html',
147
+ manager.class_index_screen(db.classes.sort, {:database => db}).body,
148
+ :verbose => verbose)
149
+ create_file(outputdir + 'function/index.html',
150
+ manager.function_index_screen(fdb.functions.sort, { :database => fdb }).body,
151
+ :verbose => verbose)
152
+ create_index_html(outputdir)
153
+ FileUtils.cp(manager_config[:themedir] + manager_config[:css_url],
154
+ outputdir.to_s, {:verbose => verbose, :preserve => true})
155
+ FileUtils.cp(manager_config[:themedir] + manager_config[:favicon_url],
156
+ outputdir.to_s, {:verbose => verbose, :preserve => true})
157
+ Dir.mktmpdir do |tmpdir|
158
+ FileUtils.cp_r(manager_config[:themedir] + 'images', tmpdir,
159
+ {:verbose => verbose, :preserve => true})
160
+ Dir.glob(File.join(tmpdir, 'images', '/**/.svn')).each do |d|
161
+ FileUtils.rm_r(d, {:verbose => verbose})
162
+ end
163
+ FileUtils.cp_r(File.join(tmpdir, 'images'), outputdir.to_s,
164
+ {:verbose => verbose, :preserve => true})
165
+ end
166
+ end
167
+
168
+ def encodename_package(str)
169
+ if $fs_casesensitive
170
+ BitClust::NameUtils.encodename_url(str)
171
+ else
172
+ BitClust::NameUtils.encodename_fs(str)
173
+ end
174
+ end
175
+
176
+ def create_index_html(outputdir)
177
+ path = outputdir + 'index.html'
178
+ File.open(path, 'w'){|io|
179
+ io.write <<HERE
180
+ <meta http-equiv="refresh" content="0; URL=doc/index.html">
181
+ <a href="doc/index.html">Go</a>
182
+ HERE
183
+ }
184
+ end
185
+
186
+ def create_html_file(entry, manager, outputdir, db)
187
+ e = entry.is_a?(Array) ? entry.sort.first : entry
188
+ case e.type_id
189
+ when :library, :class, :doc
190
+ $bitclust_html_base = '..'
191
+ path = outputdir + e.type_id.to_s + (encodename_package(e.name) + '.html')
192
+ create_html_file_p(entry, manager, path, db)
193
+ path.relative_path_from(outputdir).to_s
194
+ when :method
195
+ create_html_method_file(entry, manager, outputdir, db)
196
+ when :function
197
+ create_html_function_file(entry, manager, outputdir, db)
198
+ else
199
+ raise
200
+ end
201
+ e.unload
202
+ end
203
+
204
+ def create_html_method_file(entry, manager, outputdir, db)
205
+ path = nil
206
+ $bitclust_html_base = '../../..'
207
+ e = entry.is_a?(Array) ? entry.sort.first : entry
208
+ e.names.each{|name|
209
+ path = outputdir + e.type_id.to_s + encodename_package(e.klass.name) +
210
+ e.typechar + (encodename_package(name) + '.html')
211
+ create_html_file_p(entry, manager, path, db)
212
+ }
213
+ path.relative_path_from(outputdir).to_s
214
+ end
215
+
216
+ def create_html_function_file(entry, manager, outputdir, db)
217
+ path = nil
218
+ $bitclust_html_base = '..'
219
+ path = outputdir + entry.type_id.to_s + (entry.name + '.html')
220
+ create_html_file_p(entry, manager, path, db)
221
+ path.relative_path_from(outputdir).to_s
222
+ end
223
+
224
+ def create_html_file_p(entry, manager, path, db)
225
+ FileUtils.mkdir_p(path.dirname) unless path.dirname.directory?
226
+ html = manager.entry_screen(entry, {:database => db}).body
227
+ path.open('w') do |f|
228
+ f.write(html)
229
+ end
230
+ end
231
+
232
+ def create_file(path, str, options={})
233
+ verbose = options[:verbose]
234
+ STDERR.print("creating #{path} ...") if verbose
235
+ path.open('w') do |f|
236
+ f.write(str)
237
+ end
238
+ STDERR.puts(" done.") if verbose
239
+ end
240
+
241
+ main
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ $LOAD_PATH.unshift Pathname($0).realpath.dirname.dirname + 'lib'
5
+
6
+ require 'bitclust/methodsignature'
7
+
8
+ st = 0
9
+ ARGF.each do |line|
10
+ if /\A---/ =~ line
11
+ begin
12
+ BitClust::MethodSignature.parse(line)
13
+ rescue => err
14
+ $stderr.puts "#{ARGF.filename}:#{ARGF.file.lineno}: #{line.strip.inspect}"
15
+ st = 1
16
+ end
17
+ end
18
+ end
19
+ exit st
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+
5
+ bindir = Pathname.new(__FILE__).realpath.dirname
6
+ $LOAD_PATH.unshift((bindir + '../lib').realpath)
7
+
8
+ require 'bitclust/crossrubyutils'
9
+
10
+ include BitClust::CrossRubyUtils
11
+
12
+ def main
13
+ ENV.delete('GEM_HOME')
14
+ forall_ruby(ENV['PATH']) do |ruby, ver|
15
+ puts ver
16
+ system ruby, *ARGV
17
+ end
18
+ end
19
+
20
+ main
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # gencatalog.rb
4
+ #
5
+ # Copyright (c) 2008 Minero Aoki
6
+ #
7
+ # This program is free software.
8
+ # You can distribute/modify this program under the Ruby License.
9
+ #
10
+
11
+ require 'optparse'
12
+
13
+ def main
14
+ catalog_path = nil
15
+ parser = OptionParser.new
16
+ parser.banner = "Usage: #{File.basename($0)} --merge=PATH [<file>...]"
17
+ parser.on('--merge=PATH', 'Current catalog file.') {|path|
18
+ catalog_path = path
19
+ }
20
+ parser.on('--help', 'Prints this message and quit.') {
21
+ puts parser.help
22
+ exit 0
23
+ }
24
+ begin
25
+ parser.parse!
26
+ rescue OptionParser::ParseError => err
27
+ $stderr.puts err
28
+ $stderr.puts parser.help
29
+ exit 1
30
+ end
31
+
32
+ h = collect_messages(ARGF)
33
+ h.update load_catalog(catalog_path) if catalog_path
34
+ print_catalog h
35
+ end
36
+
37
+ def print_catalog(h)
38
+ h.keys.sort.each do |key|
39
+ puts key
40
+ puts h[key]
41
+ end
42
+ end
43
+
44
+ def collect_messages(f)
45
+ h = {}
46
+ f.each do |line|
47
+ line.scan(/_\(
48
+ (?: "( (?:[^"]+|\\.)* )"
49
+ | '( (?:[^']+|\\.)* )'
50
+ )
51
+ /x) do
52
+ text = ($1 || $2).strip
53
+ h[text] = text unless text.empty?
54
+ end
55
+ end
56
+ h
57
+ end
58
+
59
+ def load_catalog(path)
60
+ h = {}
61
+ File.open(path) {|f|
62
+ f.each do |line|
63
+ h[line.chomp] = f.gets.chomp
64
+ end
65
+ }
66
+ h
67
+ end
68
+
69
+ main