bitclust-core 0.5.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.
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,39 @@
1
+ unless Object.method_defined?(:__send)
2
+ class Object
3
+ alias __send __send__
4
+ end
5
+ end
6
+
7
+ unless Object.method_defined?(:funcall)
8
+ class Object
9
+ alias funcall __send
10
+ end
11
+ end
12
+
13
+ unless Fixnum.method_defined?(:ord)
14
+ class Fixnum
15
+ def ord
16
+ self
17
+ end
18
+ end
19
+ end
20
+
21
+ unless String.method_defined?(:lines)
22
+ class String
23
+ alias lines to_a
24
+ end
25
+ end
26
+
27
+ unless String.method_defined?(:bytesize)
28
+ class String
29
+ alias bytesize size
30
+ end
31
+ end
32
+
33
+ def fopen(*args, &block)
34
+ option = args[1]
35
+ if option and !Object.const_defined?(:Encoding)
36
+ args[1] = option.sub(/:.*\z/, '')
37
+ end
38
+ File.open(*args, &block)
39
+ end
@@ -0,0 +1,531 @@
1
+ #
2
+ # bitclust/completion.rb
3
+ #
4
+ # Copyright (c) 2006-2008 Minero Aoki
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the Ruby License.
8
+ #
9
+
10
+ module BitClust
11
+
12
+ module Completion
13
+
14
+ private
15
+
16
+ #
17
+ # Completion Search
18
+ #
19
+
20
+ def _search_classes(pattern)
21
+ expand_ic(classes(), pattern)
22
+ end
23
+
24
+ def _search_methods(pattern)
25
+ case
26
+ when pattern.empty?
27
+ recordclass = SearchResult::Record
28
+ SearchResult.new(self, pattern, classes(),
29
+ methods().map {|m| s = m.spec; recordclass.new(s, s, m) })
30
+ when pattern.special_variable?
31
+ c = fetch_class('Kernel')
32
+ SearchResult.new(self, pattern, [c], search_svar(c, pattern.method))
33
+ when pattern.class?
34
+ search_methods_from_cname(pattern)
35
+ else
36
+ case
37
+ when pattern.klass && pattern.method
38
+ GC.disable; x =
39
+ search_methods_from_cname_mname(pattern)
40
+ GC.enable; GC.start; x
41
+ when pattern.method
42
+ search_methods_from_mname(pattern)
43
+ when pattern.klass && pattern.type
44
+ search_methods_from_cname(pattern)
45
+ when pattern.type
46
+ raise 'type only search is not supportted yet'
47
+ else
48
+ raise 'must not happen'
49
+ end
50
+ end
51
+ end
52
+
53
+ def _search_functions(pattern)
54
+ expand_ic(functions(), pattern)
55
+ end
56
+
57
+ def search_svar(c, pattern)
58
+ expand(c.special_variables, pattern)\
59
+ .map {|m| SearchResult::Record.new(self, m.spec, m.spec, m) }
60
+ end
61
+
62
+ def search_methods_from_cname(pattern)
63
+ cs = expand_ic(classes(), pattern.klass)
64
+ return SearchResult.new(self, pattern, [], []) if cs.empty?
65
+ recs = cs.map {|c|
66
+ c.entries.map {|m|
67
+ if not pattern.type or m.typemark == pattern.type
68
+ s = m.spec
69
+ SearchResult::Record.new(s, s, m)
70
+ else
71
+ nil
72
+ end
73
+ }.compact
74
+ }.flatten
75
+ SearchResult.new(self, pattern, cs, recs)
76
+ end
77
+
78
+ def mspec_from_cref_mname(cref, name)
79
+ m = /\A(#{NameUtils::CLASS_PATH_RE})(#{NameUtils::TYPEMARK_RE})\Z/.match(cref)
80
+ MethodSpec.new(m[1], m[2], name)
81
+ end
82
+
83
+ def search_methods_from_mname(pattern)
84
+ #timer_init
85
+ names = expand_name_narrow(method_names(), pattern.method)
86
+ #split_time "m expandN (#{names.size})"
87
+ records = names.map {|name|
88
+ crefs = mname2crefs_narrow(name)
89
+ #split_time "c expand (#{crefs.size})"
90
+ crefs.map {|cref|
91
+ spec = mspec_from_cref_mname(classid2name(cref), name)
92
+ SearchResult::Record.new(self, spec, spec)
93
+ }
94
+ }.flatten
95
+ SearchResult.new(self, pattern, [], records)
96
+ end
97
+
98
+ def search_methods_from_cname_mname(pattern)
99
+ #timer_init
100
+ recs = try(typechars(pattern.type, pattern.method)) {|ts|
101
+ expand_method_name(pattern.klass, ts, pattern.method)
102
+ }
103
+ SearchResult.new(self, pattern, recs.map {|rec| rec.class_name }, recs)
104
+ end
105
+
106
+ def expand_method_name(c, ts, m)
107
+ names_w = expand_name_wide(method_names(), m)
108
+ return [] if names_w.empty?
109
+ #split_time "m expandW (#{names_w.size})"
110
+ names_n = squeeze_names(names_w, m)
111
+ #split_time "m squeeze (#{names_n.size})"
112
+ if names_n.empty?
113
+ recs = make_cm_combination(c, ts, names_w)
114
+ nclass = count_class(recs)
115
+ #split_time "c expandW (#{nclass}c x #{$cm_comb_m}m -> #{recs.size})"
116
+ else
117
+ recs = make_cm_combination(c, ts, names_n)
118
+ nclass = count_class(recs)
119
+ #split_time "c expandN (#{nclass}c x #{$cm_comb_m}m -> #{recs.size})"
120
+ if recs.empty?
121
+ recs = make_cm_combination(c, ts, names_w)
122
+ nclass = count_class(recs)
123
+ #split_time "c expandW (#{nclass}c x #{$cm_comb_m}m -> #{recs.size})"
124
+ end
125
+ end
126
+ return [] if recs.empty?
127
+ urecs = nclass > 50 ? recs : unify_entries(recs)
128
+ #split_time "unify (#{urecs.size})"
129
+ urecs
130
+ end
131
+
132
+ def timer_init
133
+ @ts = [Time.now]
134
+ end
135
+
136
+ def split_time(msg)
137
+ @ts.push Time.now
138
+ $stderr.puts "#{@ts.size - 1}: #{'%.3f' % (@ts[-1] - @ts[-2])}: #{msg}"
139
+ end
140
+
141
+ def count_class(recs)
142
+ recs.map {|rec| rec.class_name }.uniq.size
143
+ end
144
+
145
+ def make_cm_combination(cpat, ts, mnames)
146
+ result = []
147
+ $cm_comb_m = 0
148
+ mnames.each do |m|
149
+ crefs = expand_name_narrow(mname2crefs_wide(m), cpat, ts)
150
+ next if crefs.empty?
151
+ $cm_comb_m += 1
152
+ crefs.each do |ref|
153
+ spec = MethodSpec.new(classid2name(ref.chop), ref[-1,1], m)
154
+ result.push SearchResult::Record.new(self, spec)
155
+ end
156
+ end
157
+ result
158
+ end
159
+
160
+ def try(candidates)
161
+ candidates.each do |c|
162
+ result = yield c
163
+ return result unless result.empty?
164
+ end
165
+ []
166
+ end
167
+
168
+ def typechars(type, mpattern)
169
+ if type then [type]
170
+ elsif /\A[A-Z]/ =~ mpattern then [':', '.#']
171
+ else ['.#', ':']
172
+ end
173
+ end
174
+
175
+ def unify_entries(ents)
176
+ h = {}
177
+ ents.each do |ent|
178
+ if ent0 = h[ent]
179
+ ent0.merge ent
180
+ else
181
+ h[ent] = ent
182
+ end
183
+ end
184
+ h.values
185
+ end
186
+
187
+ # Case-insensitive search. Optimized for constant search.
188
+ def expand_ic(xs, pattern)
189
+ re1 = /\A#{Regexp.quote(pattern)}/i
190
+ result1 = xs.select {|x| x.name_match?(re1) }
191
+ return [] if result1.empty?
192
+ return result1 if result1.size == 1
193
+ re2 = /\A#{Regexp.quote(pattern)}\z/i
194
+ result2 = result1.select {|x| x.name_match?(re2) }
195
+ return result1 if result2.empty?
196
+ return result2 if result2.size == 1 # no mean
197
+ result2
198
+ end
199
+
200
+ def expand(xs, pattern)
201
+ re1 = /\A#{Regexp.quote(pattern)}/i
202
+ result1 = xs.select {|x| x.name_match?(re1) }
203
+ return [] if result1.empty?
204
+ return result1 if result1.size == 1
205
+ re2 = /\A#{Regexp.quote(pattern)}\z/i
206
+ result2 = result1.select {|x| x.name_match?(re2) }
207
+ return result1 if result2.empty?
208
+ return result2 if result2.size == 1
209
+ result3 = result2.select {|x| x.name?(pattern) }
210
+ return result2 if result3.empty?
211
+ return result3 if result3.size == 1 # no mean
212
+ result3
213
+ end
214
+
215
+ # list up all matched items (without squeezing)
216
+ def expand_name_wide(names, pattern)
217
+ re1 = /\A#{Regexp.quote(pattern)}/i
218
+ names.grep(re1)
219
+ end
220
+
221
+ # list up matched items (with squeezing)
222
+ def expand_name_narrow(names, pattern, suffixes = nil)
223
+ re1 = /\A#{Regexp.quote(pattern)}/i
224
+ result1 = names.grep(re1)
225
+ return [] if result1.empty?
226
+ return result1 if result1.size == 1
227
+ squeeze_names(result1, pattern, suffixes)
228
+ end
229
+
230
+ # squeeze result of #expand_name_wide
231
+ def squeeze_names(result1, pattern, suffixes = nil)
232
+ regexps =
233
+ [
234
+ /\A#{Regexp.quote(pattern)}.*#{suffix_pattern(suffixes)}\z/i,
235
+ /\A#{Regexp.quote(pattern)}#{suffix_pattern(suffixes)}\z/i,
236
+ /\A#{Regexp.quote(pattern)}#{suffix_pattern(suffixes)}\z/,
237
+ ]
238
+ result = result1
239
+ regexps.each do |re|
240
+ new_result = result.grep(re)
241
+ return result if new_result.empty?
242
+ return new_result if new_result.size == 1
243
+ result = new_result
244
+ end
245
+ return result
246
+ end
247
+
248
+ def suffix_pattern(suffixes)
249
+ return '' unless suffixes
250
+ "[#{Regexp.quote(suffixes)}]"
251
+ end
252
+
253
+ #
254
+ # Index
255
+ #
256
+
257
+ def save_completion_index
258
+ save_class_index
259
+ save_method_index
260
+ save_method_index_narrow
261
+ end
262
+
263
+ def intern_classname(name)
264
+ intern_table()[name]
265
+ end
266
+
267
+ def intern_table
268
+ @intern_table ||=
269
+ begin
270
+ h = {}
271
+ classnametable().each do |id, names|
272
+ names.each do |n|
273
+ h[n] = id
274
+ end
275
+ end
276
+ h
277
+ end
278
+ end
279
+
280
+ def save_class_index
281
+ atomic_write_open('class/=index') {|f|
282
+ classes().each do |c|
283
+ #f.puts "#{c.id}\t#{c.names.join(' ')}" # FIXME: support class alias
284
+ f.puts "#{c.id}\t#{c.name}"
285
+ end
286
+ }
287
+ end
288
+
289
+ def classnametable
290
+ @classnametable ||=
291
+ begin
292
+ h = {}
293
+ foreach_line('class/=index') do |line|
294
+ id, *names = line.split
295
+ h[id] = names
296
+ end
297
+ h
298
+ rescue Errno::ENOENT
299
+ {}
300
+ end
301
+ end
302
+
303
+ def save_method_index_narrow
304
+ index =
305
+ begin
306
+ h = {}
307
+ classes().each do |c|
308
+ c.entries.each do |m|
309
+ ref = c.id + m.typemark
310
+ m.names.each do |name|
311
+ (h[name] ||= []).push ref
312
+ end
313
+ end
314
+ end
315
+ h
316
+ end
317
+ atomic_write_open('method/=sindex') {|f|
318
+ index.keys.sort.each do |name|
319
+ f.puts "#{name}\t#{index[name].join(' ')}"
320
+ end
321
+ }
322
+ end
323
+
324
+ def method_index_small
325
+ @method_index_small ||=
326
+ begin
327
+ h = {}
328
+ foreach_line('method/=sindex') do |line|
329
+ name, *crefs = line.split(nil)
330
+ h[name] = crefs
331
+ end
332
+ h
333
+ end
334
+ end
335
+
336
+ # canonical class name, no inheritance
337
+ def mname2crefs_narrow(name)
338
+ method_index_small()[name]
339
+ end
340
+
341
+ def save_method_index
342
+ index =
343
+ begin
344
+ h = {}
345
+ classes().each do |c|
346
+ [ ['#', c._imap.keys],
347
+ ['.', c._smap.keys],
348
+ [':', c._cmap.keys] ].each do |t, names|
349
+ ref = c.id + t
350
+ names.each do |name|
351
+ (h[name] ||= []).push ref
352
+ end
353
+ end
354
+ end
355
+ h
356
+ end
357
+ atomic_write_open('method/=index') {|f|
358
+ index.keys.sort.each do |name|
359
+ f.puts "#{name}\t#{index[name].join(' ')}"
360
+ end
361
+ }
362
+ end
363
+
364
+ def method_names
365
+ @method_names ||= method_index_0().keys
366
+ end
367
+
368
+ # includes class aliases, includes inherited methods
369
+ def mname2crefs_wide(name)
370
+ tbl = classnametable()
371
+ mname2crefs_0(name).map {|ref|
372
+ tbl[ref.chop].map {|c| c + ref[-1,1] }
373
+ }.flatten
374
+ end
375
+
376
+ def mname2crefs_0(name)
377
+ crefs = (@method_index ||= {})[name]
378
+ return crefs if crefs
379
+ crefsstr = method_index_0()[name] or return nil
380
+ @method_index[name] = crefsstr.split(nil)
381
+ end
382
+
383
+ def method_index_0
384
+ @method_index_0 ||=
385
+ begin
386
+ h = {}
387
+ foreach_line('method/=index') do |line|
388
+ name, cnames = line.split(nil, 2)
389
+ h[name] = cnames
390
+ end
391
+ h
392
+ end
393
+ end
394
+
395
+ end
396
+
397
+
398
+ class SearchResult
399
+
400
+ def SearchResult.empty(db, pattern)
401
+ new(db, pattern, [], [])
402
+ end
403
+
404
+ def initialize(db, pattern, classes, records)
405
+ @database = db
406
+ @pattern = pattern
407
+ @classes = classes
408
+ @records = records
409
+ end
410
+
411
+ attr_reader :database
412
+ attr_reader :pattern
413
+ attr_reader :classes
414
+ attr_reader :records
415
+
416
+ def inspect
417
+ "\#<BitClust::SearchResult @pattern=#{@pattern.inspect} @classes=#{@classes.inspect} @database=#{@database.inspect} @records=[#{record.inspect}, ...] >"
418
+ end
419
+
420
+ def fail?
421
+ @records.empty?
422
+ end
423
+
424
+ def success?
425
+ not @records.empty?
426
+ end
427
+
428
+ def determined?
429
+ @records.size == 1
430
+ end
431
+
432
+ def name
433
+ @records.first.name
434
+ end
435
+
436
+ def names
437
+ @records.map {|rec| rec.names }.flatten
438
+ end
439
+
440
+ def record
441
+ @records.first
442
+ end
443
+
444
+ def each_record(&block)
445
+ @records.sort.each(&block)
446
+ end
447
+
448
+ class Record
449
+ def initialize(db, spec, origin = nil, entry = nil)
450
+ @db = db
451
+ @specs = [spec]
452
+ @origin = origin
453
+ @entry = entry
454
+ end
455
+
456
+ attr_writer :db
457
+ attr_reader :specs
458
+
459
+ def origin
460
+ @origin ||=
461
+ begin
462
+ spec = @specs.first
463
+ c = @db.fetch_class(spec.klass)
464
+ MethodSpec.parse(c.match_entry(spec.type, spec.method))
465
+ end
466
+ end
467
+
468
+ def idstring
469
+ origin().to_s
470
+ end
471
+
472
+ def entry
473
+ @entry ||= @db.get_method(origin())
474
+ end
475
+
476
+ def name
477
+ names().first
478
+ end
479
+
480
+ def names
481
+ @specs.map {|spec| spec.display_name }
482
+ end
483
+
484
+ def class_name
485
+ @specs.first.klass
486
+ end
487
+
488
+ def method_name
489
+ @specs.first.method
490
+ end
491
+
492
+ def original_name
493
+ @idstring
494
+ end
495
+
496
+ def ==(other)
497
+ @idstring == other.idstring
498
+ end
499
+
500
+ alias eql? ==
501
+
502
+ def hash
503
+ @hash ||= idstring().hash
504
+ end
505
+
506
+ def <=>(other)
507
+ entry() <=> other.entry
508
+ end
509
+
510
+ def merge(other)
511
+ @specs |= other.specs
512
+ end
513
+
514
+
515
+ def owned_method?
516
+ @specs.any? {|spec| spec.klass == origin().klass }
517
+ end
518
+
519
+ def method_of_alias_class?
520
+ @entry.klass.aliases.any?{|aliasclass| aliasclass.name?(class_name())}
521
+ end
522
+
523
+ def inherited_method?
524
+ !owned_method?() && !method_of_alias_class?()
525
+ end
526
+
527
+ end
528
+
529
+ end
530
+
531
+ end