bitclust-core 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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\$/