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
@@ -20,7 +20,7 @@ module BitClust
20
20
  @prepare = nil
21
21
  @cleanup = nil
22
22
  @purge = nil
23
- @versions = ["2.4.0", "2.5.0", "2.6.0"]
23
+ @versions = ["3.3.0", "3.4.0", "4.0.0"]
24
24
  @update = true
25
25
  @parser.banner = "Usage: #{File.basename($0, '.*')} setup [options]"
26
26
  @parser.on('--prepare', 'Prepare config file and checkout repository. Do not create database.') {
@@ -40,6 +40,11 @@ module BitClust
40
40
  }
41
41
  end
42
42
 
43
+ # DB は ~/.bitclust 配下に自前で作るのでグローバル --database は不要
44
+ def needs_database?
45
+ false
46
+ end
47
+
43
48
  def exec(argv, options)
44
49
  purge if @purge
45
50
  prepare
@@ -49,18 +54,18 @@ module BitClust
49
54
  prefix = "#{@config[:database_prefix]}-#{version}"
50
55
  FileUtils.rm_rf(prefix) if @cleanup
51
56
  init_argv = ["version=#{version}", "encoding=#{@config[:encoding]}"]
52
- init_options = { :prefix => prefix }
57
+ # @type var init_options: Subcommand::options
58
+ init_options = { :prefix => prefix, :capi => false }
53
59
  InitCommand.new.exec(init_argv, init_options)
54
- update_method_database(prefix, ["--stdlibtree=#{@config[:stdlibtree]}"])
55
- update_argv = Pathname(@config[:capi_src]).children.select(&:file?).map{|v| v.realpath.to_s }
56
- update_function_database(prefix, update_argv)
60
+ update_method_database(prefix, ["--markdowntree=#{@config[:mdtree]}"])
61
+ update_function_database(prefix, ["--markdowntree=#{@config[:capi_mdtree]}"])
57
62
  end
58
63
  end
59
64
 
60
65
  private
61
66
 
62
67
  def purge
63
- home_directory = Pathname(ENV["HOME"])
68
+ home_directory = Pathname(ENV.fetch("HOME"))
64
69
  config_dir = home_directory + ".bitclust"
65
70
  print "Remove all generated files..."
66
71
  FileUtils.rm_rf(config_dir.to_s)
@@ -69,7 +74,7 @@ module BitClust
69
74
  end
70
75
 
71
76
  def prepare
72
- home_directory = Pathname(ENV["HOME"]).expand_path
77
+ home_directory = Pathname(ENV.fetch("HOME")).expand_path
73
78
  config_dir = home_directory + ".bitclust"
74
79
  config_dir.mkpath
75
80
  config_path = config_dir + "config"
@@ -79,17 +84,24 @@ module BitClust
79
84
  :encoding => "utf-8",
80
85
  :versions => @versions,
81
86
  :default_version => @versions.max,
82
- :stdlibtree => (rubydoc_dir + "refm/api/src").to_s,
83
- :capi_src => (rubydoc_dir + "refm/capi/src/").to_s,
87
+ :mdtree => (rubydoc_dir + "manual/api").to_s,
88
+ :capi_mdtree => (rubydoc_dir + "manual/capi").to_s,
84
89
  :baseurl => "http://localhost:10080",
85
90
  :port => "10080",
86
91
  :pid_file => "/tmp/bitclust.pid",
87
92
  }
88
93
  if config_path.exist?
89
94
  @config = YAML.load_file(config_path)
95
+ unless @config[:mdtree]
96
+ # Markdown 移行(2026-07)前の設定ファイル。ソースの取得形態が
97
+ # 変わっているため、作り直しを案内する
98
+ warn "Old config file (before the Markdown migration) found: #{config_path}"
99
+ warn "Run `#{File.basename($0, '.*')} setup --purge` and setup again."
100
+ exit 1
101
+ end
90
102
  unless @config[:versions].sort == @versions.sort
91
103
  print("overwrite config file? > [y/N]")
92
- if /\Ay\z/i =~ $stdin.gets.chomp
104
+ if /\Ay\z/i =~ $stdin.gets&.chomp
93
105
  @config[:versions] = @versions
94
106
  @config[:default_version] = @versions.max
95
107
  generate_config(config_path, @config)
@@ -132,6 +144,7 @@ module BitClust
132
144
  end
133
145
 
134
146
  def update_method_database(prefix, argv)
147
+ # @type var options: Subcommand::options
135
148
  options = {
136
149
  :prefix => prefix,
137
150
  :capi => false,
@@ -142,6 +155,7 @@ module BitClust
142
155
  end
143
156
 
144
157
  def update_function_database(prefix, argv)
158
+ # @type var options: Subcommand::options
145
159
  options = {
146
160
  :prefix => prefix,
147
161
  :capi => true,
@@ -12,6 +12,7 @@ require 'bitclust/nameutils'
12
12
  require 'bitclust/subcommand'
13
13
  require 'bitclust/progress_bar'
14
14
  require 'bitclust/silent_progress_bar'
15
+ require 'bitclust/search_index_generator'
15
16
 
16
17
  module BitClust
17
18
  module Subcommands
@@ -45,7 +46,7 @@ module BitClust
45
46
  end
46
47
 
47
48
  def method_url(spec)
48
- cname, tmark, mname = *split_method_spec(spec)
49
+ cname, tmark, mname = split_method_spec(spec)
49
50
  filename = html_filename(encodename_package(mname), @suffix)
50
51
  @bitclust_html_base +
51
52
  "/method/#{encodename_package(cname)}/#{typemark2char(tmark)}/#{filename}"
@@ -90,7 +91,7 @@ module BitClust
90
91
  end
91
92
 
92
93
  def edit_url(location)
93
- "#{@edit_base_url}/#{location.file}#L#{location.line}".sub(/([^:])\/\/+/, "\\1/")
94
+ "#{@edit_base_url}/#{location.file}".sub(/([^:])\/\/+/, "\\1/")
94
95
  end
95
96
 
96
97
  def encodename_package(str)
@@ -120,6 +121,10 @@ module BitClust
120
121
  @gtm_tracking_id = nil
121
122
  @meta_robots_content = ["noindex"]
122
123
  @stop_on_syntax_error = true
124
+ @eol_warning = false
125
+ @run_ruby_wasm = nil
126
+ @sitemap_baseurl = nil
127
+ @sitemap_paths = []
123
128
  @parser.banner = "Usage: #{File.basename($0, '.*')} statichtml [options]"
124
129
  @parser.on('-o', '--outputdir=PATH', 'Output directory') do |path|
125
130
  begin
@@ -156,6 +161,15 @@ module BitClust
156
161
  @parser.on('--meta-robots-content=VALUE1,VALUE2,...', Array, 'HTML <meta> element: <meta name="robots" content="VALUE1,VALUE2..."') do |values|
157
162
  @meta_robots_content = values
158
163
  end
164
+ @parser.on('--[no-]eol-warning', 'Show a warning banner that this Ruby version is no longer maintained') do |boolean|
165
+ @eol_warning = boolean
166
+ end
167
+ @parser.on('--run-ruby-wasm=WASM_URL', 'Add a RUN button to Ruby sample code, executing it in-browser with the given ruby.wasm') do |url|
168
+ @run_ruby_wasm = url
169
+ end
170
+ @parser.on('--sitemap-baseurl=URL', 'Generate sitemap.xml under the output directory, with <loc> built from this base URL (e.g. https://docs.ruby-lang.org/ja/3.4/). Omit to skip sitemap.xml generation (default)') do |url|
171
+ @sitemap_baseurl = url
172
+ end
159
173
  @parser.on('--no-stop-on-syntax-error', 'Do not stop on syntax error') do |boolean|
160
174
  @stop_on_syntax_error = boolean
161
175
  end
@@ -173,7 +187,7 @@ module BitClust
173
187
  manager = ScreenManager.new(@manager_config)
174
188
 
175
189
  db.transaction do
176
- methods = {}
190
+ methods = {} #: Hash[String, Array[MethodEntry]]
177
191
  db.methods.each_with_index do |entry, i|
178
192
  next if entry.undefined?
179
193
 
@@ -193,22 +207,33 @@ module BitClust
193
207
  end
194
208
 
195
209
  @urlmapper.bitclust_html_base = '..'
196
- create_file(@outputdir + "library/#{html_filename("index", @suffix)}",
210
+ library_index_path = @outputdir + "library/#{html_filename("index", @suffix)}"
211
+ create_file(library_index_path,
197
212
  manager.library_index_screen(db.libraries.sort, {:database => db}).body,
198
213
  :verbose => @verbose)
199
- create_file(@outputdir + "class/#{html_filename("index", @suffix)}",
214
+ record_sitemap_path(library_index_path)
215
+ class_index_path = @outputdir + "class/#{html_filename("index", @suffix)}"
216
+ create_file(class_index_path,
200
217
  manager.class_index_screen(db.classes.sort, {:database => db}).body,
201
218
  :verbose => @verbose)
202
- create_file(@outputdir + "function/#{html_filename("index", @suffix)}",
219
+ record_sitemap_path(class_index_path)
220
+ function_index_path = @outputdir + "function/#{html_filename("index", @suffix)}"
221
+ create_file(function_index_path,
203
222
  manager.function_index_screen(fdb.functions.sort, { :database => fdb }).body,
204
223
  :verbose => @verbose)
224
+ record_sitemap_path(function_index_path)
205
225
  create_index_html(@outputdir)
226
+ create_search_index(@outputdir, db, fdb)
227
+ if baseurl = @sitemap_baseurl
228
+ create_sitemap(@outputdir, baseurl)
229
+ end
206
230
  FileUtils.cp(@manager_config[:themedir] + @manager_config[:css_url],
207
231
  @outputdir.to_s, :verbose => @verbose, :preserve => true)
208
232
  FileUtils.cp(@manager_config[:themedir] + "syntax-highlight.css",
209
233
  @outputdir.to_s, :verbose => @verbose, :preserve => true)
210
234
  FileUtils.cp(@manager_config[:themedir] + "script.js",
211
235
  @outputdir.to_s, :verbose => @verbose, :preserve => true)
236
+ copy_run_ruby_wasm_script if @run_ruby_wasm
212
237
  FileUtils.cp(@manager_config[:themedir] + @manager_config[:favicon_url],
213
238
  @outputdir.to_s, :verbose => @verbose, :preserve => true)
214
239
  Dir.mktmpdir do |tmpdir|
@@ -240,6 +265,8 @@ module BitClust
240
265
  :gtm_tracking_id => @gtm_tracking_id,
241
266
  :meta_robots_content => @meta_robots_content,
242
267
  :stop_on_syntax_error => @stop_on_syntax_error,
268
+ :eol_warning => @eol_warning,
269
+ :run_ruby_wasm => @run_ruby_wasm,
243
270
  }
244
271
  @manager_config[:urlmapper] = URLMapperEx.new(@manager_config)
245
272
  @urlmapper = @manager_config[:urlmapper]
@@ -290,6 +317,101 @@ HERE
290
317
  }
291
318
  end
292
319
 
320
+ SEARCH_JS_FILES = %w[
321
+ search_navigation.js search_ranker.js search_controller.js search_init.js
322
+ ].freeze
323
+
324
+ def create_search_index(outputdir, db, fdb)
325
+ generator = SearchIndexGenerator.new(suffix: @suffix,
326
+ fs_casesensitive: @fs_casesensitive)
327
+ jsdir = outputdir + "js"
328
+ FileUtils.mkdir_p(jsdir) unless jsdir.directory?
329
+ create_file(jsdir + "search_data.js",
330
+ generator.to_js(db, fdb),
331
+ :verbose => @verbose)
332
+ themedir = @manager_config[:themedir]
333
+ SEARCH_JS_FILES.each do |js|
334
+ FileUtils.cp(themedir + "js" + js, jsdir.to_s,
335
+ :verbose => @verbose, :preserve => true)
336
+ end
337
+ # Ship the MIT notice for the vendored Aliki files alongside them.
338
+ FileUtils.cp(themedir + "js" + "NOTICE", jsdir.to_s,
339
+ :verbose => @verbose, :preserve => true)
340
+ FileUtils.cp(themedir + "search.css", outputdir.to_s,
341
+ :verbose => @verbose, :preserve => true)
342
+ end
343
+
344
+ # A single sitemap.xml file supports at most 50,000 URLs
345
+ # (https://www.sitemaps.org/protocol.html#index). Splitting into a
346
+ # sitemap index is out of scope for now (small start); if the site
347
+ # grows past this, only the first MAX_SITEMAP_URLS pages are listed
348
+ # and a warning is printed.
349
+ MAX_SITEMAP_URLS = 50_000
350
+
351
+ # The 5 characters that need escaping to embed arbitrary text as XML
352
+ # character data (used for the <loc> contents below).
353
+ XML_ESCAPES = {
354
+ '&' => '&amp;',
355
+ '<' => '&lt;',
356
+ '>' => '&gt;',
357
+ '"' => '&quot;',
358
+ "'" => '&apos;',
359
+ }.freeze
360
+
361
+ def escape_xml(str)
362
+ str.gsub(/[&<>"']/) {|c| XML_ESCAPES.fetch(c) }
363
+ end
364
+
365
+ # Remember +path+ (an HTML page actually written under @outputdir) so
366
+ # that create_sitemap can list it later. A no-op unless
367
+ # --sitemap-baseurl was given, so sitemap generation has no effect on
368
+ # the rest of the run when the option is not used.
369
+ def record_sitemap_path(path)
370
+ return unless @sitemap_baseurl
371
+ @sitemap_paths << path.relative_path_from(@outputdir).to_s
372
+ end
373
+
374
+ # Generate outputdir/sitemap.xml: a plain XML sitemap
375
+ # (https://www.sitemaps.org/protocol.html) listing every page
376
+ # recorded via record_sitemap_path, as baseurl + relative path.
377
+ def create_sitemap(outputdir, baseurl)
378
+ paths = @sitemap_paths
379
+ if paths.size > MAX_SITEMAP_URLS
380
+ $stderr.puts "warning: #{paths.size} pages found, but a single sitemap.xml supports at most #{MAX_SITEMAP_URLS} URLs; only the first #{MAX_SITEMAP_URLS} are included. Consider a sitemap index if you need the rest."
381
+ paths = paths.first(MAX_SITEMAP_URLS)
382
+ end
383
+ base = baseurl.end_with?('/') ? baseurl : "#{baseurl}/"
384
+ xml = +%(<?xml version="1.0" encoding="UTF-8"?>\n)
385
+ xml << %(<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n)
386
+ paths.each do |path|
387
+ xml << "<url><loc>#{escape_xml(base + path)}</loc></url>\n"
388
+ end
389
+ xml << "</urlset>\n"
390
+ create_file(outputdir + "sitemap.xml", xml, :verbose => @verbose)
391
+ end
392
+
393
+ # Scripts for the RUN-button feature (statichtml --run-ruby-wasm):
394
+ # run.js sets up the button and compiles the wasm module on the main
395
+ # thread; run-worker.js is the module Worker it spawns per execution
396
+ # (see theme/default/js/run.js for why: STOP/timeout both just
397
+ # Worker#terminate() it). Both are required for the feature to work.
398
+ RUN_RUBY_WASM_JS_FILES = %w[run.js run-worker.js].freeze
399
+
400
+ # Copy the RUN-button scripts. A themedir missing one of them is
401
+ # tolerated: warn and skip instead of aborting the whole build.
402
+ def copy_run_ruby_wasm_script
403
+ jsdir = @outputdir + "js"
404
+ RUN_RUBY_WASM_JS_FILES.each do |name|
405
+ src = @manager_config[:themedir] + "js" + name
406
+ unless src.file?
407
+ $stderr.puts "warning: #{src} not found; RUN button script not copied"
408
+ next
409
+ end
410
+ FileUtils.mkdir_p(jsdir) unless jsdir.directory?
411
+ FileUtils.cp(src.to_s, jsdir.to_s, :verbose => @verbose, :preserve => true)
412
+ end
413
+ end
414
+
293
415
  def create_html_file(entry, manager, outputdir, db)
294
416
  e = entry.is_a?(Array) ? entry.sort.first : entry
295
417
  case e.type_id
@@ -328,6 +450,7 @@ HERE
328
450
  path.open('w') do |f|
329
451
  f.write(html)
330
452
  end
453
+ record_sitemap_path(path)
331
454
  end
332
455
 
333
456
  def create_file(path, str, options = {})
@@ -16,11 +16,15 @@ module BitClust
16
16
  def initialize
17
17
  super
18
18
  @root = nil
19
+ @markdowntree = nil
19
20
  @library = nil
20
21
  @parser.banner = "Usage: #{File.basename($0, '.*')} update [<file>...]"
21
22
  @parser.on('--stdlibtree=ROOT', 'Process stdlib source directory tree.') {|path|
22
23
  @root = path
23
24
  }
25
+ @parser.on('--markdowntree=ROOT', 'Process Markdown source directory tree.') {|path|
26
+ @markdowntree = path
27
+ }
24
28
  @parser.on('--library-name=NAME', 'Use NAME for library name in file mode.') {|name|
25
29
  @library = name
26
30
  }
@@ -28,25 +32,129 @@ module BitClust
28
32
 
29
33
  def parse(argv)
30
34
  super
31
- if not @root and argv.empty?
35
+ if not @root and not @markdowntree and argv.empty?
32
36
  error "no input file given"
33
37
  end
34
38
  end
35
39
 
36
40
  def exec(argv, options)
37
41
  super
42
+ if @markdowntree
43
+ # ネイティブ md パース(M3)。source には md が入り、描画は
44
+ # MDCompiler(GFM)が選択される。ブリッジは検証用に温存
45
+ # (BITCLUST_MD_BRIDGE=1 で旧経路)
46
+ if ENV['BITCLUST_MD_BRIDGE'] == '1'
47
+ argv = @db.is_a?(FunctionDatabase) ? prepare_capi_markdowntree
48
+ : (prepare_markdowntree; argv)
49
+ else
50
+ @db.transaction {
51
+ @db.update_by_markdowntree(@markdowntree || raise)
52
+ }
53
+ return
54
+ end
55
+ end
38
56
  @db.transaction {
39
57
  if @root
40
- @db.update_by_stdlibtree @root
58
+ db = @db
59
+ db.is_a?(MethodDatabase) or raise
60
+ db.update_by_stdlibtree(@root || raise)
41
61
  end
42
62
  argv.each do |path|
43
63
  @db.update_by_file path, @library || guess_library_name(path)
44
64
  end
45
65
  }
66
+ remap_source_locations if @markdowntree
67
+ ensure
68
+ FileUtils.remove_entry(@bridge_dir) if @bridge_dir
46
69
  end
47
70
 
48
71
  private
49
72
 
73
+ # Markdown ツリーを旧形式の rd ツリーへブリッジし、既存の
74
+ # stdlibtree 更新機構にそのまま渡す。
75
+ # doc(散文ページ)は stdlibtree/../../doc から読まれるため、
76
+ # md ツリー側に doc/ が同居していればレイアウトを合わせる
77
+ # (manual/api を渡すと manual/doc を拾う。md ならブリッジ変換、
78
+ # .rd のまま(移行中)なら symlink)。
79
+ def prepare_markdowntree
80
+ require 'tmpdir'
81
+ require 'bitclust/markdown_bridge'
82
+ @bridge_dir = Dir.mktmpdir('bitclust-md-bridge')
83
+ root = File.join(@bridge_dir, 'api/src')
84
+ bridge = MarkdownBridge.build(@markdowntree || raise, root)
85
+ @location_map = { root => [@markdowntree, bridge.source_map] }
86
+ # markdowntree と同じ渡され方(相対/絶対)を保った隣接 doc パス。
87
+ # copy_doc は stdlibtree 相対(../../doc/...)の location を記録する
88
+ doc = File.join(File.dirname(@markdowntree || raise), 'doc')
89
+ if File.directory?(doc)
90
+ if Dir.glob(File.join(doc, '**/*.md')).any?
91
+ doc_map = MarkdownBridge.build_doc(doc, File.join(@bridge_dir, 'doc'))
92
+ else
93
+ FileUtils.ln_s(File.expand_path(doc), File.join(@bridge_dir, 'doc'))
94
+ doc_map = nil
95
+ end
96
+ @location_map[File.join(@bridge_dir, 'doc')] = [doc, doc_map]
97
+ @location_map['../../doc'] = [doc, doc_map]
98
+ end
99
+ @root ||= root
100
+ end
101
+
102
+ # C API の Markdown ツリー(manual/capi)を旧形式の .rd 群へ変換し、
103
+ # ファイル引数として返す(--capi update <files> の経路に載せる)
104
+ def prepare_capi_markdowntree
105
+ require 'tmpdir'
106
+ require 'bitclust/markdown_to_rrd'
107
+ @bridge_dir = Dir.mktmpdir('bitclust-capi-bridge')
108
+ # src/ 配下に置く: guess_library_name が src/ 以降を id とするため
109
+ # (関数の filename が旧経路と同じ「eval.c」等になる)
110
+ src = File.join(@bridge_dir, 'src')
111
+ FileUtils.mkdir_p(src)
112
+ capi_map = {} #: Hash[String, String]
113
+ files = Dir.glob(File.join(@markdowntree || raise, '*.md')).sort.map do |f|
114
+ name = "#{File.basename(f, '.md')}.rd"
115
+ rd = File.join(src, name)
116
+ File.write(rd, MarkdownToRRD.convert(File.read(f), capi: true))
117
+ capi_map[name] = "#{File.basename(f, '.md')}.md"
118
+ rd
119
+ end
120
+ @location_map = { src => [@markdowntree, capi_map] }
121
+ files
122
+ end
123
+
124
+ # source_location のブリッジ一時パスを manual/ の md パスへ再マップする。
125
+ # 編集リンク(statichtml --edit-base-url)が凍結 refm でなく編集対象の
126
+ # md を指すようにするため。行番号はブリッジ rd 基準の近似で、
127
+ # front matter 等の分だけ md とずれることがある
128
+ def remap_source_locations
129
+ db = @db
130
+ entries =
131
+ if db.is_a?(FunctionDatabase)
132
+ db.functions
133
+ else
134
+ db.is_a?(MethodDatabase) or raise
135
+ db.libraries + db.classes + db.methods + db.docs
136
+ end
137
+ entries.each do |entry|
138
+ loc = entry.source_location or next
139
+ remapped = remap_location(loc) or next
140
+ entry.source_location = remapped
141
+ entry.save
142
+ end
143
+ end
144
+
145
+ def remap_location(loc)
146
+ file = loc.file
147
+ file.is_a?(String) or raise
148
+ @location_map.each do |bridge_root, (md_root, map)|
149
+ prefix = "#{bridge_root}/"
150
+ next unless file.start_with?(prefix)
151
+ rel = file.delete_prefix(prefix)
152
+ rel = map[rel] || rel if map
153
+ return Location.new(File.join(md_root, rel), loc.line)
154
+ end
155
+ nil
156
+ end
157
+
50
158
  def guess_library_name(path)
51
159
  if %r<(\A|/)src/> =~ path
52
160
  path.sub(%r<.*(\A|/)src/>, '').sub(/\.rd\z/, '')
@@ -56,6 +56,7 @@ module BitClust
56
56
  nl: nil, # \n
57
57
  op: "o", # operator
58
58
  period: "p", # .
59
+ qsymbols_beg: "ss", # %i(
59
60
  qwords_beg: "sx", # %w(
60
61
  rbrace: "p", # }
61
62
  rbracket: "p", # ]
@@ -65,6 +66,7 @@ module BitClust
65
66
  semicolon: nil, # ;
66
67
  sp: nil, # space
67
68
  symbeg: "ss", # :
69
+ symbols_beg: "ss", # %I(
68
70
  tlambda: "o", # ->
69
71
  tlambeg: "p", # (->) {
70
72
  tstring_beg: nil, # " (string")
@@ -131,7 +133,7 @@ module BitClust
131
133
  end
132
134
 
133
135
  def on_default(event, token, data)
134
- event_name = event.to_s.sub(/\Aon_/, "") # :on_event --> "event"
136
+ event_name = event.to_s.sub(/\Aon_/, "") ## :on_event --> "event"
135
137
  style = COLORS[event_name.to_sym]
136
138
  escaped_token = escape_html(token)
137
139
  data << (style ? "<span class=\"#{style}\">#{escaped_token}</span>" : escaped_token)
@@ -181,6 +183,9 @@ module BitClust
181
183
  @name_buffer << token
182
184
  when @stack.last == :module
183
185
  @name_buffer << token
186
+ when @stack.last == :symbol
187
+ data << "#{token}</span>"
188
+ @stack.pop
184
189
  else
185
190
  on_default(:on_const, token, data)
186
191
  end
@@ -216,8 +221,11 @@ module BitClust
216
221
 
217
222
  def on_op(token, data)
218
223
  case
219
- when token == "::" && [:class, :module].include?(@stack.last)
224
+ when token == "::" && [:class, :module].include?(_ = @stack.last)
220
225
  @name_buffer << token
226
+ when token == "<<" && @stack.last == :class
227
+ @stack.pop
228
+ on_default(:on_op, token, data)
221
229
  else
222
230
  @stack.pop if @stack.last == :method_call
223
231
  on_default(:on_op, token, data)
@@ -269,6 +277,28 @@ module BitClust
269
277
  on_default(:on_nl, token, data)
270
278
  end
271
279
 
280
+ def on_semicolon(token, data)
281
+ case
282
+ when @name_buffer.empty?
283
+ return on_default(:on_semicolon, token, data)
284
+ when @stack.last == :module
285
+ name = @name_buffer.join
286
+ data << "<span class=\"nn\">#{name}</span>"
287
+ @stack.pop
288
+ @name_buffer.clear
289
+ when @stack.last == :class
290
+ namespace = @name_buffer.values_at(0..-3).join
291
+ operator = @name_buffer[-2]
292
+ name = @name_buffer.last
293
+ data << "<span class=\"nn\">#{namespace}</span>"
294
+ data << "<span class=\"o\">#{operator}</span>"
295
+ data << "<span class=\"nc\">#{name}</span>"
296
+ @stack.pop
297
+ @name_buffer.clear
298
+ end
299
+ on_default(:on_semicolon, token, data)
300
+ end
301
+
272
302
  def on_regexp_beg(token, data)
273
303
  style = COLORS[:regexp_beg]
274
304
  data << "<span class=\"#{style}\">#{token}"
@@ -316,7 +346,7 @@ module BitClust
316
346
  case
317
347
  when token == "'"
318
348
  data << "#{token}</span>"
319
- when [:qwords, :words].include?(@stack.last)
349
+ when %i[qwords words qsymbols symbols].include?(_ = @stack.last)
320
350
  @stack.pop
321
351
  data << "#{token}</span>"
322
352
  else
@@ -340,6 +370,20 @@ module BitClust
340
370
  data
341
371
  end
342
372
 
373
+ def on_qsymbols_beg(token, data)
374
+ @stack.push(:qsymbols)
375
+ style = COLORS[:qsymbols_beg]
376
+ data << "<span class=\"#{style}\">#{token}"
377
+ data
378
+ end
379
+
380
+ def on_symbols_beg(token, data)
381
+ @stack.push(:symbols)
382
+ style = COLORS[:symbols_beg]
383
+ data << "<span class=\"#{style}\">#{token}"
384
+ data
385
+ end
386
+
343
387
  def on_heredoc_beg(token, data)
344
388
  @stack.push(:heredoc)
345
389
  on_default(:on_heredoc_beg, token, data)
@@ -363,7 +407,7 @@ module BitClust
363
407
  def on___end__(token, data)
364
408
  on_default(:on___end__, token, data)
365
409
  style = COLORS[:comment]
366
- data << "<span class=\"#{style}\">#{escape_html(@src.lines[lineno..-1].join)}</span>"
410
+ data << "<span class=\"#{style}\">#{escape_html((@src.lines[lineno..-1] || raise).join)}</span>"
367
411
  data
368
412
  end
369
413
 
@@ -18,7 +18,7 @@ module BitClust
18
18
  def detab(str, ts = 8)
19
19
  add = 0
20
20
  str.gsub(/\t/) {
21
- len = ts - ($~.begin(0) + add) % ts
21
+ len = ts - ((($~ || raise).begin(0) || raise) + add) % ts
22
22
  add += len - 1
23
23
  ' ' * len
24
24
  }
@@ -30,11 +30,11 @@ module BitClust
30
30
  end
31
31
 
32
32
  def n_minimum_indent(lines)
33
- lines.reject {|line| line.strip.empty? }.map {|line| n_indent(line) }.min
33
+ lines.reject {|line| line.strip.empty? }.map {|line| n_indent(line) }.min || raise
34
34
  end
35
35
 
36
36
  def n_indent(line)
37
- line.slice(/\A\s*/).size
37
+ (line.slice(/\A\s*/) || raise).size
38
38
  end
39
39
 
40
40
  INDENT_RE = {
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module BitClust
3
- VERSION = "1.3.0"
3
+ VERSION = "1.6.0"
4
4
  end