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
@@ -44,7 +44,8 @@ module BitClust
44
44
  sort_key() <=> other.sort_key
45
45
  end
46
46
 
47
- KIND_NUM = {:defined => 0, :redefined => 1, :added => 2}
47
+ KIND_NUM = {:defined => 0, :redefined => 1, :added => 2,
48
+ :undefined => 3, :nomethod => 4}
48
49
 
49
50
  def sort_key
50
51
  [label(), KIND_NUM[kind()]]
@@ -73,6 +74,14 @@ module BitClust
73
74
  methodid2typechar(@id)
74
75
  end
75
76
 
77
+ # bitclust#250: 表示専用の typemark。DB のバージョンが 4.0 以降なら
78
+ # module function の "." を "?." にする(それ以外の typemark は不変)。
79
+ # typemark 自体(識別子として使う方)は変えない -- URL・spec 文字列・
80
+ # refsdatabase のアンカーキー等はすべて typemark() 経由のまま
81
+ def display_typemark
82
+ NameUtils.display_typemark(typemark(), @db.propget('version'))
83
+ end
84
+
76
85
  def type_label
77
86
  case typemark()
78
87
  when '.' then 'singleton method'
@@ -80,6 +89,7 @@ module BitClust
80
89
  when '.#' then 'module function'
81
90
  when '::' then 'constant'
82
91
  when '$' then 'variable'
92
+ else raise "invalid typemark: #{typemark().inspect}"
83
93
  end
84
94
  end
85
95
 
@@ -97,10 +107,17 @@ module BitClust
97
107
 
98
108
  persistent_properties {
99
109
  property :names, '[String]'
100
- property :visibility, 'Symbol' # :public | :private | :protected
101
- property :kind, 'Symbol' # :defined | :added | :redefined
110
+ property :visibility, 'Symbol' ## :public | :private | :protected
111
+ property :kind, 'Symbol' ## :defined | :added | :redefined | :undefined | :nomethod
102
112
  property :source, 'String'
103
113
  property :source_location, 'Location'
114
+ # 名前別の since/until(bitclust#132)。トークンは
115
+ # "#{エンコード名}=#{バージョン}" の配列([String] 型を再利用)で、
116
+ # 生名の ',' '=' は encodename_url でエスケープ済みなので最後の '='
117
+ # がバージョンとの区切りになる(バージョン文字列には '=' も ',' も
118
+ # 現れない前提。fill_since/fill_until で検査する)
119
+ property :since_by_name, '[String]'
120
+ property :until_by_name, '[String]'
104
121
  }
105
122
 
106
123
  def inspect
@@ -109,7 +126,8 @@ module BitClust
109
126
  end
110
127
 
111
128
  def spec
112
- MethodSpec.new(*methodid2specparts(@id))
129
+ c, t, m, lib = methodid2specparts(@id)
130
+ MethodSpec.new(c, t, m, lib)
113
131
  end
114
132
 
115
133
  def spec_string
@@ -121,11 +139,29 @@ module BitClust
121
139
  "#{t == '$' ? '' : c}#{t}#{m}"
122
140
  end
123
141
 
142
+ # bitclust#250: label の表示専用版(module function は 4.0 以降 "?." で
143
+ # 表示)。label 自身は refsdatabase.rb の [[a:...]] アンカーキーや
144
+ # `bitclust methods --diff` の突き合わせが literal ".#" 前提で使うので
145
+ # 変えない -- テンプレート側の実際の表示箇所だけがこちらを呼ぶ
146
+ def display_label
147
+ c, t, m, _lib = methodid2specparts(@id)
148
+ "#{t == '$' ? '' : c}#{display_typemark}#{m}"
149
+ end
150
+
124
151
  def short_label
125
152
  _c, t, m, _lib = methodid2specparts(@id)
126
153
  "#{t == '#' ? '' : t}#{m}"
127
154
  end
128
155
 
156
+ # bitclust#250: short_label の表示専用版。short_label 自身の唯一の
157
+ # 呼び出し元(htmlutils.rb の link_to_method)は display_short_label に
158
+ # 切り替え済みだが、short_label 自体は同じ理由(識別子的な使い方をする
159
+ # 将来のコードのため)で変えない
160
+ def display_short_label
161
+ _c, t, m, _lib = methodid2specparts(@id)
162
+ "#{t == '#' ? '' : display_typemark}#{m}"
163
+ end
164
+
129
165
  def index_id
130
166
  "#{methodid2typechar(@id)}_#{encodename_fs(name).gsub(/=/, '--')}".upcase
131
167
  end
@@ -135,10 +171,79 @@ module BitClust
135
171
  names().map {|name| "#{c}#{t}#{name}" }
136
172
  end
137
173
 
174
+ # Every name of this entry, formatted the same way as #label (i.e.
175
+ # without the redundant class prefix on special variables such as $!,
176
+ # unlike #labels). Used where all aliases of a method need to be listed
177
+ # together, e.g. the <title> of its page.
178
+ def title_labels
179
+ c, t, _m, _lib = methodid2specparts(@id)
180
+ names().map {|name| "#{t == '$' ? '' : c}#{t}#{name}" }
181
+ end
182
+
183
+ # bitclust#250: title_labels の表示専用版(<title> タグ・見出しで使う)
184
+ def display_title_labels
185
+ c, t, _m, _lib = methodid2specparts(@id)
186
+ names().map {|name| "#{t == '$' ? '' : c}#{display_typemark}#{name}" }
187
+ end
188
+
138
189
  def name?(name)
139
190
  names().include?(name)
140
191
  end
141
192
 
193
+ # 名前別 since/until(bitclust#132)。名前ごとに別バージョンで
194
+ # 追加/削除されうる別名(例: alias)を区別して記録する
195
+
196
+ def since_of(name)
197
+ since_map[name]
198
+ end
199
+
200
+ def until_of(name)
201
+ until_map[name]
202
+ end
203
+
204
+ def since_map
205
+ decode_version_tokens(since_by_name)
206
+ end
207
+
208
+ def until_map
209
+ decode_version_tokens(until_by_name)
210
+ end
211
+
212
+ # name の since が未設定の場合のみ version を追加する。追加したら true、
213
+ # 既に値があって何もしなければ false を返す
214
+ def fill_since(name, version)
215
+ fill_version_token(:since_by_name, :since_of, name, version)
216
+ end
217
+
218
+ # fill_since の until 版
219
+ def fill_until(name, version)
220
+ fill_version_token(:until_by_name, :until_of, name, version)
221
+ end
222
+
223
+ private
224
+
225
+ def decode_version_tokens(tokens)
226
+ h = {} #: Hash[String, String]
227
+ tokens.each do |token|
228
+ i = token.rindex('=') or raise "must not happen: malformed version token #{token.inspect}"
229
+ raw_name = decodename_url(token[0...i] || raise)
230
+ h[raw_name] = token[(i + 1)..] || raise
231
+ end
232
+ h
233
+ end
234
+
235
+ def fill_version_token(prop, reader, name, version)
236
+ if version.include?('=') || version.include?(',')
237
+ raise ArgumentError, "version must not contain '=' or ',': #{version.inspect}"
238
+ end
239
+ return false if __send__(reader, name)
240
+ token = "#{encodename_url(name)}=#{version}"
241
+ __send__("#{prop}=", __send__(prop) + [token])
242
+ true
243
+ end
244
+
245
+ public
246
+
142
247
  def name_match?(re)
143
248
  names().any? {|n| re =~ n }
144
249
  end
@@ -148,7 +253,7 @@ module BitClust
148
253
  end
149
254
 
150
255
  def public?
151
- visibility() != :private
256
+ visibility() != :private && visibility() != :protected
152
257
  end
153
258
 
154
259
  def protected?
@@ -172,7 +277,11 @@ module BitClust
172
277
  end
173
278
 
174
279
  def private_instance_method?
175
- instance_method? and public?
280
+ instance_method? and private?
281
+ end
282
+
283
+ def protected_instance_method?
284
+ instance_method? and protected?
176
285
  end
177
286
 
178
287
  def singleton_method?
@@ -209,8 +318,17 @@ module BitClust
209
318
  kind() == :undefined
210
319
  end
211
320
 
321
+ # 説明のためだけに記載されていて実際には定義されていないメソッド
322
+ # (ソースの @nomethod メタデータ行で指定する)
323
+ def nomethod?
324
+ kind() == :nomethod
325
+ end
326
+
212
327
  def description
213
- source.split(/\n\n+/, 3)[1]
328
+ paragraphs = source.split(/\n\n+/).drop(1)
329
+ # {: ...} 属性行や @param などのメタデータ段落は説明文として使わない
330
+ para = paragraphs.find {|p| !p.start_with?('@', '{:') } || paragraphs.first || ''
331
+ description_text(para)
214
332
  end
215
333
  end
216
334
  end
@@ -66,7 +66,8 @@ module BitClust
66
66
  class MethodSpec
67
67
 
68
68
  def MethodSpec.parse(str)
69
- new(*NameUtils.split_method_spec(str))
69
+ c, t, m = NameUtils.split_method_spec(str)
70
+ new(c, t, m)
70
71
  end
71
72
 
72
73
  def initialize(c, t, m, library = nil)
@@ -89,8 +90,13 @@ module BitClust
89
90
  "#{@klass}#{@type}#{@method}"
90
91
  end
91
92
 
92
- def display_name
93
- @type == '$' ? "$#{@method}" : to_s()
93
+ # version はドキュメント対象の Ruby バージョン(DB の version プロパティ)。
94
+ # 指定すると module function typemark を表示用に畳む
95
+ # (4.0 以降は "?."。NameUtils#display_typemark と同じ規則)
96
+ def display_name(version = nil)
97
+ return "$#{@method}" if @type == '$'
98
+ return "#{@klass}#{NameUtils.display_typemark(_ = @type, version)}#{@method}" if version
99
+ to_s()
94
100
  end
95
101
 
96
102
  def ==(other)
@@ -182,15 +188,10 @@ module BitClust
182
188
  private :tesc
183
189
 
184
190
  def match?(m)
185
- (not @library or m.library.name?(@library)) and
186
- (not @klass or m.klass.name?(@klass)) and
191
+ (not @library or m.library.name?(@library || raise)) and
192
+ (not @klass or m.klass.name?(@klass || raise)) and
187
193
  (not @type or m.typemark == @type) and
188
- (not @method or m.name?(@method))
189
- end
190
-
191
- def select_classes(cs)
192
- return cs unless @klass
193
- expand_ic(cs, @klass, @crecache)
194
+ (not @method or m.name?(@method || raise))
194
195
  end
195
196
 
196
197
  def empty?
@@ -33,7 +33,7 @@ module BitClust
33
33
  m = METHOD_SIGNATURE_RE.match(line) or
34
34
  raise ParseError, %Q(unknown signature format: "#{line.strip}")
35
35
  method, gvar, params, block, type = m.captures
36
- new(method || gvar, params && params.strip, block && block.strip, type && type.strip)
36
+ new(method || gvar || raise, params && params.strip, block && block.strip, type && type.strip)
37
37
  end
38
38
 
39
39
  def initialize(name, params, block, type)
@@ -64,9 +64,15 @@ module BitClust
64
64
  when "[]" # aref
65
65
  "self[#{@params}]" + (@type ? " -> #{@type}" : "")
66
66
  when "[]=" # aset
67
- params = @params.split(',')
68
- val = params.pop
69
- "self[#{params.join(',').strip}] = #{val.strip}"
67
+ params = @params&.split(',')
68
+ if params && params.size >= 2
69
+ val = params.pop || raise
70
+ "self[#{params.join(',').strip}] = #{val.strip}"
71
+ else
72
+ # Signatures like []=(*idxary) in historic documents cannot be
73
+ # shaped into "self[...] = ..."; show the raw signature instead
74
+ to_s()
75
+ end
70
76
  when "`" # `command`
71
77
  "`#{@params}`" + (@type ? " -> #{@type}" : "")
72
78
  when /\A\W/ # binary operator
@@ -59,51 +59,55 @@ module BitClust
59
59
  def split_method_spec(spec)
60
60
  case spec
61
61
  when /\AKernel\$/
62
- return 'Kernel', '$', $'
62
+ return ['Kernel', '$', $' || raise]
63
63
  else
64
64
  m = /\A(#{CLASS_PATH_RE})(#{TYPEMARK_RE})(#{METHOD_NAME_RE})\z/o.match(spec) or
65
65
  raise ArgumentError, "wrong method spec: #{spec.inspect}"
66
- return *m.captures
66
+ cname = m[1] || raise
67
+ # @type var tmark: NameUtils::typemark
68
+ tmark = _ = m[2] || raise
69
+ mname = m[3] || raise
70
+ return [cname, tmark, mname]
67
71
  end
68
72
  end
69
73
 
70
74
  def methodid2specstring(id)
71
- c, t, m, _lib = *split_method_id(id)
75
+ c, t, m, _lib = split_method_id(id)
72
76
  classid2name(c) + typechar2mark(t) + decodename_url(m)
73
77
  end
74
78
 
75
79
  def methodid2specparts(id)
76
- c, t, m, lib = *split_method_id(id)
77
- return classid2name(c), typechar2mark(t), decodename_url(m), libid2name(lib)
80
+ c, t, m, lib = split_method_id(id)
81
+ [classid2name(c), typechar2mark(t), decodename_url(m), libid2name(lib)]
78
82
  end
79
83
 
80
84
  def methodid2libid(id)
81
- _c, _t, _m, lib = *split_method_id(id)
85
+ _c, _t, _m, lib = split_method_id(id)
82
86
  lib
83
87
  end
84
88
 
85
89
  def methodid2classid(id)
86
- c, _t, _m, _lib = *split_method_id(id)
90
+ c, _t, _m, _lib = split_method_id(id)
87
91
  c
88
92
  end
89
93
 
90
94
  def methodid2typechar(id)
91
- _c, t, _m, _lib = *split_method_id(id)
95
+ _c, t, _m, _lib = split_method_id(id)
92
96
  t
93
97
  end
94
98
 
95
99
  def methodid2typename(id)
96
- _c, t, _m, _lib = *split_method_id(id)
100
+ _c, t, _m, _lib = split_method_id(id)
97
101
  typechar2name(t)
98
102
  end
99
103
 
100
104
  def methodid2typemark(id)
101
- _c, t, _m, _lib = *split_method_id(id)
105
+ _c, t, _m, _lib = split_method_id(id)
102
106
  typechar2mark(t)
103
107
  end
104
108
 
105
109
  def methodid2mname(id)
106
- _c, _t, m, _lib = *split_method_id(id)
110
+ _c, _t, m, _lib = split_method_id(id)
107
111
  decodename_url(m)
108
112
  end
109
113
 
@@ -121,13 +125,13 @@ module BitClust
121
125
  "#{cid}/#{typename2char(t)}.#{encodename_url(name)}.#{libid}"
122
126
  end
123
127
 
124
- @@split_method_id = {}
128
+ @@split_method_id = {} #: Hash[String, untyped]
125
129
 
126
130
  # private module function
127
131
  def split_method_id(id)
128
132
  @@split_method_id[id] ||= begin
129
133
  c, rest = id.split("/")
130
- [c, *rest.split(%r<[/\.]>, 3)]
134
+ [c, *rest&.split(%r<[/\.]>, 3)]
131
135
  end
132
136
  end
133
137
 
@@ -142,7 +146,7 @@ module BitClust
142
146
  MARK_TO_NAME = NAME_TO_MARK.invert
143
147
 
144
148
  def typename?(n)
145
- NAME_TO_MARK.key?(n)
149
+ NAME_TO_MARK.key?(_ = n)
146
150
  end
147
151
 
148
152
  def typename2mark(name)
@@ -166,7 +170,7 @@ module BitClust
166
170
  CHAR_TO_NAME = NAME_TO_CHAR.invert
167
171
 
168
172
  def typechar?(c)
169
- CHAR_TO_NAME.key?(c)
173
+ CHAR_TO_NAME.key?(_ = c)
170
174
  end
171
175
 
172
176
  def typename2char(name)
@@ -190,7 +194,7 @@ module BitClust
190
194
  CHAR_TO_MARK = MARK_TO_CHAR.invert
191
195
 
192
196
  def typemark?(m)
193
- MARK_TO_CHAR.key?(m)
197
+ MARK_TO_CHAR.key?(_ = m)
194
198
  end
195
199
 
196
200
  def typechar2mark(char)
@@ -203,24 +207,39 @@ module BitClust
203
207
  raise "must not happen: #{mark.inspect}"
204
208
  end
205
209
 
210
+ # bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を
211
+ # るりま独自の ".#" から "?." に変える(表示のみ。ソース側は既に "?."
212
+ # 記法に対応済みで、内部的には従来どおり ".#" に変換される)。
213
+ #
214
+ # これは見た目だけの変換であり、method id・URL・spec 文字列など識別子と
215
+ # して使う ".#" 自体は一切変えない。typemark が '.#' 以外、または version
216
+ # が不明(nil/空)な場合はそのまま返す。バージョン比較は文字列比較では
217
+ # なく Gem::Version で行う("10.0" を文字列比較すると "4.0" より小さく
218
+ # 見えてしまうため)。
219
+ def display_typemark(mark, version)
220
+ return mark unless mark == '.#'
221
+ return mark unless version && !version.empty?
222
+ Gem::Version.new(version) >= Gem::Version.new('4.0') ? '?.' : mark
223
+ end
224
+
206
225
  def functionname?(n)
207
226
  /\A\w+\z/ =~ n ? true : false
208
227
  end
209
228
 
210
229
  # string -> case-sensitive ID
211
230
  def encodename_url(str)
212
- str.gsub(/[^A-Za-z0-9_]/n) {|ch| sprintf('=%02x', ch[0].ord) }
231
+ str.gsub(/[^A-Za-z0-9_]/n) {|ch| sprintf('=%02x', (ch[0] || raise).ord) }
213
232
  end
214
233
 
215
234
  # case-sensitive ID -> string
216
235
  def decodename_url(str)
217
- str.gsub(/=[\da-h]{2}/ni) {|s| s[1,2].hex.chr }
236
+ str.gsub(/=[\da-h]{2}/ni) {|s| (s[1,2] || raise).hex.chr }
218
237
  end
219
238
 
220
239
  # string -> encoded string in a rdoc way
221
240
  def encodename_rdocurl(str)
222
241
  str = str.gsub(/[^A-Za-z0-9_.]/n) {|ch|
223
- sprintf('-%02X', ch[0].ord)
242
+ sprintf('-%02X', (ch[0] || raise).ord)
224
243
  }
225
244
  str.sub(/\A-/, '')
226
245
  end
@@ -232,18 +251,18 @@ module BitClust
232
251
 
233
252
  # encoded string -> case-sensitive ID (decode only [A-Z])
234
253
  def decodeid(str)
235
- str.gsub(/-[a-z]/ni) {|s| s[1,1].upcase }
254
+ str.gsub(/-[a-z]/ni) {|s| (s[1,1] || raise).upcase }
236
255
  end
237
256
 
238
257
  def encodename_fs(str)
239
258
  str.gsub(/[^a-z0-9_]/n) {|ch|
240
- (/[A-Z]/n =~ ch) ? "-#{ch}" : sprintf('=%02x', ch[0].ord)
259
+ (/[A-Z]/n =~ ch) ? "-#{ch}" : sprintf('=%02x', (ch[0] || raise).ord)
241
260
  }.downcase
242
261
  end
243
262
 
244
263
  def decodename_fs(str)
245
264
  str.gsub(/=[\da-h]{2}|-[a-z]/ni) {|s|
246
- (/\A-/ =~ s) ? s[1,1].upcase : s[1,2].hex.chr
265
+ (/\A-/ =~ s) ? (s[1,1] || raise).upcase : (s[1,2] || raise).hex.chr
247
266
  }
248
267
  end
249
268
 
@@ -8,6 +8,7 @@
8
8
  # You can distribute/modify this program under the Ruby License.
9
9
  #
10
10
 
11
+ require 'bitclust/compat'
11
12
  require 'bitclust/parseutils'
12
13
  require 'strscan'
13
14
 
@@ -40,10 +41,15 @@ module BitClust
40
41
 
41
42
 
42
43
  # Handle pragmas like #@todo, #@include, #@since, etc.
44
+ #
45
+ # bitclust#285: prefix は #% が正式(GitHub が #@since 等の @英字 を
46
+ # ユーザー mention として解釈してしまうため)。旧 prefix #@ も、凍結タグ
47
+ # (frozen-*)の旧ソースを再取込する場合に備えて引き続き受け付ける。
43
48
  class Preprocessor < LineFilter
44
49
 
45
50
  def self.read(path, params = {})
46
51
  if path.respond_to?(:gets)
52
+ # @type var path: File
47
53
  io = wrap(path, params)
48
54
  else
49
55
  io = wrap(fopen(path, 'r:UTF-8'), params)
@@ -81,36 +87,48 @@ module BitClust
81
87
  def next_line(f)
82
88
  while line = f.gets
83
89
  case line
84
- when /\A(?!\#@)/
90
+ when /\A(?!\#[@%])/
85
91
  if current_cond.processing?
86
92
  @buf.push line
87
93
  break
88
94
  end
89
- when /\A\#@\#/ # preprocessor comment
95
+ when /\A\#[@%]\#/ # preprocessor comment
90
96
  ;
91
- when /\A\#@todo/i
92
- @buf.push line.gsub(/\A\#/, '') if current_cond.processing?
93
- when /\A\#@include\s*\((.*?)\)/
97
+ when /\A\#[@%]todo/i
98
+ # 出力は prefix によらず @todo に正規化する(下流の表示処理は
99
+ # 従来どおり @todo だけを見ればよい)
100
+ @buf.push line.sub(/\A\#[@%]/, '@') if current_cond.processing?
101
+ when /\A\#[@%]include\s*\((.*?)\)/
94
102
  next unless current_cond.processing?
95
103
  begin
96
- file = $1.strip
97
- basedir = File.dirname(line.location.file)
98
- @buf.concat Preprocessor.process("#{basedir}/#{file}", @params)
104
+ file = ($1 || raise).strip
105
+ basedir = File.dirname(line.location.file || raise)
106
+ path = "#{basedir}/#{file}"
107
+ # md ツリーのネイティブパース: 断片は .md 拡張子付きで保存されるが
108
+ # include ターゲットは元式の名前のまま(拡張子なしの pack-template、
109
+ # .rd 付きの fiddle/2.0/fiddle.lib.rd 等)。md 側の実ファイル名を試す
110
+ unless File.exist?(path)
111
+ cand = ["#{path}.md", path.sub(/\.rd\z/, '.md')].find { |c| File.exist?(c) }
112
+ path = cand if cand
113
+ end
114
+ @buf.concat Preprocessor.process(path, @params)
99
115
  rescue Errno::ENOENT => _err
100
116
  raise WrongInclude, "#{line.location}: \#@include'ed file not exist: #{file}"
101
117
  end
102
- when /\A\#@since\b/
118
+ when /\A\#[@%]since\b/
103
119
  cond_stmt_begin line, build_cond_by_value(line, 'version >=')
104
- when /\A\#@until\b/
120
+ when /\A\#[@%]until\b/
105
121
  cond_stmt_begin line, build_cond_by_value(line, 'version <')
106
- when /\A\#@samplecode\b/
122
+ when /\A\#[@%]version\b/
123
+ cond_stmt_begin line, build_cond_by_range(line)
124
+ when /\A\#[@%]samplecode\b/
107
125
  samplecode_begin(line, samplecode_description_by_value(line))
108
- when /\A\#@if\b/
109
- cond_stmt_begin line, line.sub(/\A\#@if/, '').strip
110
- when /\A\#@else\s*\z/
126
+ when /\A\#[@%]if\b/
127
+ cond_stmt_begin line, line.sub(/\A\#[@%]if/, '').strip
128
+ when /\A\#[@%]else\s*\z/
111
129
  parse_error "no matching \#@if", line if cond_toplevel?
112
130
  cond_invert
113
- when /\A\#@end\s*\z/
131
+ when /\A\#[@%]end\s*\z/
114
132
  if samplecode_processing?
115
133
  samplecode_end
116
134
  else
@@ -123,7 +141,7 @@ module BitClust
123
141
  end
124
142
  if @buf.empty?
125
143
  unless cond_toplevel?
126
- parse_error "unterminated \#@if", @last_if
144
+ parse_error "unterminated \#@if", @last_if || raise
127
145
  end
128
146
  end
129
147
  @buf.shift
@@ -138,8 +156,27 @@ module BitClust
138
156
  end
139
157
  end
140
158
 
159
+ # 版範囲の省略記法(#285): #%version A...B は半開区間 [A, B)。
160
+ # Ruby の終端排他 Range リテラルに合わせて3点ドット(.. は終端を含むと
161
+ # 誤解しやすいため受け付けない)。A... は A 以上、...B は B 未満。
162
+ # 版はダブルクォート付きでもよい
163
+ VERSION_LITERAL = /"(\d+(?:\.\d+)*)"|(\d+(?:\.\d+)*)/
164
+ def build_cond_by_range(line)
165
+ raw = line.sub(/\A\#[@%]version\b/, '').strip
166
+ case raw
167
+ when /\A#{VERSION_LITERAL}\.\.\.#{VERSION_LITERAL}\z/o
168
+ %Q(version >= "#{$1 || $2}" and version < "#{$3 || $4}")
169
+ when /\A#{VERSION_LITERAL}\.\.\.\z/o
170
+ %Q(version >= "#{$1 || $2}")
171
+ when /\A\.\.\.#{VERSION_LITERAL}\z/o
172
+ %Q(version < "#{$1 || $2}")
173
+ else
174
+ parse_error "wrong version range (expected A...B / A... / ...B)", line
175
+ end
176
+ end
177
+
141
178
  def build_cond_by_value(line, left)
142
- case ver = line.sub(/\A\#@\w+/, '').strip
179
+ case ver = line.sub(/\A\#[@%]\w+/, '').strip
143
180
  when /\A[\d\.]+\z/
144
181
  %Q(#{left} "#{ver}")
145
182
  when /\A"[\d\.]+"\z/
@@ -150,7 +187,7 @@ module BitClust
150
187
  end
151
188
 
152
189
  def current_cond
153
- @state_stack.last
190
+ @state_stack.last || raise
154
191
  end
155
192
 
156
193
  def cond_init
@@ -162,13 +199,13 @@ module BitClust
162
199
  end
163
200
 
164
201
  def cond_push(bool)
165
- last = @state_stack.last
202
+ last = @state_stack.last || raise
166
203
  @state_stack.push(last.next(bool, :condition))
167
204
  end
168
205
 
169
206
  def cond_invert
170
- b = @state_stack.pop.processing?
171
- last = @state_stack.last
207
+ b = (@state_stack.pop || raise).processing?
208
+ last = @state_stack.last || raise
172
209
  @state_stack.push(last.next(!b, :condition))
173
210
  end
174
211
 
@@ -229,10 +266,10 @@ module BitClust
229
266
  def eval_primary(s)
230
267
  s.skip(/\s+/)
231
268
  if t = s.scan(/\w+/)
232
- unless @params.key?(t)
269
+ unless @params.key?(t) # steep:ignore
233
270
  scan_error "database property `#{t}' not exist"
234
271
  end
235
- @params[t]
272
+ @params[t] # steep:ignore
236
273
  elsif t = s.scan(/".*?"/)
237
274
  eval(t)
238
275
  elsif t = s.scan(/'.*?'/)
@@ -258,7 +295,7 @@ module BitClust
258
295
  end
259
296
 
260
297
  def samplecode_push(description)
261
- last = @state_stack.last
298
+ last = @state_stack.last || raise
262
299
  @state_stack.push(last.next(true, :samplecode))
263
300
  end
264
301
 
@@ -267,11 +304,11 @@ module BitClust
267
304
  end
268
305
 
269
306
  def samplecode_processing?
270
- @state_stack.last.samplecode?
307
+ (@state_stack.last || raise).samplecode?
271
308
  end
272
309
 
273
310
  def samplecode_description_by_value(line)
274
- line.sub(/\A\#@samplecode/, "")
311
+ line.sub(/\A\#[@%]samplecode/, "")
275
312
  end
276
313
 
277
314
  def scan_error(msg)
@@ -321,10 +358,10 @@ module BitClust
321
358
 
322
359
  def next_line(f)
323
360
  while line = f.gets
324
- if /\A\#@include\s*\((.*?)\)/ =~ line
361
+ if /\A\#[@%]include\s*\((.*?)\)/ =~ line
325
362
  begin
326
- file = $1.strip
327
- basedir = File.dirname(line.location.file)
363
+ file = ($1 || raise).strip
364
+ basedir = File.dirname(line.location.file || raise)
328
365
  @buf.concat LineCollector.process("#{basedir}/#{file}")
329
366
  rescue Errno::ENOENT => _err
330
367
  raise WrongInclude, "#{line.location}: \#@include'ed file not exist: #{file}"