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,66 @@
1
+ #
2
+ # bitclust/refsdatabase.rb
3
+ #
4
+ # This program is free software.
5
+ # You can distribute this program under the Ruby License.
6
+ #
7
+
8
+ module BitClust
9
+ class RefsDatabase
10
+ def self.load(src)
11
+ if src.respond_to?(:to_str)
12
+ buf = fopen(src.to_str, 'r:UTF-8'){|f| f.read}
13
+ elsif src.respond_to?(:to_io)
14
+ buf = src.to_io.read
15
+ else
16
+ buf = src.read
17
+ end
18
+
19
+ refs = self.new
20
+ buf.each_line{|l|
21
+ if /((?:\\,|[^,])+),((?:\\,|[^,])+),((?:\\,|[^,])+),((?:\\,|[^,])+)\n/ =~ l
22
+ type, id, linkid, desc = [$1, $2, $3, $4].map{|e| e.gsub(/\\(.)/){|s| $1 == ',' ? ',' : s } }
23
+ refs[type, id, linkid] = desc
24
+ end
25
+ }
26
+ refs
27
+ end
28
+
29
+ def initialize
30
+ @h = {}
31
+ end
32
+
33
+ def []=(type, mid, linkid, desc)
34
+ @h[[type.to_s, mid, linkid]] = desc
35
+ end
36
+
37
+ def [](type, mid, linkid)
38
+ @h[[type.to_s, mid, linkid]]
39
+ end
40
+
41
+ def save(s)
42
+ if s.respond_to?(:to_str)
43
+ path = s.to_str
44
+ io = fopen(path, 'w:UTF-8')
45
+ elsif s.respond_to?(:to_io)
46
+ io = s.to_io
47
+ else
48
+ io = s
49
+ end
50
+
51
+ @h.each{|k, v|
52
+ io.write( [k, v].flatten.map{|e| e.gsub(/,/, '\\,') }.join(',') + "\n" )
53
+ }
54
+ end
55
+
56
+ def extract(entry)
57
+ entry.source.each_line{|l|
58
+ if /\A={1,6}\[a:(\w+)\] *(.*)/ =~ l
59
+ entry.labels.each{|name|
60
+ self[entry.class.type_id, name, $1] = $2
61
+ }
62
+ end
63
+ }
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,330 @@
1
+ #
2
+ # bitclust/requesthandler.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
+ require 'bitclust/compat'
11
+ require 'bitclust/screen'
12
+ require 'bitclust/methodid'
13
+ require 'bitclust/nameutils'
14
+ require 'bitclust/simplesearcher'
15
+
16
+ module BitClust
17
+
18
+ class RequestHandler
19
+
20
+ def initialize(db, manager)
21
+ if db.is_a? Array
22
+ @db, @cdb = db
23
+ else
24
+ @db = db
25
+ end
26
+ @screenmanager = manager
27
+ @conf = { :database => @db }
28
+ end
29
+
30
+ def handle(webrick_req)
31
+ _handle(Request.new(webrick_req))
32
+ rescue WEBrick::HTTPStatus::Status
33
+ raise
34
+ rescue BitClust::NotFoundError => err
35
+ return not_found_response(err)
36
+ rescue => err
37
+ return error_response(err)
38
+ end
39
+
40
+ private
41
+
42
+ def _handle(req)
43
+ return handle_doc(req) unless req.defined_type?
44
+ mid = "handle_#{req.type_id}"
45
+ unless respond_to?(mid, true)
46
+ raise RequestError, "wrong request: type_id=#{req.type_id}"
47
+ end
48
+ funcall(mid, req)
49
+ end
50
+
51
+ def error_response(err)
52
+ ErrorScreen.new(err).response
53
+ end
54
+
55
+ def not_found_response(err)
56
+ NotFoundScreen.new(err).response
57
+ end
58
+
59
+ def handle_library(req)
60
+ return library_index() unless req.library_name
61
+ lib = @db.fetch_library(req.library_name)
62
+ @screenmanager.library_screen(lib, @conf).response
63
+ end
64
+
65
+ def handle_class(req)
66
+ return class_index() unless req.class_name
67
+ c = @db.fetch_class(req.class_name)
68
+ h = @conf.dup
69
+ h[:level] = req.ancestors_level
70
+ @screenmanager.class_screen(c, h).response
71
+ end
72
+
73
+ def handle_method(req)
74
+ ms = @db.fetch_methods(req.method_spec)
75
+ raise MethodNotFound.new(req.method_spec.to_s) if ms.nil? || ms.empty?
76
+ @screenmanager.method_screen(ms, @conf).response
77
+ end
78
+
79
+ def library_index
80
+ @screenmanager.library_index_screen(@db.libraries.sort, @conf).response
81
+ end
82
+
83
+ def class_index
84
+ @screenmanager.class_index_screen(@db.classes.sort, @conf).response
85
+ end
86
+
87
+ def handle_opensearchdescription(req)
88
+ @screenmanager.opensearchdescription_screen(req.full_uri, @conf).response
89
+ end
90
+
91
+ def handle_search(req)
92
+ ret = []
93
+ q0 = req.query['q'] || ''
94
+ q = URI.unescape(q0)
95
+ start = Time.now.to_i
96
+ ret = SimpleSearcher.search_pattern(@db, q)
97
+ elapsed_time = Time.now.to_f - start.to_f
98
+ c = @conf.dup
99
+ c[:q] = q0
100
+ c[:elapsed_time] = elapsed_time
101
+ @screenmanager.search_screen(ret, c).response
102
+ end
103
+
104
+ def handle_doc(req)
105
+ d = @db.fetch_doc(req.doc_name || 'index' )
106
+ @screenmanager.doc_screen(d, @conf).response
107
+ end
108
+
109
+ def handle_function(req)
110
+ return function_index() unless req.function_name
111
+ f = @cdb.fetch_function(req.function_name)
112
+ @screenmanager.function_screen(f, @conf).response
113
+ end
114
+
115
+ def function_index
116
+ @screenmanager.function_index_screen(@cdb.functions.sort, @conf).response
117
+ end
118
+
119
+ end
120
+
121
+ class RackRequestHandler < RequestHandler
122
+ def handle(rack_req)
123
+ _handle(RackRequest.new(rack_req))
124
+ rescue BitClust::NotFoundError => err
125
+ return not_found_response(err)
126
+ rescue => err
127
+ return error_response(err)
128
+ end
129
+ end
130
+
131
+ class Request
132
+
133
+ include NameUtils
134
+
135
+ def initialize(wreq)
136
+ @wreq = wreq
137
+ end
138
+
139
+ def library?
140
+ type_id() == :library
141
+ end
142
+
143
+ def class?
144
+ type_id() == :class
145
+ end
146
+
147
+ def method?
148
+ type_id() == :method
149
+ end
150
+
151
+ def doc_name
152
+ name = path_info.sub(%r!\A/!, '')
153
+ name unless name.empty?
154
+ end
155
+
156
+ def library_name
157
+ raise '#library_name called but not library request' unless library?
158
+ id = type_param()
159
+ return nil unless id
160
+ name = libid2name(id)
161
+ unless libname?(name)
162
+ raise InvalidKey, "invalid library name: #{name.inspect}"
163
+ end
164
+ name
165
+ end
166
+
167
+ def class_name
168
+ raise '#class_name called but not class request' unless class?
169
+ id = type_param()
170
+ return nil unless id
171
+ name = classid2name(id)
172
+ unless classname?(name)
173
+ raise InvalidKey, "invalid class name: #{name.inspect}"
174
+ end
175
+ name
176
+ end
177
+
178
+ def method_spec
179
+ return nil unless method?
180
+ param = type_param()
181
+ return nil unless param
182
+ cid, typechar, mencoded = param.split('/', 3)
183
+ raise InvalidKey, 'missing class name' unless cid
184
+ raise InvalidKey, 'missing type name' unless typechar
185
+ raise InvalidKey, 'missing method name' unless mencoded
186
+ unless typechar?(typechar)
187
+ raise InvalidKey, "invalid method-type ID: #{typechar.inspect}"
188
+ end
189
+ cname = classid2name(cid)
190
+ tmark = typechar2mark(typechar)
191
+ mname = decodename_url(mencoded)
192
+ unless classname?(cname)
193
+ raise InvalidKey, "invalid class name: #{cname.inspect}"
194
+ end
195
+ case tmark
196
+ when '$'
197
+ unless gvarname?('$' + mname)
198
+ raise InvalidKey, "invalid variable name: #{('$' + mname).inspect}"
199
+ end
200
+ when '.', '#', '.#', '::'
201
+ unless methodname?(mname)
202
+ raise InvalidKey, "invalid method name: #{mname.inspect}"
203
+ end
204
+ end
205
+ MethodSpec.new(cname, tmark, mname)
206
+ end
207
+
208
+ def defined_type?
209
+ type, param = parse_path_info()
210
+ case type
211
+ when 'library', 'class', 'method', 'function', 'search', 'opensearchdescription'
212
+ true
213
+ else
214
+ false
215
+ end
216
+ end
217
+
218
+ def type_id
219
+ type, param = parse_path_info()
220
+ type.intern if type
221
+ end
222
+
223
+ def function?
224
+ type_id() == :function
225
+ end
226
+
227
+ def function_name
228
+ raise '#function_name called but not function request' unless function?
229
+ id = type_param()
230
+ return nil unless id
231
+ name = id
232
+ unless functionname?(name)
233
+ raise InvalidKey, "invalid function name: #{name.inspect}"
234
+ end
235
+ name
236
+ end
237
+
238
+ def ancestors_level
239
+ ret = query['a'].to_i
240
+ if ret < 0
241
+ 0
242
+ else
243
+ ret
244
+ end
245
+ end
246
+
247
+ def query
248
+ @wreq.query
249
+ end
250
+
251
+ def full_uri
252
+ @wreq.request_uri
253
+ end
254
+
255
+ private
256
+
257
+ def type_param
258
+ type, param = parse_path_info()
259
+ return nil unless param
260
+ return nil if param.empty?
261
+ param
262
+ end
263
+
264
+ def parse_path_info
265
+ return nil unless path_info
266
+ _, type, param = path_info.split('/', 3)
267
+ param = nil if not param or param.empty?
268
+ return type, param
269
+ end
270
+
271
+ def path_info
272
+ @wreq.path_info
273
+ end
274
+
275
+ end
276
+
277
+ class RackRequest < Request
278
+
279
+ def initialize(rack_req)
280
+ @rack_req = rack_req
281
+ end
282
+
283
+ def query
284
+ @rack_req.params
285
+ end
286
+
287
+ def path_info
288
+ @rack_req.env["PATH_INFO"]
289
+ end
290
+
291
+ def full_uri
292
+ URI.parse(@rack_req.url)
293
+ end
294
+ end
295
+
296
+ class Screen # reopen
297
+ def response
298
+ Response.new(self)
299
+ end
300
+ end
301
+
302
+
303
+ class Response
304
+
305
+ def initialize(screen)
306
+ @screen = screen
307
+ end
308
+
309
+ def update(webrick_res)
310
+ webrick_res.status = @screen.status if @screen.status
311
+ webrick_res['Content-Type'] = @screen.content_type
312
+ # webrick_res['Last-Modified'] = @screen.last_modified
313
+ body = @screen.body
314
+ webrick_res['Content-Length'] = body.bytesize
315
+ webrick_res.body = body
316
+ end
317
+
318
+ def rack_finish
319
+ [
320
+ @screen.status || 200,
321
+ {
322
+ 'Content-Type' => @screen.content_type,
323
+ },
324
+ @screen.body
325
+ ]
326
+ end
327
+
328
+ end
329
+
330
+ end
@@ -0,0 +1,349 @@
1
+ require 'rdoc/ri/ri_reader'
2
+ require 'rdoc/ri/ri_cache'
3
+ require 'rdoc/ri/ri_paths'
4
+
5
+ class ApplicationError < StandardError; end
6
+ class RiClassNotFound < ApplicationError; end
7
+
8
+ class RiDatabase
9
+ def RiDatabase.open_system_db
10
+ new(RI::Paths.path(true, false, false, false), RUBY_VERSION)
11
+ end
12
+
13
+ def RiDatabase.open(dir, version)
14
+ new(RI::Paths.path(false, false, false, false, dir), version)
15
+ end
16
+
17
+ def initialize(ripath, version)
18
+ @ripath = ripath
19
+ @reader = RI::RiReader.new(RI::RiCache.new(@ripath))
20
+ @version = version
21
+ end
22
+
23
+ attr_reader :version
24
+
25
+ def get_method(m)
26
+ @reader.get_method(m)
27
+ end
28
+
29
+ def current_class=(name)
30
+ @klass = lookup_class(name)
31
+ @singleton_methods = wrap_entries(@reader.singleton_methods(@klass))
32
+ @instance_methods = wrap_entries(@reader.instance_methods(@klass))
33
+ rescue RiClassNotFound
34
+ @klass = nil
35
+ @singleton_methods = []
36
+ @instance_methods = []
37
+ end
38
+
39
+ attr_reader :class
40
+ attr_reader :singleton_methods
41
+ attr_reader :instance_methods
42
+
43
+ def lookup_class(name)
44
+ ns = @reader.top_level_namespace.first
45
+ name.split('::').each do |const|
46
+ ns = ns.contained_class_named(const) or
47
+ raise RiClassNotFound, "no such class in RDoc database: #{name}"
48
+ end
49
+ ns
50
+ end
51
+
52
+ private
53
+
54
+ def wrap_entries(ents)
55
+ ents.map {|m|
56
+ [RiMethodEntry.new(m.name, m)] +
57
+ m.aliases.map {|a| RiMethodEntry.new(a.name, m) }
58
+ }.flatten.uniq
59
+ end
60
+ end
61
+
62
+ class Ent
63
+ def initialize(name, ent)
64
+ @name = name
65
+ @entry = ent
66
+ end
67
+
68
+ attr_reader :name
69
+ attr_reader :entry
70
+
71
+ def ==(other)
72
+ @name == other.name
73
+ end
74
+
75
+ alias eql? ==
76
+
77
+ def hash
78
+ @name.hash
79
+ end
80
+
81
+ def <=>(other)
82
+ @name <=> other.name
83
+ end
84
+ end
85
+
86
+ class BCMethodEntry < Ent
87
+ def bitclust?
88
+ true
89
+ end
90
+
91
+ def inspect
92
+ "\#<BitClust #{@name} #{@entry.inspect}>"
93
+ end
94
+
95
+ def fullname
96
+ "#{@entry.klass.name}#{@entry.typemark}#{@name}"
97
+ end
98
+
99
+ def id
100
+ if @entry.defined?
101
+ fullname()
102
+ else
103
+ "#{fullname()}.#{@entry.library.name}"
104
+ end
105
+ end
106
+ end
107
+
108
+ class RiMethodEntry < Ent
109
+ def bitclust?
110
+ false
111
+ end
112
+
113
+ def inspect
114
+ "\#<RDoc #{@name} #{@entry.fullname}>"
115
+ end
116
+
117
+ def singleton_method?
118
+ @entry.singleton_method?
119
+ end
120
+
121
+ def fullname
122
+ c, t, m = @entry.fullname.split(/([\.\#])/, 2)
123
+ "#{c}#{t}#{@name}"
124
+ end
125
+ end
126
+
127
+
128
+ module RI
129
+
130
+ class RiReader # reopen
131
+ def singleton_methods(c)
132
+ c.singleton_methods.map {|ent| get_method(ent) }
133
+ end
134
+
135
+ def instance_methods(c)
136
+ c.instance_methods.map {|ent| get_method(ent) }
137
+ end
138
+ end
139
+
140
+ class ClassEntry # reopen
141
+ def singleton_methods
142
+ @class_methods
143
+ end
144
+
145
+ attr_reader :instance_methods
146
+
147
+ def method_entries
148
+ @class_methods.sort_by {|m| m.name } +
149
+ @instance_methods.sort_by {|m| m.name }
150
+ end
151
+ end
152
+
153
+ class MethodEntry # reopen
154
+ def fullname
155
+ "#{@in_class.full_name}#{@is_class_method ? '.' : '#'}#{@name}"
156
+ end
157
+
158
+ def singleton_method?
159
+ @is_class_method
160
+ end
161
+ end
162
+
163
+ class MethodDescription # reopen
164
+ def fullname
165
+ name = full_name()
166
+ unless /\#/ =~ name
167
+ components = name.split('::')
168
+ m = components.pop
169
+ components.join('::') + '.' + m
170
+ else
171
+ name
172
+ end
173
+ end
174
+
175
+ def singleton_method?
176
+ @is_class_method ||= false
177
+ @is_class_method
178
+ end
179
+ end
180
+
181
+ end
182
+
183
+
184
+ module HTMLUtils
185
+
186
+ ESC = {
187
+ '&' => '&amp;',
188
+ '<' => '&lt;',
189
+ '>' => '&gt;',
190
+ '"' => '&quot;'
191
+ }
192
+
193
+ def escape(str)
194
+ t = ESC
195
+ str.gsub(/[&"<>]/) {|s| t[s] }
196
+ end
197
+ module_function :escape
198
+
199
+ UNESC = ESC.invert
200
+ UNESC['&nbsp;'] = ' '
201
+
202
+ def unescape(str)
203
+ t = UNESC
204
+ str.gsub(/&\w+;/) {|s| t[s] || s }
205
+ end
206
+ module_function :unescape
207
+
208
+ end
209
+
210
+
211
+ class Formatter
212
+
213
+ include HTMLUtils
214
+
215
+ def method_info(m)
216
+ @f = StringIO.new
217
+ describe_method m
218
+ @f.string
219
+ end
220
+
221
+ private
222
+
223
+ def line(s = nil)
224
+ if s
225
+ @f.puts s
226
+ else
227
+ @f.puts
228
+ end
229
+ end
230
+
231
+ def describe_method(m)
232
+ if m.params[0,1] == '('
233
+ line "--- #{m.full_name}#{trim_space(m.params)}"
234
+ else
235
+ m.params.lines.each do |sig|
236
+ line "--- #{trim_sig(sig)}\#@todo\n"
237
+ end
238
+ end
239
+ unless m.aliases.empty?
240
+ m.aliases.each do |a|
241
+ line "alias #{a.name}"
242
+ end
243
+ end
244
+ #line "\#@TODO rewrite me"
245
+ line
246
+ @f.puts format_elements(m.comment)
247
+ line
248
+ end
249
+
250
+ def trim_space(s)
251
+ s.sub(/\(\s+/, '(').sub(/\s+\)/, ')').sub(/\(\)/, '')
252
+ end
253
+
254
+ def trim_sig(s)
255
+ s = trim_space(s)
256
+ s.sub!(/\A[a-z]\w+\./, '')
257
+ s.sub!(/=>/, '->')
258
+ s.sub!(/(->.*)\bstr(ing)?\b/){ $1 + 'String' }
259
+ s.sub!(/(->.*)\bint(eger)?\b/){ $1 + 'Integer' }
260
+ s.sub!(/(->.*)\b(an_)?obj\b/){ $1 + 'object' }
261
+ s.sub!(/(->.*)\b(a_)?hash\b/){ $1 + 'Hash' }
262
+ s.sub!(/(->.*)\b(an_)?array\b/){ $1 + 'Array' }
263
+ s.sub!(/ or /, ' | ')
264
+ s
265
+ end
266
+
267
+ def format_elements(elems)
268
+ return "" unless elems
269
+ return "" if elems.empty?
270
+ elems.map {|elem| format_element(elem) }.join("\n\n")
271
+ end
272
+
273
+ def format_element(e)
274
+ case e
275
+ when SM::Flow::P, SM::Flow::LI
276
+ paragraph(e)
277
+ when SM::Flow::LIST
278
+ list(e)
279
+ when SM::Flow::VERB
280
+ verbatim(e)
281
+ when SM::Flow::H
282
+ headline(e)
283
+ when SM::Flow::RULE
284
+ ;
285
+ else
286
+ raise "unkwnown markup: #{e.class}"
287
+ end
288
+ end
289
+
290
+ def headline(e)
291
+ h = '==' + ('=' * e.level)
292
+ text = unescape(e.text)
293
+ "#{h} #{text}"
294
+ end
295
+
296
+ def paragraph(e)
297
+ wrap(unescape(remove_inline(e.body)))
298
+ end
299
+
300
+ def verbatim(e)
301
+ unescape(e.body.rstrip) #.gsub(/^/, ' ')
302
+ end
303
+
304
+ def list(e)
305
+ case e.type
306
+ when SM::ListBase::BULLET
307
+ e.contents.map {|item| "* #{format_element(e)}" }.join("\n")
308
+ when SM::ListBase::NUMBER,
309
+ SM::ListBase::LOWERALPHA,
310
+ SM::ListBase::UPPERALPHA
311
+ num = case e.type
312
+ when SM::ListBase::NUMBER then '1'
313
+ when SM::ListBase::LOWERALPHA then 'a'
314
+ when SM::ListBase::UPPERALPHA then 'A'
315
+ end
316
+ e.contents.map {|item|
317
+ str = "#{num}. #{format_element(e)}"
318
+ num = num.succ
319
+ str
320
+ }.join("\n")
321
+ when SM::ListBase::LABELED
322
+ e.contents.map {|item| "#{item.label} #{format_element(e)}" }.join("\n")
323
+ when SM::ListBase::NOTE
324
+ e.contents.map {|item| "#{item.label}\t#{format_element(e)}" }.join("\n")
325
+ else
326
+ raise "unknown list type: #{e.type.inspect}"
327
+ end
328
+ end
329
+
330
+ def remove_inline(str)
331
+ str.gsub(/<\/?\w+>/, '')
332
+ end
333
+
334
+ def wrap(str)
335
+ width = 60
336
+ buf = ''
337
+ line = ''
338
+ str.split.each do |chunk|
339
+ line << chunk << ' '
340
+ if line.size > width
341
+ buf << line.strip << "\n"
342
+ line = ''
343
+ end
344
+ end
345
+ buf << line
346
+ buf.strip
347
+ end
348
+
349
+ end