bitclust-core 1.3.0 → 1.6.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +65 -0
  4. data/Rakefile +32 -0
  5. data/data/bitclust/catalog/ja_JP.UTF-8 +19 -1
  6. data/data/bitclust/searchpage/index.html +90 -0
  7. data/data/bitclust/template/class +69 -8
  8. data/data/bitclust/template/function +2 -2
  9. data/data/bitclust/template/layout +11 -3
  10. data/data/bitclust/template/method +2 -2
  11. data/data/bitclust/template/search +1 -1
  12. data/data/bitclust/template.epub/class +22 -1
  13. data/data/bitclust/template.epub/function +2 -2
  14. data/data/bitclust/template.epub/layout +1 -3
  15. data/data/bitclust/template.epub/method +2 -2
  16. data/data/bitclust/template.lillia/class +26 -5
  17. data/data/bitclust/template.lillia/layout +1 -3
  18. data/data/bitclust/template.lillia/method +2 -2
  19. data/data/bitclust/template.offline/class +48 -4
  20. data/data/bitclust/template.offline/function +2 -2
  21. data/data/bitclust/template.offline/layout +27 -3
  22. data/data/bitclust/template.offline/method +2 -2
  23. data/lib/bitclust/app.rb +16 -17
  24. data/lib/bitclust/capi_converter.rb +20 -0
  25. data/lib/bitclust/classentry.rb +67 -17
  26. data/lib/bitclust/compat.rb +2 -2
  27. data/lib/bitclust/completion.rb +58 -27
  28. data/lib/bitclust/crossrubyutils.rb +8 -5
  29. data/lib/bitclust/database.rb +28 -8
  30. data/lib/bitclust/doc_converter.rb +63 -0
  31. data/lib/bitclust/docentry.rb +1 -1
  32. data/lib/bitclust/entity_splitter.rb +221 -0
  33. data/lib/bitclust/entry.rb +47 -7
  34. data/lib/bitclust/functiondatabase.rb +13 -1
  35. data/lib/bitclust/functionentry.rb +5 -1
  36. data/lib/bitclust/functionreferenceparser.rb +4 -4
  37. data/lib/bitclust/generators/epub.rb +10 -3
  38. data/lib/bitclust/htmlutils.rb +1 -1
  39. data/lib/bitclust/include_graph.rb +375 -0
  40. data/lib/bitclust/include_pruner.rb +144 -0
  41. data/lib/bitclust/interface.rb +1 -1
  42. data/lib/bitclust/libraryentry.rb +8 -7
  43. data/lib/bitclust/lineinput.rb +7 -5
  44. data/lib/bitclust/link_checker.rb +179 -0
  45. data/lib/bitclust/markdown_bridge.rb +186 -0
  46. data/lib/bitclust/markdown_orchestrator.rb +267 -0
  47. data/lib/bitclust/markdown_to_rrd.rb +824 -0
  48. data/lib/bitclust/markdown_tree.rb +217 -0
  49. data/lib/bitclust/mdcompiler.rb +883 -0
  50. data/lib/bitclust/mdparser.rb +347 -0
  51. data/lib/bitclust/messagecatalog.rb +3 -3
  52. data/lib/bitclust/method_since_calculator.rb +117 -0
  53. data/lib/bitclust/methoddatabase.rb +124 -14
  54. data/lib/bitclust/methodentry.rb +125 -7
  55. data/lib/bitclust/methodid.rb +12 -11
  56. data/lib/bitclust/methodsignature.rb +10 -4
  57. data/lib/bitclust/nameutils.rb +41 -22
  58. data/lib/bitclust/preprocessor.rb +66 -29
  59. data/lib/bitclust/rdcompiler.rb +195 -95
  60. data/lib/bitclust/refsdatabase.rb +23 -8
  61. data/lib/bitclust/reloadable_request_handler.rb +72 -0
  62. data/lib/bitclust/requesthandler.rb +21 -16
  63. data/lib/bitclust/ridatabase.rb +10 -4
  64. data/lib/bitclust/rrd_to_markdown.rb +925 -0
  65. data/lib/bitclust/rrdparser.rb +196 -30
  66. data/lib/bitclust/runner.rb +17 -2
  67. data/lib/bitclust/screen.rb +80 -11
  68. data/lib/bitclust/search_index_generator.rb +288 -0
  69. data/lib/bitclust/searcher.rb +69 -20
  70. data/lib/bitclust/simplesearcher.rb +24 -12
  71. data/lib/bitclust/subcommand.rb +7 -1
  72. data/lib/bitclust/subcommands/ancestors_command.rb +9 -2
  73. data/lib/bitclust/subcommands/checklink_command.rb +54 -0
  74. data/lib/bitclust/subcommands/chm_command.rb +15 -7
  75. data/lib/bitclust/subcommands/classes_command.rb +5 -0
  76. data/lib/bitclust/subcommands/extract_command.rb +5 -0
  77. data/lib/bitclust/subcommands/htmlfile_command.rb +21 -7
  78. data/lib/bitclust/subcommands/init_command.rb +4 -1
  79. data/lib/bitclust/subcommands/list_command.rb +12 -4
  80. data/lib/bitclust/subcommands/lookup_command.rb +28 -9
  81. data/lib/bitclust/subcommands/methods_command.rb +10 -2
  82. data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
  83. data/lib/bitclust/subcommands/preproc_command.rb +5 -0
  84. data/lib/bitclust/subcommands/property_command.rb +2 -1
  85. data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
  86. data/lib/bitclust/subcommands/server_command.rb +10 -8
  87. data/lib/bitclust/subcommands/setup_command.rb +24 -10
  88. data/lib/bitclust/subcommands/statichtml_command.rb +129 -6
  89. data/lib/bitclust/subcommands/update_command.rb +110 -2
  90. data/lib/bitclust/syntax_highlighter.rb +48 -4
  91. data/lib/bitclust/textutils.rb +3 -3
  92. data/lib/bitclust/version.rb +1 -1
  93. data/lib/bitclust/version_badges.rb +90 -0
  94. data/lib/bitclust/whole_file_gate.rb +137 -0
  95. data/test/test_app.rb +269 -0
  96. data/test/test_bitclust.rb +2 -0
  97. data/test/test_capi_converter.rb +91 -0
  98. data/test/test_class_screen.rb +413 -0
  99. data/test/test_copy_doc_md.rb +53 -0
  100. data/test/test_doc_converter.rb +78 -0
  101. data/test/test_entity_splitter.rb +231 -0
  102. data/test/test_entry.rb +207 -2
  103. data/test/test_eol_warning.rb +43 -0
  104. data/test/test_functionreferenceparser.rb +11 -0
  105. data/test/test_include_graph.rb +640 -0
  106. data/test/test_include_pruner.rb +133 -0
  107. data/test/test_link_checker.rb +114 -0
  108. data/test/test_lookup_command.rb +135 -0
  109. data/test/test_markdown_bridge.rb +217 -0
  110. data/test/test_markdown_orchestrator.rb +478 -0
  111. data/test/test_markdown_to_rrd.rb +757 -0
  112. data/test/test_markdown_tree.rb +369 -0
  113. data/test/test_mdcompiler.rb +1276 -0
  114. data/test/test_mdparser.rb +538 -0
  115. data/test/test_method_screen.rb +166 -0
  116. data/test/test_method_since_calculator.rb +290 -0
  117. data/test/test_methoddatabase.rb +16 -0
  118. data/test/test_methodentry.rb +328 -0
  119. data/test/test_methodsignature.rb +3 -1
  120. data/test/test_methodsince_command.rb +168 -0
  121. data/test/test_nameutils.rb +22 -0
  122. data/test/test_preprocessor.rb +215 -0
  123. data/test/test_rdcompiler.rb +401 -30
  124. data/test/test_refsdatabase.rb +32 -0
  125. data/test/test_rrd_to_markdown.rb +968 -0
  126. data/test/test_rrdparser.rb +295 -2
  127. data/test/test_run_ruby_wasm.rb +54 -0
  128. data/test/test_run_worker_prelude.rb +83 -0
  129. data/test/test_runner.rb +51 -0
  130. data/test/test_search_index_generator.rb +513 -0
  131. data/test/test_search_screen.rb +48 -0
  132. data/test/test_searcher.rb +132 -0
  133. data/test/test_searchpage_command.rb +167 -0
  134. data/test/test_simplesearcher.rb +28 -0
  135. data/test/test_statichtml_command.rb +204 -0
  136. data/test/test_syntax_highlighter.rb +60 -0
  137. data/test/test_whole_file_gate.rb +175 -0
  138. data/theme/default/js/NOTICE +42 -0
  139. data/theme/default/js/run-worker.js +111 -0
  140. data/theme/default/js/run.js +350 -0
  141. data/theme/default/js/search_controller.js +134 -0
  142. data/theme/default/js/search_init.js +176 -0
  143. data/theme/default/js/search_navigation.js +110 -0
  144. data/theme/default/js/search_page.js +202 -0
  145. data/theme/default/js/search_ranker.js +244 -0
  146. data/theme/default/script.js +91 -18
  147. data/theme/default/search.css +149 -0
  148. data/theme/default/style.css +193 -21
  149. data/theme/default/syntax-highlight.css +8 -0
  150. data/theme/lillia/style.css +45 -1
  151. metadata +161 -20
  152. data/README +0 -32
@@ -14,6 +14,8 @@ require 'bitclust/htmlutils'
14
14
  require 'bitclust/textutils'
15
15
  require 'bitclust/messagecatalog'
16
16
  require 'bitclust/syntax_highlighter'
17
+ require 'bitclust/version_badges'
18
+ require 'rouge'
17
19
  require 'stringio'
18
20
 
19
21
  module BitClust
@@ -24,17 +26,16 @@ module BitClust
24
26
  include HTMLUtils
25
27
  include TextUtils
26
28
  include Translatable
29
+ include VersionBadges
27
30
 
28
31
  def initialize(urlmapper, hlevel = 1, opt = {})
29
32
  @urlmapper = urlmapper
30
33
  @catalog = opt[:catalog]
31
34
  @hlevel = hlevel
32
35
  @type = nil
33
- @library = nil
34
- @class = nil
35
36
  @method = nil
36
37
  @option = opt.dup
37
- init_message_catalog(@catalog)
38
+ init_message_catalog(@catalog || MessageCatalog.new({}, 'C'))
38
39
  end
39
40
 
40
41
  def compile(src)
@@ -80,10 +81,10 @@ module BitClust
80
81
  when /\A---/
81
82
  entry_chunk
82
83
  when /\A=+/
83
- headline @f.gets
84
+ headline @f.gets || raise
84
85
  when /\A(\s+)\*\s/, /\A(\s+)\(\d+\)\s/
85
86
  @item_stack = []
86
- item_list($1.size)
87
+ item_list(($1 || raise).size)
87
88
  raise "@item_stack should be empty. #{@item_stack.inspect}" unless @item_stack.empty?
88
89
  when %r<\A//emlist(?:\[(?:[^\[\]]+?)?\]\[\w+?\])?\{>
89
90
  emlist
@@ -91,8 +92,12 @@ module BitClust
91
92
  dlist
92
93
  when /\A\s+\S/
93
94
  list
95
+ when /\A@see\b/
96
+ # findings#1: doc/lib ページの @see もメソッドエントリと同じく
97
+ # SEE_ALSO として解釈する(従来は段落テキストだった)
98
+ see
94
99
  else
95
- if @f.peek.strip.empty?
100
+ if @f.peek&.strip&.empty?
96
101
  @f.gets
97
102
  else
98
103
  paragraph
@@ -107,23 +112,35 @@ module BitClust
107
112
  end
108
113
  end
109
114
 
115
+ # シグネチャ行に続くメソッド属性行({: ...}。直前のシグネチャ行に束縛)
116
+ METHOD_ATTRIBUTE_LINE_RE = /\A\{:.*\}[ \t]*$/
117
+
110
118
  def entry_chunk
111
119
  @out.puts '<dl>' if @option[:force]
112
120
  first = true
113
- @f.while_match(/\A---/) do |line|
114
- method_signature(line, first)
115
- first = false
121
+ attrs = [] #: Array[String]
122
+ while @f.next?
123
+ if /\A---/ =~ @f.peek
124
+ method_signature(@f.gets || raise, first)
125
+ first = false
126
+ elsif !first && METHOD_ATTRIBUTE_LINE_RE =~ @f.peek
127
+ # メソッド属性行は本文には描画しない(undef のみ後でメッセージ)
128
+ attrs.concat attribute_tokens(@f.gets)
129
+ else
130
+ break
131
+ end
116
132
  end
117
- props = {}
133
+ props = {} #: Hash[String?, String?]
118
134
  @f.while_match(/\A:/) do |line|
119
135
  k, v = line.sub(/\A:/, '').split(':', 2)
120
- props[k.strip] = v.strip
136
+ props[k&.strip] = v&.strip
121
137
  end if @type == :method
122
138
  @out.puts %Q(<dd class="#{@type.to_s}-description">)
139
+ undef_message if attrs.include?('undef')
123
140
  while @f.next?
124
141
  case @f.peek
125
142
  when /\A===+/
126
- headline @f.gets
143
+ headline @f.gets || raise
127
144
  when /\A==?/
128
145
  if @option[:force]
129
146
  break
@@ -134,7 +151,7 @@ module BitClust
134
151
  break
135
152
  when /\A(\s+)\*\s/, /\A(\s+)\(\d+\)\s/
136
153
  @item_stack = []
137
- item_list($1.size)
154
+ item_list(($1 || raise).size)
138
155
  raise "@item_stack should be empty. #{@item_stack.inspect}" unless @item_stack.empty?
139
156
  when /\A:\s/
140
157
  dlist
@@ -142,14 +159,16 @@ module BitClust
142
159
  emlist
143
160
  when /\A\s+\S/
144
161
  list
145
- when /@see/
162
+ when /\A@see\b/
163
+ # findings#3: 無アンカーの /@see/ は行の途中に @see を含む
164
+ # 本文行を吸ってしまう(@todo も同様)
146
165
  see
147
- when /@todo/
166
+ when /\A@todo\b/
148
167
  todo
149
168
  when /\A@[a-z]/
150
169
  entry_info
151
170
  else
152
- if @f.peek.strip.empty?
171
+ if @f.peek&.strip&.empty?
153
172
  @f.gets
154
173
  else
155
174
  entry_paragraph
@@ -161,9 +180,9 @@ module BitClust
161
180
  end
162
181
 
163
182
  def headline(line)
164
- level = @hlevel + (line.slice(/\A=+/).size - 3)
183
+ level = @hlevel + (line.slice(/\A=+/)&.size.to_i - 3)
165
184
  label = line.sub(/\A=+(\[a:(.*?)\])?/, '').strip
166
- frag = $2 if $2 and not $2.empty?
185
+ frag = $2 if $2 and not ($2 || raise).empty?
167
186
  line h(level, escape_html(label), frag)
168
187
  end
169
188
 
@@ -172,7 +191,9 @@ module BitClust
172
191
  "<h#{level} #{name}>#{label}</h#{level}>"
173
192
  end
174
193
 
175
- def item_list(level = 0, indent = true)
194
+ ITEM_RE = /\A(\s+)(?:\*\s|\(\d+\))/
195
+
196
+ def item_list(level = 0)
176
197
  open_tag = nil
177
198
  close_tag = nil
178
199
  case @f.peek
@@ -183,11 +204,9 @@ module BitClust
183
204
  open_tag = "<ol>"
184
205
  close_tag = "</ol>"
185
206
  end
186
- if indent
187
- line open_tag
188
- @item_stack.push(close_tag)
189
- end
190
- @f.while_match(/\A(\s+)(?:\*\s|\(\d+\))/) do |line|
207
+ line open_tag
208
+ @item_stack.push(close_tag)
209
+ @f.while_match(ITEM_RE) do |line|
191
210
  string "<li>"
192
211
  @item_stack.push("</li>")
193
212
  string compile_text(line.sub(/\A(\s+)(?:\*|\(\d+\))/, '').strip)
@@ -196,15 +215,14 @@ module BitClust
196
215
  nl
197
216
  string compile_text(cont.strip)
198
217
  end
218
+ end
219
+ if (m = ITEM_RE.match(@f.peek)) && level < (m[1] || raise).size
220
+ item_list(($1 || raise).size)
199
221
  line @item_stack.pop # current level li
200
- elsif /\A(\s+)(?:\*\s|\(\d+\))/ =~ @f.peek and level < $1.size
201
- item_list($1.size)
202
- line @item_stack.pop # current level ul or ol
203
- elsif /\A(\s+)(?:\*\s|\(\d+\))/ =~ @f.peek and level > $1.size
222
+ break if ITEM_RE =~ @f.peek and level > ($1 || raise).size
223
+ elsif m && level > (m[1] || raise).size
204
224
  line @item_stack.pop # current level li
205
- line @item_stack.pop # current level ul or ol
206
- line @item_stack.pop # previous level li
207
- item_list($1.size, false)
225
+ break
208
226
  else
209
227
  line @item_stack.pop # current level li
210
228
  end
@@ -257,8 +275,8 @@ module BitClust
257
275
  line '</dd>'
258
276
  end
259
277
 
260
- def dt(s)
261
- "<dt>#{s}</dt>"
278
+ def dt(s, id = nil)
279
+ id ? %Q(<dt id="#{escape_html(id)}">#{s}</dt>) : "<dt>#{s}</dt>"
262
280
  end
263
281
 
264
282
  def stop_on_syntax_error?
@@ -266,31 +284,48 @@ module BitClust
266
284
  @option[:stop_on_syntax_error]
267
285
  end
268
286
 
287
+ # lang 指定付きコードブロックのハイライト HTML を返す。
288
+ # ruby(rb などの Rouge 上の alias を含む)は構文チェックを兼ねる
289
+ # Ripper ベースの SyntaxHighlighter、その他の言語は Rouge を使う。
290
+ # Rouge が知らない言語はエスケープのみ(従来この経路と ruby の構文
291
+ # エラー時フォールバックはエスケープされずに出力されていたのを修正)。
292
+ # invalid: true(md の ```ruby invalid)は構文として完全でないコードの
293
+ # 印で、Ripper の構文チェックをせず Rouge の lexer で色付けする(#251)
294
+ def highlight_source(src, lang, caption, invalid: false)
295
+ lexer = ::Rouge::Lexer.find(lang)
296
+ if lexer && lexer.tag == 'ruby' && !invalid
297
+ begin
298
+ filename = (caption&.size || 0) > 2 ? caption : @f.name or raise
299
+ BitClust::SyntaxHighlighter.new(src, filename).highlight
300
+ rescue BitClust::SyntaxHighlighter::Error => ex
301
+ $stderr.puts ex.message
302
+ exit(false) if stop_on_syntax_error?
303
+ escape_html(src)
304
+ end
305
+ elsif lexer
306
+ ::Rouge::Formatters::HTML.new.format(lexer.lex(src))
307
+ else
308
+ escape_html(src)
309
+ end
310
+ end
311
+
269
312
  def emlist
270
313
  command = @f.gets
271
314
  if %r!\A//emlist\[(?<caption>[^\[\]]+?)?\]\[(?<lang>\w+?)\]! =~ command
272
- line "<pre class=\"highlight #{lang}\">"
315
+ # @type var caption: String?
316
+ # @type var lang: String
317
+ # caption は pre 内の absolute 配置ではなく、pre の上に密着した
318
+ # タブとして描画する(pre 内だと編集・貼り付けで先頭行と重なる)
273
319
  line "<span class=\"caption\">#{escape_html(caption)}</span>" if caption
274
- line "<code>"
320
+ # <code> の直後に改行を入れると pre の内容の先頭に余計な空行として
321
+ # 表示される(pre 開始タグ直後の改行と違いブラウザは無視しない)
322
+ # ため、コード本体は <code> に直接続ける(#254)
323
+ string "<pre class=\"highlight #{lang}\"><code>"
275
324
  src = +""
276
325
  @f.until_terminator(%r<\A//\}>) do |line|
277
326
  src << line
278
327
  end
279
- if lang == "ruby"
280
- begin
281
- filename = (caption&.size || 0) > 2 ? caption : @f.name
282
- string BitClust::SyntaxHighlighter.new(src, filename).highlight
283
- rescue BitClust::SyntaxHighlighter::Error => ex
284
- $stderr.puts ex.message
285
- if stop_on_syntax_error?
286
- exit(false)
287
- else
288
- string src
289
- end
290
- end
291
- else
292
- string src
293
- end
328
+ string highlight_source(src, lang, caption)
294
329
  line '</code></pre>'
295
330
  else
296
331
  line '<pre>'
@@ -303,7 +338,7 @@ module BitClust
303
338
 
304
339
  def list
305
340
  lines = unindent_block(canonicalize(@f.break(/\A\S/)))
306
- while lines.last.empty?
341
+ while lines.last&.empty?
307
342
  lines.pop
308
343
  end
309
344
  line '<pre>'
@@ -328,7 +363,7 @@ module BitClust
328
363
  end
329
364
 
330
365
  def see
331
- header = @f.gets
366
+ header = @f.gets or raise
332
367
  header.slice!(/\A\@\w+/)
333
368
  body = [header] + @f.span(/\A\s+\S/)
334
369
  line '<p>'
@@ -337,7 +372,7 @@ module BitClust
337
372
  end
338
373
 
339
374
  def todo
340
- header = @f.gets
375
+ header = @f.gets or raise
341
376
  header.slice!(/\A\@\w+/)
342
377
  body = header
343
378
  line '<p class="todo">'
@@ -345,12 +380,25 @@ module BitClust
345
380
  line '</p>'
346
381
  end
347
382
 
383
+ def attribute_tokens(attr_line)
384
+ (attr_line || '')[/\A\{:(.*)\}/, 1].to_s.strip.split(/\s+/)
385
+ end
386
+
387
+ # {: undef} 属性付きエントリのメッセージ。ページを見に来た人向け
388
+ # (statichtml は undefined エントリを skip するので server 等の動的経路用)。
389
+ # nomethod は説明が本文に書かれている前提のマーカーなので何も描画しない
390
+ def undef_message
391
+ line '<p>'
392
+ line 'このメソッドは定義されていません。'
393
+ line '</p>'
394
+ end
395
+
348
396
  def entry_info
349
397
  line '<dl>'
350
398
  while @f.next? and /\A\@(?!see)\w+|\A$/ =~ @f.peek
351
- header = @f.gets
399
+ header = @f.gets or raise
352
400
  next if /\A$/ =~ header
353
- cmd = header.slice!(/\A\@\w+/)
401
+ cmd = header.slice!(/\A\@\w+/) or raise
354
402
  @f.ungets(header)
355
403
  case cmd
356
404
  when '@param', '@arg'
@@ -386,9 +434,10 @@ module BitClust
386
434
  string %Q(<dt class="method-heading")
387
435
  string %Q( id="#{@method.index_id}") if first
388
436
  string '><code>'
389
- string @method.klass.name + @method.typemark if @opt
437
+ string @method.klass.name + @method.display_typemark if @opt
390
438
  string escape_html(sig.friendly_string)
391
439
  string '</code>'
440
+ string heading_version_badges(@method, sig.name, first) if @method
392
441
  if first
393
442
  string '<span class="permalink">['
394
443
  string a_href(@urlmapper.method_url(methodid2specstring(@method.id)), "permalink")
@@ -396,7 +445,9 @@ module BitClust
396
445
  string rdoc_link(@method.id, @option[:database].properties["version"])
397
446
  if @option[:edit_base_url] && @method.source_location
398
447
  string ']['
399
- string a_href(@urlmapper.edit_url(@method.source_location), 'edit')
448
+ # @type var urlmapper: ::BitClust::Subcommands::StatichtmlCommand::URLMapperEx
449
+ urlmapper = _ = @urlmapper
450
+ string a_href(urlmapper.edit_url(@method.source_location), 'edit')
400
451
  end
401
452
  string ']</span>'
402
453
  end
@@ -407,54 +458,68 @@ module BitClust
407
458
  end
408
459
 
409
460
  BracketLink = /\[\[[\w-]+?:[!-~]+?(?:\[\] )?\]\]/n
461
+ # BitClust には ISO/JIS へのリンク機能がないため、[[ISO:8601]]/[[JIS:X 0301]] は
462
+ # リンクにせず平文で表示する (rurema/bitclust#236)。引数に空白を含む JIS も拾う。
463
+ StandardRef = /\[\[(?:ISO|JIS):[!-~][!-~ ]*?\]\]/n
410
464
  NeedESC = /[&"<>]/
411
465
 
412
466
  def compile_text(str)
413
467
  escape_table = HTMLUtils::ESC
414
- str.gsub(/(#{NeedESC})|(#{BracketLink})/o) {
415
- if char = $1 then escape_table[char]
416
- elsif tok = $2 then bracket_link(tok[2..-3])
417
- elsif tok = $3 then seems_code(tok)
468
+ str.gsub(/(#{NeedESC})|(#{StandardRef})|(#{BracketLink})/o) {
469
+ # @type var char: '&' | '"' | '<' | '>'
470
+ if char = _ = $1 then escape_table[char]
471
+ elsif tok = $2 then standard_ref(tok[2..-3] || raise)
472
+ elsif tok = $3 then bracket_link(tok[2..-3] || raise)
418
473
  else
419
474
  raise 'must not happen'
420
475
  end
421
476
  }
422
477
  end
423
478
 
479
+ def standard_ref(link)
480
+ # 例: "ISO:8601" -> "ISO 8601", "JIS:X 0301" -> "JIS X 0301"
481
+ type, _arg = link.split(':', 2)
482
+ arg = _arg&.strip or raise
483
+ escape_html("#{type} #{arg}")
484
+ end
485
+
424
486
  def bracket_link(link, label = nil, frag = nil)
425
487
  type, _arg = link.split(':', 2)
426
- arg = _arg.rstrip
488
+ arg = _arg&.rstrip or raise
489
+ # link_checker(bitclust/link_checker.rb)が指定されているときは
490
+ # 参照を検証用に記録する。描画には影響しない
491
+ @option[:link_checker]&.note_ref(type || raise, arg)
427
492
  case type
428
493
  when 'lib'
429
494
  protect(link) {
430
495
  case arg
431
496
  when '/', '_index'
432
- label = 'All libraries'
497
+ label = _('Library Index')
433
498
  when '_builtin'
434
- label = 'Builtin libraries'
499
+ label = _('Builtin Library')
435
500
  end
436
501
  library_link(arg, label, frag)
437
502
  }
438
503
  when 'c'
439
504
  protect(link) { class_link(arg, label, frag) }
440
505
  when 'm'
441
- protect(link) { method_link(complete_spec(arg), label || arg, frag) }
506
+ protect(link) { method_link(complete_spec(arg), label || display_spec(arg), frag) }
442
507
  when 'f'
443
508
  protect(link) {
444
509
  case arg
445
510
  when '/', '_index'
446
- arg, label = '', 'All C API'
511
+ arg, label = '', _('Function Index')
447
512
  end
448
513
  function_link(arg, label || arg, frag)
449
514
  }
450
515
  when 'd'
451
516
  protect(link) { document_link(arg, label, frag) }
452
517
  when 'ref'
453
- protect(link) { reference_link(arg) }
518
+ protect(link) { reference_link(arg, label) }
454
519
  when 'url'
455
520
  direct_url(arg)
456
521
  when 'man'
457
- man_link(arg)
522
+ man_link(arg, label)
458
523
  when 'rfc', 'RFC'
459
524
  rfc_link(arg)
460
525
  when 'ruby-list', 'ruby-dev', 'ruby-ext', 'ruby-talk', 'ruby-core'
@@ -476,37 +541,51 @@ module BitClust
476
541
  %Q(<a class="external" href="#{escape_html(url)}">#{escape_html(url)}</a>)
477
542
  end
478
543
 
479
- def reference_link(arg)
544
+ # label はラベル付き参照(MDCompiler#ref_md_link)からの表示テキスト
545
+ # 上書き。指定時は refs テーブルからのセクションラベル計算を行わない
546
+ def reference_link(arg, label = nil)
480
547
  case arg
481
- when /(\w+):(.*)\#(\w+)\z/
548
+ when /(\w+):(.*)\#([-\w]+)\z/
549
+ # @type var type: String
550
+ # @type var name: String
551
+ # @type var frag: String
482
552
  type, name, frag = $1, $2, $3
483
- case type
484
- when 'lib'
485
- title, t, id = name, LibraryEntry.type_id.to_s, name
486
- when 'c'
487
- title, t, id = name, ClassEntry.type_id.to_s, name
488
- when 'm'
489
- title, t, id = name, MethodEntry.type_id.to_s, name
490
- when 'd'
491
- title, t, id = @option[:database].get_doc(name).title, DocEntry.type_id.to_s, name
492
- else
493
- raise "must not happen"
553
+ unless label
554
+ # @type var title: String
555
+ # @type var t: String
556
+ # @type var id: String
557
+ case type
558
+ when 'lib'
559
+ title, t, id = name, LibraryEntry.type_id.to_s, name
560
+ when 'c'
561
+ title, t, id = name, ClassEntry.type_id.to_s, name
562
+ when 'm'
563
+ # 表示側(title)のみ display_spec で畳む。id は refs テーブルの
564
+ # キーなので記載どおり(bitclust#282)
565
+ title, t, id = display_spec(name), MethodEntry.type_id.to_s, name
566
+ when 'd'
567
+ title, t, id = @option[:database].get_doc(name).title, DocEntry.type_id.to_s, name
568
+ else
569
+ raise "must not happen"
570
+ end
571
+ label = @option[:database].refs[t, id, frag]
572
+ label = title + '/' + label if label and name
494
573
  end
495
- label = @option[:database].refs[t, id, frag]
496
- label = title + '/' + label if label and name
497
574
  bracket_link("#{type}:#{name}", label, frag)
498
- when /\A(\w+)\z/
499
- e = @option[:entry]
500
- frag = $1
501
- type = e.type_id.to_s
502
- label = @option[:database].refs[type, e.name, frag] || frag
575
+ when /\A([-\w]+)\z/
576
+ frag = $1 || raise
577
+ unless label
578
+ e = @option[:entry]
579
+ type = e.type_id.to_s
580
+ label = @option[:database].refs[type, e.name, frag] || frag
581
+ end
503
582
  a_href('#' + frag, label)
504
583
  else
505
584
  raise "must not happen"
506
585
  end
507
586
  end
508
587
 
509
- BLADE_URL = 'http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/%s/%s'
588
+ BLADE_URL = 'https://blade.ruby-lang.org/%s/%s'
510
589
 
511
590
  def blade_link(ml, num)
512
591
  url = sprintf(BLADE_URL, ml, num)
@@ -544,10 +623,12 @@ module BitClust
544
623
  end
545
624
  end
546
625
 
547
- def man_link(spec)
548
- m = /([\w\.\/]+)\((\w+)\)/.match(spec) or return escape_html(spec)
549
- url = man_url(m[2], escape_html(m[1])) or return escape_html(spec)
550
- %Q(<a class="external" href="#{escape_html(url)}">#{escape_html("#{m[1]}(#{m[2]})")}</a>)
626
+ # label はラベル付き参照(MDCompiler#ref_md_link)からの表示テキスト
627
+ # 上書き。リンクにできない spec ではラベルをプレーン表示に使う
628
+ def man_link(spec, label = nil)
629
+ m = /([\w\.\/]+)\((\w+)\)/.match(spec) or return escape_html(label || spec)
630
+ url = man_url(m[2], escape_html(m[1] || raise)) or return escape_html(label || spec)
631
+ %Q(<a class="external" href="#{escape_html(url)}">#{escape_html(label || "#{m[1]}(#{m[2]})")}</a>)
551
632
  end
552
633
 
553
634
  BUGS_URL = "https://bugs.ruby-lang.org/issues/%s"
@@ -559,8 +640,14 @@ module BitClust
559
640
 
560
641
  def rdoc_url(method_id, version)
561
642
  cname, tmark, mname, _libname = methodid2specparts(method_id)
562
- tchar = typemark2char(tmark) == 'i' ? 'i' : 'c'
563
- cname = cname.split(".").first
643
+ cname = cname.split(".").first || raise
644
+ # rdoc は Kernel のモジュール関数(rb_define_global_function 由来)
645
+ # private インスタンスメソッドとして掲載しているため i にする
646
+ if typemark2char(tmark) == 'i' || (cname == "Kernel" && tmark == '.#')
647
+ tchar = 'i'
648
+ else
649
+ tchar = 'c'
650
+ end
564
651
  cname = cname.gsub('::', '/')
565
652
  id = "method-#{tchar}-#{encodename_rdocurl(mname)}"
566
653
 
@@ -580,6 +667,19 @@ module BitClust
580
667
  end
581
668
  end
582
669
 
670
+ # bitclust#282: [[m:...]] のデフォルトラベル(参照の記載文字列)は表示
671
+ # 専用なので、module function の ".#" は DB バージョンが 4.0 以降なら
672
+ # "?." で表示する(#250/#277 の display_typemark と同じ選択規則。
673
+ # Markdown ソースの [m:Kernel?.x] も inline 復元時に ".#" へ正規化されて
674
+ # ここに来るため、どちらの表記で書かれていても見出し表記と一致する)。
675
+ # spec・URL・アンカーキーは不変。明示ラベル(label 引数)には触れない
676
+ def display_spec(spec)
677
+ return spec unless spec.include?('.#')
678
+ db = @option[:database]
679
+ version = db ? db.propget('version') : nil
680
+ spec.sub('.#', NameUtils.display_typemark('.#', version))
681
+ end
682
+
583
683
  def seems_code(text)
584
684
  # FIXME
585
685
  escape_html(text)
@@ -12,17 +12,21 @@ module BitClust
12
12
  class RefsDatabase
13
13
  def self.load(src)
14
14
  if src.respond_to?(:to_str)
15
+ # @type var src: _ToStr
15
16
  buf = fopen(src.to_str, 'r:UTF-8'){|f| f.read}
16
17
  elsif src.respond_to?(:to_io)
18
+ # @type var src: _ToIO
17
19
  buf = src.to_io.read
18
20
  else
21
+ # @type var src: _Reader
19
22
  buf = src.read
20
23
  end
21
24
 
22
25
  refs = self.new
23
- buf.each_line{|l|
26
+ buf&.each_line{|l|
24
27
  if /((?:\\,|[^,])+),((?:\\,|[^,])+),((?:\\,|[^,])+),((?:\\,|[^,])+)\n/ =~ l
25
- type, id, linkid, desc = [$1, $2, $3, $4].map{|e| e.gsub(/\\(.)/){|s| $1 == ',' ? ',' : s } }
28
+ type, id, linkid, desc = [$1, $2, $3, $4].map{|e| e&.gsub(/\\(.)/){|s| $1 == ',' ? ',' : s } }
29
+ type || raise; id || raise; linkid || raise; desc || raise
26
30
  refs[type, id, linkid] = desc
27
31
  end
28
32
  }
@@ -43,15 +47,18 @@ module BitClust
43
47
 
44
48
  def save(s)
45
49
  if s.respond_to?(:to_str)
50
+ # @type var s: _ToStr
46
51
  path = s.to_str
47
52
  io = fopen(path, 'w:UTF-8')
48
53
  elsif s.respond_to?(:to_io)
54
+ # @type var s: _ToIO
49
55
  io = s.to_io
50
56
  else
51
57
  io = s
52
58
  end
59
+ # @type var io: IO
53
60
 
54
- @h.each{|k, v|
61
+ @h.sort.each{|k, v|
55
62
  io.write( [k, v].flatten.map{|e| e.gsub(/,/, '\\,') }.join(',') + "\n" )
56
63
  }
57
64
  io.close
@@ -59,11 +66,19 @@ module BitClust
59
66
 
60
67
  def extract(entry)
61
68
  entry.source.each_line{|l|
62
- if /\A={1,6}\[a:(\w+)\] *(.*)/ =~ l
63
- entry.labels.each{|name|
64
- self[entry.class.type_id, name, $1] = $2
65
- }
66
- end
69
+ anchor, label =
70
+ if /\A={1,6}\[a:([-\w]+)\] *(.*)/ =~ l
71
+ [$1, $2]
72
+ elsif /\A\#{1,6} +(.*?) *\{#([-\w]+)\}\s*\z/ =~ l
73
+ # md ソース: 「### 見出し {#anchor}」形式(M3 ネイティブパース)。
74
+ # ラベルは rd 表示形へ戻す(\` エスケープ解除等。pattern_matching)
75
+ Kernel.require 'bitclust/markdown_to_rrd'
76
+ [$2, ::BitClust::MarkdownToRRD.restore_description($1 || raise)]
77
+ end
78
+ next unless anchor
79
+ entry.labels.each{|name|
80
+ self[entry.class.type_id, name, anchor || raise] = label || raise
81
+ }
67
82
  }
68
83
  end
69
84
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # bitclust/reloadable_request_handler.rb
4
+ #
5
+
6
+ require 'bitclust/methoddatabase'
7
+ require 'bitclust/functiondatabase'
8
+ require 'bitclust/requesthandler'
9
+
10
+ module BitClust
11
+
12
+ # `bitclust server` を再起動しなくても `rake generate` で再生成された DB を
13
+ # 読み直せるようにするラッパー(bitclust#275)。
14
+ #
15
+ # MethodDatabase/FunctionDatabase はエントリを一度読むとメモリ上にメモ化
16
+ # し続けるため、App#initialize で一度だけ生成した RequestHandler をずっと
17
+ # 使い回す従来の作りでは、DB をディスク上で再生成してもプロセスは古い内容
18
+ # を返し続けてしまう。
19
+ #
20
+ # 新鮮さの判定は DB ディレクトリ直下の "properties" ファイルの mtime で行う。
21
+ # `rake generate` は対象を rm_rf してから MethodDatabase#init(properties を
22
+ # touch) → update という手順を踏むため、再生成のたびに properties は必ず
23
+ # 書き直される。#handle は毎リクエストその mtime を stat するだけなので
24
+ # コストは無視できる。
25
+ #
26
+ # 再生成の途中(rm_rf 直後〜init 前)は properties が一時的に存在しない。
27
+ # その間は新鮮さを判定できないので、古い RequestHandler をそのまま使い
28
+ # 続けてリクエストを失敗させない。次のリクエストで改めて判定する。
29
+ class ReloadableRequestHandler
30
+
31
+ def initialize(dbpath, capi, manager, request_handler_class)
32
+ @dbpath = dbpath
33
+ @capi = capi
34
+ @manager = manager
35
+ @request_handler_class = request_handler_class
36
+ @properties_mtime = properties_mtime
37
+ @handler = build_handler
38
+ end
39
+
40
+ def handle(req)
41
+ reload_if_stale
42
+ @handler.handle(req)
43
+ end
44
+
45
+ private
46
+
47
+ def reload_if_stale
48
+ mtime = properties_mtime
49
+ return unless mtime # 再生成中で properties が一時的に無い: 判定を諦めて継続
50
+ return if @properties_mtime == mtime
51
+
52
+ @properties_mtime = mtime
53
+ @handler = build_handler
54
+ $bitclust_context_cache = nil # clear cache
55
+ end
56
+
57
+ def properties_mtime
58
+ File.mtime("#{@dbpath}/properties")
59
+ rescue Errno::ENOENT
60
+ nil
61
+ end
62
+
63
+ def build_handler
64
+ db = BitClust::MethodDatabase.new(@dbpath)
65
+ if @capi
66
+ db = [db, BitClust::FunctionDatabase.new(@dbpath)] #: [MethodDatabase, FunctionDatabase]
67
+ end
68
+ @request_handler_class.new(db, @manager)
69
+ end
70
+
71
+ end
72
+ end