bitclust-core 1.6.1 → 1.7.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/Rakefile +1 -0
  3. data/data/bitclust/catalog/ja_JP.UTF-8 +4 -2
  4. data/data/bitclust/template/function-index +1 -0
  5. data/data/bitclust/template/layout +1 -0
  6. data/data/bitclust/template.offline/function-index +1 -0
  7. data/data/bitclust/template.offline/layout +3 -0
  8. data/lib/bitclust/classentry.rb +1 -1
  9. data/lib/bitclust/docentry.rb +1 -1
  10. data/lib/bitclust/functionentry.rb +1 -1
  11. data/lib/bitclust/irb.rb +68 -0
  12. data/lib/bitclust/markdown_exporter.rb +311 -0
  13. data/lib/bitclust/mdcompiler.rb +15 -1
  14. data/lib/bitclust/mdparser.rb +42 -1
  15. data/lib/bitclust/methodentry.rb +24 -0
  16. data/lib/bitclust/methodsignature.rb +1 -1
  17. data/lib/bitclust/preprocessor.rb +4 -1
  18. data/lib/bitclust/rbs_overload_matcher.rb +179 -0
  19. data/lib/bitclust/rbs_sig_importer.rb +276 -0
  20. data/lib/bitclust/rbs_signatures.rb +125 -0
  21. data/lib/bitclust/rdcompiler.rb +12 -1
  22. data/lib/bitclust/runner.rb +2 -0
  23. data/lib/bitclust/screen.rb +4 -0
  24. data/lib/bitclust/search_index_generator.rb +28 -10
  25. data/lib/bitclust/searcher.rb +12 -2
  26. data/lib/bitclust/subcommands/rbssig_command.rb +99 -0
  27. data/lib/bitclust/subcommands/setup_command.rb +4 -1
  28. data/lib/bitclust/subcommands/statichtml_command.rb +59 -0
  29. data/lib/bitclust/version.rb +1 -1
  30. data/lib/bitclust-irb.rb +2 -0
  31. data/test/test_irb_plugin.rb +118 -0
  32. data/test/test_link_checker.rb +1 -1
  33. data/test/test_markdown_exporter.rb +260 -0
  34. data/test/test_mdcompiler.rb +47 -5
  35. data/test/test_mdparser.rb +184 -4
  36. data/test/test_methodentry.rb +111 -0
  37. data/test/test_methodsignature.rb +3 -1
  38. data/test/test_preprocessor.rb +26 -0
  39. data/test/test_rbs_overload_matcher.rb +170 -0
  40. data/test/test_rbs_sig_importer.rb +237 -0
  41. data/test/test_rbssig_command.rb +186 -0
  42. data/test/test_rdcompiler.rb +99 -1
  43. data/test/test_search_index_generator.rb +81 -0
  44. data/test/test_setup_command.rb +21 -0
  45. data/test/test_statichtml_command.rb +137 -0
  46. data/theme/default/js/run.js +14 -0
  47. data/theme/default/js/search_init.js +3 -1
  48. data/theme/default/js/search_page.js +7 -1
  49. data/theme/default/js/version_switcher.js +147 -0
  50. data/theme/default/search.css +21 -0
  51. data/theme/default/style.css +83 -7
  52. data/theme/lillia/style.css +26 -0
  53. metadata +35 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c50953e0ecaf38a9d4c6187b22eaccedd4b9f52029bb638e026fbbfc655d8a2
4
- data.tar.gz: c5ce94f2931f5472651e6bd425c766ba19b9ff4acc87d8fd5ae8e57f384db3d6
3
+ metadata.gz: 9f02fb9cdddf44a8c30df3e0e9d2fa51159db37abb39220e090a5bc94b61c9cf
4
+ data.tar.gz: ceffcab8cd2c8bdd34d555e104cd8ac0d774f89b4e42296ff26b64f16570ccbd
5
5
  SHA512:
6
- metadata.gz: 4766a837042d881e63e96264338d7e04aa6e10e63db1b2ff22ca0e53e6f6e97f766efadbe9c1a6ff8c4b89bfac996eb4c060c9b9abfd8c4aff420c57a987a3f7
7
- data.tar.gz: e64091ccdeddf09ed18339d2be46af88abc28c6d9aaf6ae9da888c4125c5ab47b526c315d9f630e76389258fe56f950600b1265d42c10d98a2d595b3892f02d8
6
+ metadata.gz: 66e1b74b7651e3c12be4d9f55facd88ab6a29def01fae1deb67670191d861a070d95a05c21309e61edf7f43ee7a6f3a948aff410a3bc400eb4b96b5ec6822634
7
+ data.tar.gz: 680e2945c4225e31b0417495c72945a90824105a7af7d9151a006a30d690fc7b36bd04b9fd4427fad46ab6b1513517c7c052ce49c9ec7913ed3b155402f9fa20
data/Rakefile CHANGED
@@ -21,6 +21,7 @@ namespace :test do
21
21
  sh 'qjs', 'test/js/test_run.mjs'
22
22
  sh 'qjs', 'test/js/test_script.mjs'
23
23
  sh 'qjs', 'test/js/test_search.mjs'
24
+ sh 'qjs', 'test/js/test_version_switcher.mjs'
24
25
  end
25
26
  end
26
27
 
@@ -7,7 +7,7 @@ Added/Redefined Methods
7
7
  All Classes
8
8
  クラス一覧
9
9
  All Functions
10
- 関数一覧
10
+ C API 関数一覧
11
11
  All Libraries
12
12
  ライブラリ一覧
13
13
  Ancestor Methods
@@ -51,7 +51,7 @@ File
51
51
  FileFormat
52
52
  ファイルフォーマット
53
53
  Function Index
54
- 関数一覧
54
+ C API 関数一覧
55
55
  Index
56
56
  目次
57
57
  I/O
@@ -112,6 +112,8 @@ Text
112
112
  テキスト
113
113
  Thread
114
114
  スレッド
115
+ This is a list of functions provided by the Ruby C API for developing Ruby itself and C extension libraries. These functions cannot be called directly from Ruby programs.
116
+ Ruby の C API(C 言語 API)の関数の一覧です。Ruby 本体や拡張ライブラリの開発に使うもので、Ruby プログラムから直接呼び出す関数ではありません。
115
117
  This reference manual is for a version of Ruby that is no longer maintained.
116
118
  このマニュアルは既にメンテナンスが終了したバージョンの Ruby を対象としています。
117
119
  Undefined Methods for Explanation
@@ -8,6 +8,7 @@
8
8
  headline_init
9
9
  %>
10
10
  <%= headline(_("Function Index")) %>
11
+ <p><%= _('This is a list of functions provided by the Ruby C API for developing Ruby itself and C extension libraries. These functions cannot be called directly from Ruby programs.') %></p>
11
12
  <table class="entries functions">
12
13
  <%
13
14
  headline_push
@@ -13,6 +13,7 @@
13
13
  <link rel="search" type="application/opensearchdescription+xml" title="<%= _('Ruby %s Reference Manual', ruby_version()) %>" href="<%=h opensearchdescription_url() %>">
14
14
  <% if run_ruby_wasm_url %>
15
15
  <meta name="rurema-run-ruby-wasm" content="<%=h run_ruby_wasm_url %>">
16
+ <meta name="rurema-run-help" content="<%=h document_url('help') %>#run">
16
17
  <script type="module" src="<%=h custom_js_url('js/run.js') %>"></script>
17
18
  <% end %>
18
19
  </head>
@@ -36,6 +36,7 @@
36
36
  </header>
37
37
 
38
38
  <main>
39
+ <p><%= _('This is a list of functions provided by the Ruby C API for developing Ruby itself and C extension libraries. These functions cannot be called directly from Ruby programs.') %></p>
39
40
  <table class="entries functions">
40
41
  <%
41
42
  headline_push
@@ -21,8 +21,10 @@
21
21
  <script src="<%=h custom_js_url('js/search_ranker.js') %>"></script>
22
22
  <script src="<%=h custom_js_url('js/search_controller.js') %>"></script>
23
23
  <script src="<%=h custom_js_url('js/search_init.js') %>"></script>
24
+ <script defer src="<%=h custom_js_url('js/version_switcher.js') %>"></script>
24
25
  <% if run_ruby_wasm_url %>
25
26
  <meta name="rurema-run-ruby-wasm" content="<%=h run_ruby_wasm_url %>">
27
+ <meta name="rurema-run-help" content="<%=h document_url('help') %>#run">
26
28
  <script type="module" src="<%=h custom_js_url('js/run.js') %>"></script>
27
29
  <% end %>
28
30
  </head>
@@ -35,6 +37,7 @@
35
37
  <% end %>
36
38
  <div id="rurema-topbar">
37
39
  <div id="rurema-brand"><%= manual_home_link %></div>
40
+ <div id="version-switcher"></div>
38
41
  <div id="search-section" role="search">
39
42
  <input id="search-field" type="text" autocomplete="off" spellcheck="false"
40
43
  placeholder="クラス・メソッドを検索 (/)"
@@ -452,7 +452,7 @@ module BitClust
452
452
  end
453
453
 
454
454
  def description
455
- description_text(source.split(/\n\n+/, 2)[0].strip)
455
+ description_text(source.split(/\n\n+/, 2)[0].to_s.strip)
456
456
  end
457
457
 
458
458
  def clear_cache
@@ -84,7 +84,7 @@ module BitClust
84
84
  end
85
85
 
86
86
  def description
87
- description_text(source.split(/\n\n/, 2)[0].strip)
87
+ description_text(source.split(/\n\n/, 2)[0].to_s.strip)
88
88
  end
89
89
  end
90
90
  end
@@ -83,7 +83,7 @@ module BitClust
83
83
  end
84
84
 
85
85
  def description
86
- description_text(source.split(/\n\n+/, 2)[0].strip)
86
+ description_text(source.split(/\n\n+/, 2)[0].to_s.strip)
87
87
  end
88
88
  end
89
89
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # irb からるりま(Ruby リファレンスマニュアル)を引く refe コマンド。
4
+ # ~/.irbrc に `require "bitclust/irb"` と書くと irb に `refe` コマンドが
5
+ # 登録される。検索対象の DB は refe コマンドと同じ場所
6
+ # (bitclust setup が作る ~/.bitclust/config)から探す。
7
+
8
+ require 'stringio'
9
+ require 'bitclust'
10
+ require 'bitclust/searcher'
11
+
12
+ module BitClust
13
+ module Irb
14
+ USAGE = <<~USAGE
15
+ Usage: refe <pattern>
16
+
17
+ 例:
18
+ refe String#gsub インスタンスメソッド
19
+ refe Array.new 特異メソッド
20
+ refe Comparable クラス・モジュール
21
+ refe printf 名前だけでの検索
22
+
23
+ DB が無い場合は `bitclust setup` で作成してください。
24
+ USAGE
25
+
26
+ # pattern を検索して整形済みテキストを io へ書く。db が nil なら
27
+ # 既定の場所から探す。検索の失敗は例外にせず io へメッセージを書く
28
+ # (irb セッションを止めないため)
29
+ def self.lookup(pattern, io: $stdout, db: nil)
30
+ words = pattern.to_s.split
31
+ if words.empty?
32
+ io.puts USAGE
33
+ return
34
+ end
35
+ view = TerminalView.new(Plain.new,
36
+ { describe_all: false, line: false, encoding: nil },
37
+ io: io)
38
+ Searcher.new.run_query(db, words, view)
39
+ rescue BitClust::UserError => err
40
+ io.puts err.message
41
+ end
42
+
43
+ begin
44
+ require 'irb/command'
45
+ rescue LoadError
46
+ # irb が無い(または irb < 1.13 で公開コマンド API が無い)環境では
47
+ # コマンド登録だけを諦め、Irb.lookup は使えるままにする
48
+ else
49
+ class RefeCommand < ::IRB::Command::Base
50
+ category 'Documentation'
51
+ description 'るりま(Ruby リファレンスマニュアル)を検索して表示します'
52
+ help_message USAGE
53
+
54
+ def execute(arg)
55
+ content = StringIO.new
56
+ BitClust::Irb.lookup(arg, io: content)
57
+ if ::IRB.const_defined?(:Pager)
58
+ ::IRB::Pager.page_content(content.string)
59
+ else
60
+ $stdout.puts content.string
61
+ end
62
+ end
63
+ end
64
+
65
+ ::IRB::Command.register(:refe, RefeCommand)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,311 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bitclust/nameutils'
4
+
5
+ module BitClust
6
+
7
+ # statichtml の --markdown-output 用に、HTML の各ページと対になる Markdown を
8
+ # エントリの前処理済みソース(Markdown ツリー由来の md)から組み立てる。
9
+ # 参照記法(単一ブラケットの [m:...] 等と、参照 scheme を宛先にした
10
+ # ラベル付きリンク)は相対 .md リンクへ解決する。解決できない参照
11
+ # (未対応の種別・不正な指定)はそのまま残し、エラーにしない。
12
+ # コードスパンとコードフェンスの中は変換しない。
13
+ class MarkdownExporter
14
+
15
+ # クラスページの メソッド種別 → セクション見出し(doctree の md と同じ名称)
16
+ CLASS_SECTIONS = [
17
+ ["Class Methods", :public, :singleton_method],
18
+ ["Instance Methods", :public, :instance_method],
19
+ ["Private Class Methods", :private, :singleton_method],
20
+ ["Private Instance Methods", :private, :instance_method],
21
+ ["Protected Instance Methods", :protected, :instance_method],
22
+ ["Module Functions", :public, :module_function],
23
+ ["Constants", :public, :constant],
24
+ ["Special Variables", :public, :special_variable],
25
+ ].freeze
26
+
27
+ def initialize(urlmapper, html_suffix)
28
+ @urlmapper = urlmapper
29
+ @html_suffix = html_suffix
30
+ end
31
+
32
+ def library_page(entry)
33
+ page("# library #{entry.name}", entry.source)
34
+ end
35
+
36
+ # クラスページは HTML と同じく本文+メソッドリンク一覧(本文は載せない)
37
+ def class_page(entry)
38
+ out = +""
39
+ CLASS_SECTIONS.each do |section, visibility, type|
40
+ methods = entry.entries.select { |m|
41
+ !m.undefined? && m.type == type && m.visibility == visibility
42
+ }
43
+ next if methods.empty?
44
+ out << "\n## #{section}\n\n"
45
+ methods.each do |m|
46
+ m.names.each do |name|
47
+ spec = m.klass.name + m.typemark + name
48
+ url = ref_url('m', spec)
49
+ out << (url ? "- [#{escape_label(name)}](#{url})\n" : "- #{escape_label(name)}\n")
50
+ end
51
+ end
52
+ end
53
+ page(class_heading(entry), entry.source, out)
54
+ end
55
+
56
+ def method_page(method_name, entries)
57
+ out = +"# #{method_name.sub('.#', '?.')}\n"
58
+ entries.each do |m|
59
+ body = m.source.strip
60
+ out << "\n" << convert_text(body) << "\n" unless body.empty?
61
+ end
62
+ out
63
+ end
64
+
65
+ def doc_page(entry)
66
+ page("# #{entry.title}", entry.source)
67
+ end
68
+
69
+ def function_page(entry)
70
+ page("# #{entry.name}", "### #{entry.header}\n#{entry.source}")
71
+ end
72
+
73
+ # 本文中の参照をコードスパン・コードフェンスを避けて相対 .md リンクへ
74
+ # 変換する
75
+ def convert_text(str)
76
+ out = +''
77
+ fence = nil
78
+ str.each_line do |line|
79
+ if fence
80
+ out << line
81
+ stripped = line.strip
82
+ fence = nil if stripped.match?(/\A`+\z/) && stripped.length >= (fence || 0)
83
+ elsif (m = /\A {0,3}(`{3,})/.match(line))
84
+ fence = (m[1] || raise).length
85
+ out << line
86
+ else
87
+ out << convert_line(line)
88
+ end
89
+ end
90
+ out
91
+ end
92
+
93
+ private
94
+
95
+ def page(heading, source, trailer = "")
96
+ out = +"#{heading}\n"
97
+ body = source.strip
98
+ out << "\n" << convert_text(body) << "\n" unless body.empty?
99
+ out << trailer
100
+ out
101
+ end
102
+
103
+ def class_heading(entry)
104
+ case entry.type
105
+ when :class
106
+ sc = entry.superclass
107
+ sc ? "# class #{entry.name} < #{sc.name}" : "# class #{entry.name}"
108
+ when :module
109
+ "# module #{entry.name}"
110
+ when :object
111
+ "# object #{entry.name}"
112
+ else
113
+ "# #{entry.name}"
114
+ end
115
+ end
116
+
117
+ def convert_line(line)
118
+ spans = [] #: Array[String]
119
+ converted = convert_refs(extract_code_spans(line, spans))
120
+ converted.gsub(/\x00(\d+)\x00/) { spans[($1 || raise).to_i] || raise }
121
+ end
122
+
123
+ # インラインコードスパン(CommonMark 6.1、N 連バッククォートの同長
124
+ # ペアリング)を書かれたままプレースホルダへ退避する
125
+ def extract_code_spans(str, saved)
126
+ result = +''
127
+ i = 0
128
+ len = str.length
129
+ while i < len
130
+ c = str[i] or raise
131
+ if c == '\\' && i + 1 < len
132
+ result << (str[i, 2] || raise)
133
+ i += 2
134
+ next
135
+ end
136
+ if c == '`'
137
+ run = 1
138
+ run += 1 while str[i + run] == '`'
139
+ close = find_code_span_close(str, i + run, run)
140
+ if close
141
+ saved << (str[i...(close + run)] || raise)
142
+ result << "\x00#{saved.size - 1}\x00"
143
+ i = close + run
144
+ next
145
+ end
146
+ result << ('`' * run)
147
+ i += run
148
+ next
149
+ end
150
+ result << c
151
+ i += 1
152
+ end
153
+ result
154
+ end
155
+
156
+ def find_code_span_close(str, from, run_len)
157
+ i = from
158
+ len = str.length
159
+ while i < len
160
+ case str[i]
161
+ when "\n"
162
+ return nil
163
+ when '`'
164
+ j = i
165
+ j += 1 while str[j] == '`'
166
+ return i if j - i == run_len
167
+ i = j
168
+ else
169
+ i += 1
170
+ end
171
+ end
172
+ nil
173
+ end
174
+
175
+ REF_SCHEME_RE = /\A([a-zA-Z][a-zA-Z-]*):(.*)\z/m
176
+
177
+ def convert_refs(str)
178
+ result = +''
179
+ i = 0
180
+ while i < str.length
181
+ if str[i] == '\\' && i + 1 < str.length
182
+ result << (str[i, 2] || raise)
183
+ i += 2
184
+ next
185
+ end
186
+ if str[i] == '['
187
+ close = matching_delimiter(str, i, '[', ']')
188
+ if close
189
+ if str[close + 1] == '(' &&
190
+ (dest_end = matching_delimiter(str, close + 1, '(', ')', space_ends: true)) &&
191
+ (dest = str[(close + 2)...dest_end])
192
+ # ラベル付きリンク [text](dest)
193
+ if (m = REF_SCHEME_RE.match(dest)) && (url = ref_url(m[1] || raise, m[2] || raise))
194
+ result << "[#{str[(i + 1)...close]}](#{url})"
195
+ i = dest_end + 1
196
+ next
197
+ end
198
+ if dest.start_with?('#') || dest.match?(%r{\Ahttps?://})
199
+ # 通常の Markdown リンクはそのまま(ラベル内は再解釈しない)
200
+ result << (str[i..dest_end] || raise)
201
+ i = dest_end + 1
202
+ next
203
+ end
204
+ end
205
+ # 単一ブラケット参照 [type:target]([[ は除外)
206
+ inner = str[(i + 1)...close] || raise
207
+ if str[i + 1] != '[' && (m = REF_SCHEME_RE.match(inner)) &&
208
+ (converted = bare_ref(m[1] || raise, m[2] || raise, inner))
209
+ result << converted
210
+ i = close + 1
211
+ next
212
+ end
213
+ end
214
+ end
215
+ result << (str[i] || raise)
216
+ i += 1
217
+ end
218
+ result
219
+ end
220
+
221
+ # open 位置の括弧に対応する閉じ括弧の位置(\ エスケープ対応・ネスト可)。
222
+ # space_ends: リンク宛先用。空白が現れたらリンクではない(nil)
223
+ def matching_delimiter(str, open, open_char, close_char, space_ends: false)
224
+ depth = 0
225
+ i = open
226
+ while i < str.length
227
+ c = str[i]
228
+ if c == '\\'
229
+ i += 1
230
+ elsif space_ends && c =~ /\s/
231
+ return nil
232
+ elsif c == open_char
233
+ depth += 1
234
+ elsif c == close_char
235
+ depth -= 1
236
+ return i if depth == 0
237
+ end
238
+ i += 1
239
+ end
240
+ nil
241
+ end
242
+
243
+ # 単一ブラケット参照 1 個を Markdown リンクにする。ラベルは書かれた
244
+ # ままの形(エスケープ込み)から type: を落としたもの
245
+ def bare_ref(type, arg, written)
246
+ label =
247
+ case type
248
+ when 'm', 'c', 'lib', 'd', 'f'
249
+ written.split(':', 2)[1]
250
+ when 'url'
251
+ arg
252
+ when 'ref'
253
+ case arg
254
+ when /\A(\w+):(.*)\#([-\w]+)\z/
255
+ "#{$2}##{$3}"
256
+ when /\A[-\w]+\z/
257
+ arg
258
+ end
259
+ end
260
+ return nil unless label
261
+ url = ref_url(type, arg)
262
+ return nil unless url
263
+ "[#{label}](#{url})"
264
+ end
265
+
266
+ # 参照 scheme の宛先 URL(.md への相対リンク)。解決できなければ nil
267
+ def ref_url(type, arg)
268
+ arg = unescape_md_brackets(arg)
269
+ case type
270
+ when 'm'
271
+ # md 表記の module function ?. は内部表記 .# に正規化して引く
272
+ md_suffix(@urlmapper.method_url(arg.sub('?.', '.#')))
273
+ when 'c'
274
+ md_suffix(@urlmapper.class_url(arg))
275
+ when 'lib'
276
+ md_suffix(@urlmapper.library_url(arg))
277
+ when 'd'
278
+ md_suffix(@urlmapper.document_url(arg))
279
+ when 'f'
280
+ md_suffix(@urlmapper.function_url(arg))
281
+ when 'url'
282
+ arg
283
+ when 'ref'
284
+ case arg
285
+ when /\A(\w+):(.*)\#([-\w]+)\z/
286
+ url = ref_url($1 || raise, $2 || raise)
287
+ url ? "#{url}##{$3}" : nil
288
+ when /\A[-\w]+\z/
289
+ "##{arg}"
290
+ end
291
+ end
292
+ rescue StandardError
293
+ nil
294
+ end
295
+
296
+ def md_suffix(html_url)
297
+ return html_url unless html_url.end_with?(@html_suffix)
298
+ "#{html_url[0, html_url.length - @html_suffix.length]}.md"
299
+ end
300
+
301
+ def unescape_md_brackets(str)
302
+ str.gsub(/\\([\[\]\\])/, '\1')
303
+ end
304
+
305
+ def escape_label(text)
306
+ text.gsub(/[\\\[\]]/) { "\\#{$&}" }
307
+ end
308
+
309
+ end
310
+
311
+ end
@@ -126,6 +126,12 @@ module BitClust
126
126
  break
127
127
  end
128
128
  end
129
+ # RBS シグネチャは見出し <dt> 群の直後・説明 <dd> の直前に、この
130
+ # チャンクへ振り分けられたぶんだけ <dt> 行として出す(rd 側
131
+ # entry_chunk と同じ。rbs_sig property が無ければ何も出ない)
132
+ if (rbs_dts = rbs_signature_dts_for_chunk())
133
+ @out.puts rbs_dts
134
+ end
129
135
  @out.puts %Q(<dd class="#{@type.to_s}-description">)
130
136
  undef_message if attrs.include?('undef')
131
137
  while @f.next?
@@ -184,6 +190,12 @@ module BitClust
184
190
  super(sig_line.sub(signature_re, '--- '), first)
185
191
  end
186
192
 
193
+ # RBS シグネチャのチャンク走査(RbsSignatures)用: md のシグネチャ行を
194
+ # rd の正規形にして返す(method_signature と同じ変換)
195
+ def rbs_signature_line(line)
196
+ line.sub(signature_re, '--- ') if signature_re =~ line
197
+ end
198
+
187
199
  def headline(line)
188
200
  hashes = line[/\A#+/] || raise
189
201
  label = line.sub(/\A#+\s*/, '').strip
@@ -405,11 +417,13 @@ module BitClust
405
417
  }
406
418
  end
407
419
 
420
+ # 列揃えはインラインスタイルで出す。align 属性は presentational hint
421
+ # としての扱いになり、テーマ CSS の text-align 指定に負けて効かない
408
422
  def table_row(row, tag, aligns)
409
423
  cells = split_table_row(row)
410
424
  string '<tr>'
411
425
  cells.each_with_index do |cell, i|
412
- align = aligns[i] ? %Q( align="#{aligns[i]}") : ''
426
+ align = aligns[i] ? %Q( style="text-align:#{aligns[i]}") : ''
413
427
  string "<#{tag}#{align}>#{compile_text(cell)}</#{tag}>"
414
428
  end
415
429
  line '</tr>'
@@ -314,11 +314,50 @@ module BitClust
314
314
  src = (header + body).join('')
315
315
  src.location = sig_lines[0].location
316
316
  sigs = sig_lines.map {|line| method_signature(line) }
317
+ # キーワード不一致はより具体的なメッセージになるよう
318
+ # check_chunk_signatures(signature crash)より先に検査する
319
+ check_entry_style(sig_lines, sigs)
317
320
  mainsig = check_chunk_signatures(sigs, sig_lines[0])
318
321
  names = sigs.map {|s| s.name }.compact.uniq.sort
319
322
  Chunk.new(mainsig, names, src)
320
323
  end
321
324
 
325
+ # セクション(h2)の型ごとに期待するエントリキーワード。
326
+ # キーワードは型決定には使われない(型はセクションで決まる)ため、
327
+ # 食い違ったまま書けてしまう分類間違い(rurema/doctree#3291 の
328
+ # 「Class Methods 配下の const」等)をパース時に検出する
329
+ ENTRY_KEYWORD_RE = /\A### (module_function def|def|const|gvar) /
330
+ EXPECTED_ENTRY_KEYWORD = {
331
+ '#' => 'def',
332
+ '.' => 'def',
333
+ '.#' => 'module_function def',
334
+ '::' => 'const',
335
+ '$' => 'gvar',
336
+ }.freeze
337
+
338
+ # エントリの書式とセクションの一致検査。
339
+ # - キーワードがセクションの種別と一致すること
340
+ # - 特異メソッドは `def Klass.name` / `def self.name` のプレフィクス
341
+ # 必須(MARKUP_SPEC §3.1。def 行だけでメソッド種別が分かるようにする)
342
+ def check_entry_style(sig_lines, sigs)
343
+ cxt = @context.signature
344
+ return unless cxt && cxt.type
345
+ expected = EXPECTED_ENTRY_KEYWORD[cxt.type] or return
346
+ sig_lines.zip(sigs).each do |line, sig|
347
+ keyword = (line || raise)[ENTRY_KEYWORD_RE, 1]
348
+ unless keyword == expected
349
+ parse_error "entry keyword `#{keyword}' does not match this section (expected `#{expected}')", line
350
+ end
351
+ if cxt.type == '.' && sig && sig.type.nil?
352
+ parse_error "singleton method entry must be written as `def #{cxt.klass || 'Klass'}.name' or `def self.name'", line
353
+ end
354
+ end
355
+ end
356
+
357
+ # `def self.name` 形式。type は singleton に確定し、klass は
358
+ # 見出しコンテキスト(H1/H2)から補完される
359
+ SELF_SIGNATURE = /\A---\s*self\.(#{METHOD_NAME_RE})/
360
+
322
361
  # md シグネチャ行を rd 形式(--- ...)へ落とし、既存の
323
362
  # Signature パースを継承する。エラー時は元の行を報告する
324
363
  def method_signature(line)
@@ -326,12 +365,14 @@ module BitClust
326
365
  begin
327
366
  # 描画(MethodSignature.parse)が受理できないシグネチャは
328
367
  # statichtml まで進んでから落ちる(か、誤った名前で DB に入る)ので
329
- # パース時に拒否する。RBS 形式(def name:)や self. プレフィクスが該当
368
+ # パース時に拒否する。RBS 形式(def name:)が該当
330
369
  MethodSignature.parse(rd_line)
331
370
  rescue ParseError
332
371
  parse_error "unsupported method signature", line
333
372
  end
334
373
  case
374
+ when m = SELF_SIGNATURE.match(rd_line)
375
+ Signature.new(nil, '.', m[1])
335
376
  when m = SIGNATURE.match(rd_line)
336
377
  klass, typemark_, name = m.captures
337
378
  typemark = _ = typemark_
@@ -10,6 +10,7 @@
10
10
 
11
11
  require 'bitclust/entry'
12
12
  require 'bitclust/exception'
13
+ require 'json'
13
14
 
14
15
  module BitClust
15
16
 
@@ -118,6 +119,11 @@ module BitClust
118
119
  # 現れない前提。fill_since/fill_until で検査する)
119
120
  property :since_by_name, '[String]'
120
121
  property :until_by_name, '[String]'
122
+ # RBS シグネチャ表示。オーバーロード配列(RbsSigImporter 参照)の
123
+ # JSON 1 行を持つ。型シグネチャは ',' を普通に含むので [String] 型
124
+ # (',' 区切り)は使えない。JSON は改行を含まず、property ファイルの
125
+ # key=value 1 行形式は値中の '=' を許す(split('=', 2))ので安全
126
+ property :rbs_sig, 'String'
121
127
  }
122
128
 
123
129
  def inspect
@@ -166,6 +172,24 @@ module BitClust
166
172
  "#{methodid2typechar(@id)}_#{encodename_fs(name).gsub(/=/, '--')}".upcase
167
173
  end
168
174
 
175
+ # rbs_sig(JSON)をオーバーロード配列に戻す。現行形式は
176
+ # {"overloads":[{"segments":..., "params":..., "arity":..., "block":...}]}
177
+ # で、セグメント行列そのものの配列だった旧形式の DB は segments だけの
178
+ # オーバーロード列に読み替える。property が無い古い DB(nil)・
179
+ # シグネチャ無しで保存された空文字列・未保存エントリの "(uninitialized)"
180
+ # センチネル・壊れた JSON はすべて nil(= 表示しない)に落として
181
+ # 描画側を守る
182
+ def rbs_signature_overloads
183
+ json = rbs_sig()
184
+ return nil unless json && (json.start_with?('{') || json.start_with?('['))
185
+ data = JSON.parse(json)
186
+ overloads = data.is_a?(Hash) ? data['overloads']
187
+ : data.map {|segments| {'segments' => segments} }
188
+ overloads.is_a?(Array) && !overloads.empty? ? overloads : nil
189
+ rescue JSON::ParserError
190
+ nil
191
+ end
192
+
169
193
  def labels
170
194
  c, t, _m, _lib = methodid2specparts(@id)
171
195
  names().map {|name| "#{c}#{t}#{name}" }
@@ -21,7 +21,7 @@ module BitClust
21
21
 
22
22
  METHOD_SIGNATURE_RE = /\A
23
23
  --- \s*
24
- (?: (?:#{CLASS_PATH_RE} #{TYPEMARK_RE})? (#{METHOD_NAME_RE})
24
+ (?: (?:#{CLASS_PATH_RE} #{TYPEMARK_RE} | self\.)? (#{METHOD_NAME_RE})
25
25
  | (#{GVAR_RE})
26
26
  ) \s* # method name ($1) or gvar name ($2)
27
27
  (?: \( (.*?) \) \s* )? # parameters (optional); $3=parameter_list