bitclust-core 0.8.0 → 0.9.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +16 -0
  3. data/Gemfile +1 -7
  4. data/data/bitclust/template/class +14 -0
  5. data/data/bitclust/template/doc +1 -1
  6. data/data/bitclust/template/layout +1 -1
  7. data/data/bitclust/template.epub/class +101 -0
  8. data/data/bitclust/template.epub/class-index +28 -0
  9. data/data/bitclust/template.epub/container.xml +6 -0
  10. data/data/bitclust/template.epub/contents +23 -0
  11. data/data/bitclust/template.epub/doc +13 -0
  12. data/data/bitclust/template.epub/function +22 -0
  13. data/data/bitclust/template.epub/function-index +24 -0
  14. data/data/bitclust/template.epub/layout +19 -0
  15. data/data/bitclust/template.epub/library +75 -0
  16. data/data/bitclust/template.epub/library-index +47 -0
  17. data/data/bitclust/template.epub/method +21 -0
  18. data/data/bitclust/template.epub/mimetype +1 -0
  19. data/data/bitclust/template.epub/nav.xhtml +6 -0
  20. data/data/bitclust/template.epub/rd_file +6 -0
  21. data/data/bitclust/template.lillia/class +17 -0
  22. data/data/bitclust/template.lillia/doc +1 -1
  23. data/data/bitclust/template.lillia/layout +1 -1
  24. data/data/bitclust/template.offline/class +14 -0
  25. data/data/bitclust/template.offline/doc +1 -1
  26. data/data/bitclust/template.offline/layout +1 -1
  27. data/lib/bitclust/classentry.rb +31 -11
  28. data/lib/bitclust/docentry.rb +2 -2
  29. data/lib/bitclust/entry.rb +1 -0
  30. data/lib/bitclust/exception.rb +1 -0
  31. data/lib/bitclust/functionreferenceparser.rb +4 -3
  32. data/lib/bitclust/generators/epub.rb +118 -0
  33. data/lib/bitclust/libraryentry.rb +4 -6
  34. data/lib/bitclust/methodsignature.rb +1 -1
  35. data/lib/bitclust/nameutils.rb +12 -1
  36. data/lib/bitclust/rdcompiler.rb +101 -57
  37. data/lib/bitclust/rrdparser.rb +20 -6
  38. data/lib/bitclust/runner.rb +2 -0
  39. data/lib/bitclust/screen.rb +10 -2
  40. data/lib/bitclust/searcher.rb +4 -0
  41. data/lib/bitclust/subcommands/epub_command.rb +68 -0
  42. data/lib/bitclust/subcommands/methods_command.rb +13 -0
  43. data/lib/bitclust/subcommands/setup_command.rb +2 -2
  44. data/lib/bitclust/subcommands/statichtml_command.rb +54 -32
  45. data/lib/bitclust/version.rb +1 -1
  46. data/test/run_test.rb +0 -0
  47. data/test/test_functiondatabase.rb +3 -3
  48. data/test/test_functionreferenceparser.rb +51 -0
  49. data/test/test_methoddatabase.rb +33 -0
  50. data/test/test_nameutils.rb +13 -0
  51. data/test/test_rdcompiler.rb +176 -13
  52. data/test/test_refsdatabase.rb +8 -0
  53. metadata +41 -23
@@ -64,7 +64,7 @@ module BitClust
64
64
  def realname
65
65
  alias? ? aliasof.name : name
66
66
  end
67
-
67
+
68
68
  def name_match?(re)
69
69
  re =~ name()
70
70
  end
@@ -81,6 +81,8 @@ module BitClust
81
81
  property :superclass, 'ClassEntry'
82
82
  property :included, '[ClassEntry]'
83
83
  property :extended, '[ClassEntry]'
84
+ property :dynamically_included, '[ClassEntry]'
85
+ property :dynamically_extended, '[ClassEntry]'
84
86
  property :library, 'LibraryEntry'
85
87
  property :aliases, '[ClassEntry]'
86
88
  property :aliasof, 'ClassEntry'
@@ -133,7 +135,7 @@ module BitClust
133
135
  ancestors.any?{|k| k.name == 'Exception' }
134
136
  end
135
137
  end
136
-
138
+
137
139
  def include(m)
138
140
  included().push m
139
141
  end
@@ -142,6 +144,24 @@ module BitClust
142
144
  extended().push m
143
145
  end
144
146
 
147
+ # Add a module +m+ to the dynamically included module list.
148
+ def dynamic_include(m, lib)
149
+ if m.library != lib
150
+ message = "dynamically included module #{m.name} should be defined in the module #{lib.name}"
151
+ raise InvalidLibrary, message
152
+ end
153
+ dynamically_included().push m
154
+ end
155
+
156
+ # Add a module +m+ to the dynamically extended module list.
157
+ def dynamic_extend(m, lib)
158
+ if m.library != lib
159
+ message = "dynamically extended module #{m.name} should be defined in the module #{lib.name}"
160
+ raise InvalidLibrary, message
161
+ end
162
+ dynamically_extended().push m
163
+ end
164
+
145
165
  # Add a alias +c+ to the alias list.
146
166
  def alias(c)
147
167
  aliases().push c
@@ -208,7 +228,7 @@ module BitClust
208
228
  .map {|ent| MethodEntry.new(@db, "#{@id}/#{ent}") }
209
229
  ret = @entries
210
230
  ancestors[1..level].each{|c| ret += c.entries }
211
- ret
231
+ ret
212
232
  end
213
233
 
214
234
  alias methods entries
@@ -260,42 +280,42 @@ module BitClust
260
280
 
261
281
  def singleton_methods(level = 0)
262
282
  # FIXME: inheritance
263
- entries(level).select {|m| m.singleton_method? }.sort
283
+ entries(level).select {|m| m.singleton_method? }.sort_by {|entry| entry.sort_key }
264
284
  end
265
285
 
266
286
  def public_singleton_methods(level = 0)
267
287
  # FIXME: inheritance
268
- entries(level).select {|m| m.public_singleton_method? }.sort
288
+ entries(level).select {|m| m.public_singleton_method? }.sort_by {|entry| entry.sort_key }
269
289
  end
270
290
 
271
291
  def instance_methods(level = 0)
272
292
  # FIXME: inheritance
273
- entries(level).select {|m| m.instance_method? }.sort
293
+ entries(level).select {|m| m.instance_method? }.sort_by {|entry| entry.sort_key }
274
294
  end
275
295
 
276
296
  def private_singleton_methods(level = 0)
277
297
  # FIXME: inheritance
278
- entries(level).select {|m| m.private_singleton_method? }.sort
298
+ entries(level).select {|m| m.private_singleton_method? }.sort_by {|entry| entry.sort_key }
279
299
  end
280
300
 
281
301
  def public_instance_methods(level = 0)
282
302
  # FIXME: inheritance
283
- entries(level).select {|m| m.public_instance_method? }.sort
303
+ entries(level).select {|m| m.public_instance_method? }.sort_by {|entry| entry.sort_key }
284
304
  end
285
305
 
286
306
  def private_instance_methods(level = 0)
287
307
  # FIXME: inheritance
288
- entries(level).select {|m| m.private_instance_method? }.sort
308
+ entries(level).select {|m| m.private_instance_method? }.sort_by {|entry| entry.sort_key }
289
309
  end
290
310
 
291
311
  alias private_methods private_instance_methods
292
312
 
293
313
  def constants(level = 0)
294
- entries(level).select {|m| m.constant? }.sort
314
+ entries(level).select {|m| m.constant? }.sort_by {|entry| entry.sort_key }
295
315
  end
296
316
 
297
317
  def special_variables
298
- entries().select {|m| m.special_variable? }.sort
318
+ entries().select {|m| m.special_variable? }.sort_by {|entry| entry.sort_key }
299
319
  end
300
320
 
301
321
  def singleton_method?(name, inherit = true)
@@ -50,7 +50,7 @@ module BitClust
50
50
  def labels
51
51
  [label()]
52
52
  end
53
-
53
+
54
54
  def name?(n)
55
55
  name() == n
56
56
  end
@@ -71,7 +71,7 @@ module BitClust
71
71
  def error_classes
72
72
  classes.select{|c| c.error_class? }
73
73
  end
74
-
74
+
75
75
  def methods
76
76
  @db.methods
77
77
  end
@@ -205,6 +205,7 @@ module BitClust
205
205
  end
206
206
 
207
207
  def restore_entries(str, klass)
208
+ return [] if str.nil?
208
209
  str.split(',').map {|id| klass.load(@db, id) }
209
210
  end
210
211
 
@@ -17,6 +17,7 @@ module BitClust
17
17
  class WrongInclude < DocumentError; end
18
18
  class InvalidLink < DocumentError; end
19
19
  class InvalidAncestor < DocumentError; end
20
+ class InvalidLibrary < DocumentError; end
20
21
  class UserError < Error; end
21
22
  class InvalidDatabase < UserError; end
22
23
  class InvalidKey < UserError; end
@@ -28,13 +28,14 @@ module BitClust
28
28
 
29
29
  def parse_file(path, filename, properties)
30
30
  fopen(path, 'r:UTF-8') {|f|
31
- return parse(f, filename)
31
+ return parse(f, filename, properties)
32
32
  }
33
33
  end
34
34
 
35
- def parse(f, filename)
35
+ def parse(f, filename, params = {})
36
36
  @filename = filename
37
- file_entries LineInput.new(f)
37
+ s = Preprocessor.read(f, params)
38
+ file_entries LineInput.for_string(s)
38
39
  @db.functions
39
40
  end
40
41
 
@@ -0,0 +1,118 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+ require 'erb'
4
+
5
+ require 'bitclust/subcommands/statichtml_command'
6
+
7
+ module BitClust
8
+ module Generators
9
+ class EPUB
10
+ def initialize(options = {})
11
+ @options = options.dup
12
+ @prefix = options[:prefix]
13
+ @capi = options[:capi]
14
+ @outputdir = options[:outputdir]
15
+ @filename = options[:filename]
16
+ @templatedir = options[:templatedir]
17
+ @catalog = options[:catalog]
18
+ @themedir = options[:themedir]
19
+ @fs_casesensitive = options[:fs_casesensitive]
20
+ @keep = options[:keep]
21
+ @verbose = options[:verbose]
22
+ end
23
+
24
+ CONTENTS_DIR_NAME = 'OEBPS'
25
+
26
+ def generate
27
+ make_epub_directory do |epub_directory|
28
+ contents_directory = epub_directory + CONTENTS_DIR_NAME
29
+ copy_static_files(epub_directory)
30
+ generate_xhtml_files(contents_directory)
31
+ generate_contents_opf(epub_directory)
32
+ pack_epub(epub_directory)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def make_epub_directory
39
+ dir = Dir.mktmpdir("epub-", @outputdir)
40
+ yield Pathname.new(dir)
41
+ ensure
42
+ FileUtils.rm_rf(dir, :secure => true, :verbose => @verbose) unless @keep
43
+ end
44
+
45
+ def copy_static_files(epub_directory)
46
+ FileUtils.cp(@templatedir + "mimetype", epub_directory, :verbose => @verbose)
47
+ FileUtils.cp(@templatedir + "nav.xhtml", epub_directory, :verbose => @verbose)
48
+ meta_inf_directory = epub_directory + "META-INF"
49
+ FileUtils.mkdir_p(meta_inf_directory, :verbose => @verbose)
50
+ FileUtils.cp(@templatedir + "container.xml", meta_inf_directory, :verbose => @verbose)
51
+ end
52
+
53
+ def generate_xhtml_files(contents_directory)
54
+ argv = [
55
+ "--outputdir=#{contents_directory}",
56
+ "--templatedir=#{@templatedir}",
57
+ "--catalog=#{@catalog}",
58
+ "--themedir=#{@themedir}",
59
+ "--suffix=.xhtml",
60
+ ]
61
+ argv << "--fs-casesensitive" if @fs_casesensitive
62
+ argv << "--quiet" unless @verbose
63
+ options = {
64
+ :prefix => @prefix,
65
+ :capi => @capi,
66
+ }
67
+ cmd = BitClust::Subcommands::StatichtmlCommand.new
68
+ cmd.parse(argv)
69
+ cmd.exec(argv, options)
70
+ end
71
+
72
+ def generate_contents_opf(epub_directory)
73
+ items = []
74
+ glob_relative_path(epub_directory, "#{CONTENTS_DIR_NAME}/class/*.xhtml").each do |path|
75
+ items << {
76
+ :id => decodename_package(path.basename(".*").to_s),
77
+ :path => path
78
+ }
79
+ end
80
+ items.sort_by!{|item| item[:path] }
81
+ contents = ERB.new(File.read(@templatedir + "contents"), nil, "-").result(binding)
82
+ File.open(epub_directory + "contents.opf", "w") do |f|
83
+ f.write contents
84
+ end
85
+ end
86
+
87
+ def pack_epub(epub_directory)
88
+ epub_filename = @outputdir + @filename
89
+ Dir.chdir(epub_directory.to_s) do
90
+ system("zip -0 -X #{epub_filename} mimetype")
91
+ system("zip -r #{epub_filename} ./* -x mimetype")
92
+ end
93
+ end
94
+
95
+ def glob_relative_path(path, pattern)
96
+ relative_paths = []
97
+ absolute_path_to_search = Pathname.new(path).realpath
98
+ Dir.glob(absolute_path_to_search + pattern) do |absolute_path|
99
+ absolute_path = Pathname.new(absolute_path)
100
+ relative_paths << absolute_path.relative_path_from(absolute_path_to_search)
101
+ end
102
+ relative_paths
103
+ end
104
+
105
+ def decodename_package(str)
106
+ if @fs_casesensitive
107
+ NameUtils.decodename_url(str)
108
+ else
109
+ NameUtils.decodename_fs(str)
110
+ end
111
+ end
112
+
113
+ def last_modified
114
+ Time.now.iso8601
115
+ end
116
+ end
117
+ end
118
+ end
@@ -24,6 +24,7 @@ module BitClust
24
24
  def initialize(db, id)
25
25
  super db
26
26
  @id = id
27
+ @name = libid2name(@id)
27
28
  if saved?
28
29
  @classmap = nil
29
30
  @methodmap = nil
@@ -36,7 +37,9 @@ module BitClust
36
37
  init_properties
37
38
  end
38
39
 
39
- attr_reader :id
40
+ attr_reader :id, :name
41
+
42
+ alias label name
40
43
 
41
44
  def ==(other)
42
45
  @id == other.id
@@ -52,11 +55,6 @@ module BitClust
52
55
  @id.casecmp(other.id)
53
56
  end
54
57
 
55
- def name
56
- libid2name(@id)
57
- end
58
- alias label name
59
-
60
58
  def labels
61
59
  [label()]
62
60
  end
@@ -59,7 +59,7 @@ module BitClust
59
59
  when /\A\$/ # gvar
60
60
  @name + (@type ? " -> #{@type}" : "")
61
61
  when "+@", "-@", "~", "!", "!@" # unary operator
62
- "#{@name.sub(/@/, '')}#{@params}" + (@type ? " -> #{@type}" : "")
62
+ "#{@name.sub(/@/, '')} self" + (@type ? " -> #{@type}" : "")
63
63
  when "[]" # aref
64
64
  "self[#{@params}]" + (@type ? " -> #{@type}" : "")
65
65
  when "[]=" # aset
@@ -21,7 +21,7 @@ module BitClust
21
21
  CONST_PATH_RE = /#{CONST_RE}(?:::#{CONST_RE})*/
22
22
  CLASS_NAME_RE = /(?:#{CONST_RE}(?:::compatible)?|fatal|ARGF.class|main)/
23
23
  CLASS_PATH_RE = /(?:#{CONST_PATH_RE}(?:::compatible)?|fatal|ARGF.class|main)/
24
- METHOD_NAME_RE = /\w+[?!=]?|===|==|=~|<=>|<=|>=|!=|!|!@|!~|\[\]=|\[\]|\*\*|>>|<<|\+@|\-@|[~+\-*\/%&|^<>`]/
24
+ METHOD_NAME_RE = /\w+[?!=]?|===|==|=~|<=>|<=|>=|!=|!~|!@|!|\[\]=|\[\]|\*\*|>>|<<|\+@|\-@|[~+\-*\/%&|^<>`]/
25
25
  TYPEMARK_RE = /(?:\.|\#|\.\#|::|\$)/
26
26
  METHOD_SPEC_RE = /#{CLASS_PATH_RE}#{TYPEMARK_RE}#{METHOD_NAME_RE}/
27
27
  GVAR_RE = /\$(?:\w+|-.|\S)/
@@ -212,6 +212,14 @@ module BitClust
212
212
  str.gsub(/=[\da-h]{2}/ni) {|s| s[1,2].hex.chr }
213
213
  end
214
214
 
215
+ # string -> encoded string in a rdoc way
216
+ def encodename_rdocurl(str)
217
+ str = str.gsub(/[^A-Za-z0-9_.]/n) {|ch|
218
+ sprintf('-%02X', ch[0].ord)
219
+ }
220
+ str.sub(/\A-/, '')
221
+ end
222
+
215
223
  # case-sensitive ID -> encoded string (encode only [A-Z])
216
224
  def encodeid(str)
217
225
  str.gsub(/[A-Z]/n) {|ch| "-#{ch}" }.downcase
@@ -234,6 +242,9 @@ module BitClust
234
242
  }
235
243
  end
236
244
 
245
+ def html_filename(basename, suffix)
246
+ "#{basename}#{suffix}"
247
+ end
237
248
  end
238
249
 
239
250
  end
@@ -38,13 +38,23 @@ module BitClust
38
38
  }
39
39
  end
40
40
 
41
+ def compile_function(f, opt = nil)
42
+ @opt = opt
43
+ @type = :function
44
+ setup(f.source) {
45
+ entry
46
+ }
47
+ ensure
48
+ @opt = nil
49
+ end
50
+
41
51
  # FIXME
42
52
  def compile_method(m, opt = nil)
43
53
  @opt = opt
44
54
  @type = :method
45
55
  @method = m
46
56
  setup(m.source) {
47
- method_entry
57
+ entry
48
58
  }
49
59
  ensure
50
60
  @opt = nil
@@ -63,13 +73,13 @@ module BitClust
63
73
  while @f.next?
64
74
  case @f.peek
65
75
  when /\A---/
66
- method_entry_chunk
76
+ entry_chunk
67
77
  when /\A=+/
68
78
  headline @f.gets
69
- when /\A\s+\*\s/
70
- ulist
71
- when /\A\s+\(\d+\)\s/
72
- olist
79
+ when /\A(\s+)\*\s/, /\A(\s+)\(\d+\)\s/
80
+ @item_stack = []
81
+ item_list($1.size)
82
+ raise "@item_stack should be empty. #{@item_stack.inspect}" unless @item_stack.empty?
73
83
  when %r<\A//emlist\{>
74
84
  emlist
75
85
  when /\A:\s/
@@ -86,13 +96,13 @@ module BitClust
86
96
  end
87
97
  end
88
98
 
89
- def method_entry
99
+ def entry
90
100
  while @f.next?
91
- method_entry_chunk
101
+ entry_chunk
92
102
  end
93
103
  end
94
104
 
95
- def method_entry_chunk
105
+ def entry_chunk
96
106
  @out.puts '<dl>' if @option[:force]
97
107
  first = true
98
108
  @f.while_match(/\A---/) do |line|
@@ -103,8 +113,8 @@ module BitClust
103
113
  @f.while_match(/\A:/) do |line|
104
114
  k, v = line.sub(/\A:/, '').split(':', 2)
105
115
  props[k.strip] = v.strip
106
- end
107
- @out.puts '<dd class="method-description">'
116
+ end if @type == :method
117
+ @out.puts %Q(<dd class="#{@type.to_s}-description">)
108
118
  while @f.next?
109
119
  case @f.peek
110
120
  when /\A===+/
@@ -113,14 +123,14 @@ module BitClust
113
123
  if @option[:force]
114
124
  break
115
125
  else
116
- raise "method entry includes headline: #{@f.peek.inspect}"
126
+ raise "#{@type.to_s} entry includes headline: #{@f.peek.inspect}"
117
127
  end
118
128
  when /\A---/
119
129
  break
120
- when /\A\s+\*\s/
121
- ulist
122
- when /\A\s+\(\d+\)\s/
123
- olist
130
+ when /\A(\s+)\*\s/, /\A(\s+)\(\d+\)\s/
131
+ @item_stack = []
132
+ item_list($1.size)
133
+ raise "@item_stack should be empty. #{@item_stack.inspect}" unless @item_stack.empty?
124
134
  when /\A:\s/
125
135
  dlist
126
136
  when %r<\A//emlist\{>
@@ -132,12 +142,12 @@ module BitClust
132
142
  when /@todo/
133
143
  todo
134
144
  when /\A@[a-z]/
135
- method_info
145
+ entry_info
136
146
  else
137
147
  if @f.peek.strip.empty?
138
148
  @f.gets
139
149
  else
140
- method_entry_paragraph
150
+ entry_paragraph
141
151
  end
142
152
  end
143
153
  end
@@ -157,32 +167,45 @@ module BitClust
157
167
  "<h#{level} #{name}>#{label}</h#{level}>"
158
168
  end
159
169
 
160
- def ulist
161
- @out.puts '<ul>'
162
- @f.while_match(/\A\s+\*\s/) do |line|
163
- string '<li>'
164
- string compile_text(line.sub(/\A\s+\*/, '').strip)
165
- @f.while_match(/\A\s+[^\*\s]/) do |cont|
166
- nl
167
- string compile_text(cont.strip)
168
- end
169
- line '</li>'
170
+ def item_list(level = 0, indent = true)
171
+ open_tag = nil
172
+ close_tag = nil
173
+ pattern = nil
174
+ case @f.peek
175
+ when /\A(\s+)\*\s/
176
+ open_tag = "<ul>"
177
+ close_tag = "</ul>"
178
+ when /\A(\s+)\(\d+\)\s/
179
+ open_tag = "<ol>"
180
+ close_tag = "</ol>"
170
181
  end
171
- line '</ul>'
172
- end
173
-
174
- def olist
175
- @out.puts '<ol>'
176
- @f.while_match(/\A\s+\(\d+\)/) do |line|
177
- string '<li>'
178
- string compile_text(line.sub(/\A\s+\(\d+\)/, '').strip)
179
- @f.while_match(/\A\s+(?!\(\d+\))\S/) do |cont|
180
- string "\n"
181
- string compile_text(cont.strip)
182
+ if indent
183
+ line open_tag
184
+ @item_stack.push(close_tag)
185
+ end
186
+ @f.while_match(/\A(\s+)(?:\*\s|\(\d+\))/) do |line|
187
+ string "<li>"
188
+ @item_stack.push("</li>")
189
+ string compile_text(line.sub(/\A(\s+)(?:\*|\(\d+\))/, '').strip)
190
+ if /\A(\s+)(?!\*\s|\(\d+\))\S/ =~ @f.peek
191
+ @f.while_match(/\A\s+(?!\*\s|\(\d+\))\S/) do |cont|
192
+ nl
193
+ string compile_text(cont.strip)
194
+ end
195
+ line @item_stack.pop # current level li
196
+ elsif /\A(\s+)(?:\*\s|\(\d+\))/ =~ @f.peek and level < $1.size
197
+ item_list($1.size)
198
+ line @item_stack.pop # current level ul or ol
199
+ elsif /\A(\s+)(?:\*\s|\(\d+\))/ =~ @f.peek and level > $1.size
200
+ line @item_stack.pop # current level li
201
+ line @item_stack.pop # current level ul or ol
202
+ line @item_stack.pop # previous level li
203
+ item_list($1.size, false)
204
+ else
205
+ line @item_stack.pop # current level li
182
206
  end
183
- line '</li>'
184
207
  end
185
- line '</ol>'
208
+ line @item_stack.pop unless @item_stack.empty?
186
209
  end
187
210
 
188
211
  def dlist
@@ -293,7 +316,7 @@ module BitClust
293
316
  line '</p>'
294
317
  end
295
318
 
296
- def method_info
319
+ def entry_info
297
320
  line '<dl>'
298
321
  while @f.next? and /\A\@(?!see)\w+|\A$/ =~ @f.peek
299
322
  header = @f.gets
@@ -303,7 +326,7 @@ module BitClust
303
326
  case cmd
304
327
  when '@param', '@arg'
305
328
  name = header.slice!(/\A\s*\w+/) || '?'
306
- line "<dt class='method-param'>[PARAM] #{escape_html(name.strip)}:</dt>"
329
+ line "<dt class='#{@type.to_s}-param'>[PARAM] #{escape_html(name.strip)}:</dt>"
307
330
  when '@raise'
308
331
  ex = header.slice!(/\A\s*[\w:]+/) || '?'
309
332
  line "<dt>[EXCEPTION] #{escape_html(ex.strip)}:</dt>"
@@ -318,15 +341,15 @@ module BitClust
318
341
  end
319
342
 
320
343
  # FIXME: parse @param, @return, ...
321
- def method_entry_paragraph
344
+ def entry_paragraph
322
345
  line '<p>'
323
- read_method_entry_paragraph(@f).each do |line|
346
+ read_entry_paragraph(@f).each do |line|
324
347
  line compile_text(line.strip)
325
348
  end
326
349
  line '</p>'
327
350
  end
328
351
 
329
- def read_method_entry_paragraph(f)
352
+ def read_entry_paragraph(f)
330
353
  f.span(%r<\A(?!---|=|//emlist\{|@[a-z])\S>)
331
354
  end
332
355
 
@@ -342,6 +365,8 @@ module BitClust
342
365
  if first
343
366
  string '<span class="permalink">['
344
367
  string a_href(@urlmapper.method_url(methodid2specstring(@method.id)), "permalink")
368
+ string ']['
369
+ string rdoc_link(@method.id, @option[:database].properties["version"])
345
370
  string ']</span>'
346
371
  end
347
372
  if @method and not @method.defined?
@@ -370,7 +395,7 @@ module BitClust
370
395
  arg = _arg.rstrip
371
396
  case type
372
397
  when 'lib'
373
- then protect(link) {
398
+ protect(link) {
374
399
  case arg
375
400
  when '/', '_index'
376
401
  label = 'All libraries'
@@ -379,20 +404,26 @@ module BitClust
379
404
  end
380
405
  library_link(arg, label, frag)
381
406
  }
382
- when 'c' then protect(link) { class_link(arg, label, frag) }
383
- when 'm' then protect(link) { method_link(complete_spec(arg), label || arg, frag) }
407
+ when 'c'
408
+ protect(link) { class_link(arg, label, frag) }
409
+ when 'm'
410
+ protect(link) { method_link(complete_spec(arg), label || arg, frag) }
384
411
  when 'f'
385
- then protect(link) {
412
+ protect(link) {
386
413
  case arg
387
414
  when '/', '_index'
388
415
  arg, label = '', 'All C API'
389
416
  end
390
417
  function_link(arg, label || arg, frag)
391
418
  }
392
- when 'd' then protect(link) { document_link(arg, label, frag) }
393
- when 'ref' then protect(link) { reference_link(arg) }
394
- when 'url' then direct_url(arg)
395
- when 'man' then man_link(arg)
419
+ when 'd'
420
+ protect(link) { document_link(arg, label, frag) }
421
+ when 'ref'
422
+ protect(link) { reference_link(arg) }
423
+ when 'url'
424
+ direct_url(arg)
425
+ when 'man'
426
+ man_link(arg)
396
427
  when 'rfc', 'RFC'
397
428
  rfc_link(arg)
398
429
  when 'ruby-list', 'ruby-dev', 'ruby-ext', 'ruby-talk', 'ruby-core'
@@ -462,7 +493,7 @@ module BitClust
462
493
  MAN_HEADER_URL = "#{opengroup_url}/basedefs/%s.html"
463
494
  MAN_LINUX_URL = "http://man7.org/linux/man-pages/man%1$s/%2$s.%1$s.html"
464
495
  MAN_FREEBSD_URL = "http://www.freebsd.org/cgi/man.cgi?query=%2$s&sektion=%1$s&manpath=FreeBSD+9.0-RELEASE"
465
-
496
+
466
497
  def man_url(section, page)
467
498
  case section
468
499
  when "1"
@@ -475,17 +506,30 @@ module BitClust
475
506
  sprintf(MAN_LINUX_URL, $1, page)
476
507
  when /\A([1-9])freebsd\Z/
477
508
  sprintf(MAN_FREEBSD_URL, $1, page)
478
- else
509
+ else
479
510
  nil
480
511
  end
481
512
  end
482
-
513
+
483
514
  def man_link(spec)
484
515
  m = /([\w\.\/]+)\((\w+)\)/.match(spec) or return escape_html(spec)
485
516
  url = man_url(m[2], escape_html(m[1])) or return escape_html(spec)
486
517
  %Q(<a class="external" href="#{escape_html(url)}">#{escape_html("#{m[1]}(#{m[2]})")}</a>)
487
518
  end
488
519
 
520
+ def rdoc_url(method_id, version)
521
+ cname, tmark, mname, libname = methodid2specparts(method_id)
522
+ tchar = typemark2char(tmark) == 'i' ? 'i' : 'c'
523
+ cname = cname.gsub('::', '/')
524
+ id = "method-#{tchar}-#{encodename_rdocurl(mname)}"
525
+
526
+ "http://docs.ruby-lang.org/en/#{version}/#{cname}.html##{id}"
527
+ end
528
+
529
+ def rdoc_link(method_id, version)
530
+ a_href(rdoc_url(method_id, version), "rdoc")
531
+ end
532
+
489
533
  def complete_spec(spec0)
490
534
  case spec0
491
535
  when /\A\$/