bitclust-core 1.2.6 → 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.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/README.md +65 -0
- data/Rakefile +32 -0
- data/data/bitclust/catalog/ja_JP.UTF-8 +19 -1
- data/data/bitclust/searchpage/index.html +90 -0
- data/data/bitclust/template/class +69 -8
- data/data/bitclust/template/function +2 -2
- data/data/bitclust/template/layout +11 -3
- data/data/bitclust/template/method +2 -2
- data/data/bitclust/template/search +1 -1
- data/data/bitclust/template.epub/class +22 -1
- data/data/bitclust/template.epub/function +2 -2
- data/data/bitclust/template.epub/layout +1 -3
- data/data/bitclust/template.epub/method +2 -2
- data/data/bitclust/template.lillia/class +26 -5
- data/data/bitclust/template.lillia/layout +1 -3
- data/data/bitclust/template.lillia/method +2 -2
- data/data/bitclust/template.offline/class +49 -5
- data/data/bitclust/template.offline/function +2 -2
- data/data/bitclust/template.offline/layout +27 -3
- data/data/bitclust/template.offline/method +2 -2
- data/lib/bitclust/app.rb +17 -17
- data/lib/bitclust/capi_converter.rb +20 -0
- data/lib/bitclust/classentry.rb +68 -17
- data/lib/bitclust/compat.rb +3 -2
- data/lib/bitclust/completion.rb +59 -27
- data/lib/bitclust/crossrubyutils.rb +9 -5
- data/lib/bitclust/database.rb +29 -8
- data/lib/bitclust/doc_converter.rb +63 -0
- data/lib/bitclust/docentry.rb +2 -1
- data/lib/bitclust/entity_splitter.rb +221 -0
- data/lib/bitclust/entry.rb +48 -7
- data/lib/bitclust/exception.rb +1 -0
- data/lib/bitclust/functiondatabase.rb +14 -1
- data/lib/bitclust/functionentry.rb +6 -1
- data/lib/bitclust/functionreferenceparser.rb +5 -4
- data/lib/bitclust/generators/epub.rb +11 -3
- data/lib/bitclust/htmlutils.rb +2 -1
- data/lib/bitclust/include_graph.rb +375 -0
- data/lib/bitclust/include_pruner.rb +144 -0
- data/lib/bitclust/interface.rb +2 -1
- data/lib/bitclust/libraryentry.rb +9 -7
- data/lib/bitclust/lineinput.rb +8 -5
- data/lib/bitclust/link_checker.rb +179 -0
- data/lib/bitclust/markdown_bridge.rb +186 -0
- data/lib/bitclust/markdown_orchestrator.rb +267 -0
- data/lib/bitclust/markdown_to_rrd.rb +824 -0
- data/lib/bitclust/markdown_tree.rb +217 -0
- data/lib/bitclust/mdcompiler.rb +883 -0
- data/lib/bitclust/mdparser.rb +347 -0
- data/lib/bitclust/messagecatalog.rb +4 -3
- data/lib/bitclust/method_since_calculator.rb +117 -0
- data/lib/bitclust/methoddatabase.rb +125 -14
- data/lib/bitclust/methodentry.rb +130 -7
- data/lib/bitclust/methodid.rb +13 -11
- data/lib/bitclust/methodsignature.rb +11 -4
- data/lib/bitclust/nameutils.rb +42 -22
- data/lib/bitclust/parseutils.rb +1 -0
- data/lib/bitclust/preprocessor.rb +70 -32
- data/lib/bitclust/progress_bar.rb +1 -0
- data/lib/bitclust/rdcompiler.rb +197 -96
- data/lib/bitclust/refsdatabase.rb +24 -8
- data/lib/bitclust/reloadable_request_handler.rb +72 -0
- data/lib/bitclust/requesthandler.rb +22 -16
- data/lib/bitclust/ridatabase.rb +11 -4
- data/lib/bitclust/rrd_to_markdown.rb +925 -0
- data/lib/bitclust/rrdparser.rb +197 -30
- data/lib/bitclust/runner.rb +18 -2
- data/lib/bitclust/screen.rb +81 -11
- data/lib/bitclust/search_index_generator.rb +288 -0
- data/lib/bitclust/searcher.rb +72 -22
- data/lib/bitclust/server.rb +6 -1
- data/lib/bitclust/silent_progress_bar.rb +1 -0
- data/lib/bitclust/simplesearcher.rb +25 -12
- data/lib/bitclust/subcommand.rb +8 -1
- data/lib/bitclust/subcommands/ancestors_command.rb +10 -2
- data/lib/bitclust/subcommands/checklink_command.rb +54 -0
- data/lib/bitclust/subcommands/chm_command.rb +16 -7
- data/lib/bitclust/subcommands/classes_command.rb +6 -0
- data/lib/bitclust/subcommands/epub_command.rb +1 -0
- data/lib/bitclust/subcommands/extract_command.rb +6 -0
- data/lib/bitclust/subcommands/htmlfile_command.rb +22 -7
- data/lib/bitclust/subcommands/init_command.rb +5 -1
- data/lib/bitclust/subcommands/list_command.rb +13 -4
- data/lib/bitclust/subcommands/lookup_command.rb +29 -9
- data/lib/bitclust/subcommands/methods_command.rb +11 -2
- data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
- data/lib/bitclust/subcommands/preproc_command.rb +6 -0
- data/lib/bitclust/subcommands/property_command.rb +3 -1
- data/lib/bitclust/subcommands/query_command.rb +1 -0
- data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
- data/lib/bitclust/subcommands/server_command.rb +16 -10
- data/lib/bitclust/subcommands/setup_command.rb +25 -10
- data/lib/bitclust/subcommands/statichtml_command.rb +132 -6
- data/lib/bitclust/subcommands/update_command.rb +111 -2
- data/lib/bitclust/syntax_highlighter.rb +52 -5
- data/lib/bitclust/textutils.rb +4 -3
- data/lib/bitclust/version.rb +2 -1
- data/lib/bitclust/version_badges.rb +90 -0
- data/lib/bitclust/whole_file_gate.rb +137 -0
- data/lib/bitclust.rb +1 -0
- data/test/test_app.rb +269 -0
- data/test/test_bitclust.rb +2 -0
- data/test/test_capi_converter.rb +91 -0
- data/test/test_class_screen.rb +413 -0
- data/test/test_copy_doc_md.rb +53 -0
- data/test/test_doc_converter.rb +78 -0
- data/test/test_entity_splitter.rb +231 -0
- data/test/test_entry.rb +207 -2
- data/test/test_eol_warning.rb +43 -0
- data/test/test_functionreferenceparser.rb +11 -0
- data/test/test_include_graph.rb +640 -0
- data/test/test_include_pruner.rb +133 -0
- data/test/test_link_checker.rb +114 -0
- data/test/test_lookup_command.rb +135 -0
- data/test/test_markdown_bridge.rb +217 -0
- data/test/test_markdown_orchestrator.rb +478 -0
- data/test/test_markdown_to_rrd.rb +757 -0
- data/test/test_markdown_tree.rb +369 -0
- data/test/test_mdcompiler.rb +1276 -0
- data/test/test_mdparser.rb +538 -0
- data/test/test_method_screen.rb +166 -0
- data/test/test_method_since_calculator.rb +290 -0
- data/test/test_methoddatabase.rb +16 -0
- data/test/test_methodentry.rb +328 -0
- data/test/test_methodsignature.rb +3 -1
- data/test/test_methodsince_command.rb +168 -0
- data/test/test_nameutils.rb +22 -0
- data/test/test_preprocessor.rb +215 -0
- data/test/test_rdcompiler.rb +429 -27
- data/test/test_refsdatabase.rb +32 -0
- data/test/test_rrd_to_markdown.rb +968 -0
- data/test/test_rrdparser.rb +295 -2
- data/test/test_run_ruby_wasm.rb +54 -0
- data/test/test_run_worker_prelude.rb +83 -0
- data/test/test_runner.rb +51 -0
- data/test/test_search_index_generator.rb +513 -0
- data/test/test_search_screen.rb +48 -0
- data/test/test_searcher.rb +153 -0
- data/test/test_searchpage_command.rb +167 -0
- data/test/test_simplesearcher.rb +28 -0
- data/test/test_statichtml_command.rb +204 -0
- data/test/test_syntax_highlighter.rb +60 -0
- data/test/test_whole_file_gate.rb +175 -0
- data/theme/default/js/NOTICE +42 -0
- data/theme/default/js/run-worker.js +111 -0
- data/theme/default/js/run.js +350 -0
- data/theme/default/js/search_controller.js +134 -0
- data/theme/default/js/search_init.js +176 -0
- data/theme/default/js/search_navigation.js +110 -0
- data/theme/default/js/search_page.js +202 -0
- data/theme/default/js/search_ranker.js +244 -0
- data/theme/default/script.js +91 -18
- data/theme/default/search.css +149 -0
- data/theme/default/style.css +193 -21
- data/theme/default/syntax-highlight.css +8 -0
- data/theme/lillia/style.css +45 -1
- metadata +178 -21
- data/README +0 -32
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# bitclust/subcommands/searchpage_command.rb
|
|
4
|
+
#
|
|
5
|
+
# Generates a standalone, cross-version, client-side search page from
|
|
6
|
+
# multiple version databases:
|
|
7
|
+
#
|
|
8
|
+
# bitclust searchpage --outputdir=DIR db-3.0 db-3.1 ... db-4.1
|
|
9
|
+
#
|
|
10
|
+
# The output (index.html + js/search_data.js + assets) is fully static and
|
|
11
|
+
# replaces the server-backed rurema-search app at docs.ruby-lang.org/ja/search/.
|
|
12
|
+
# Each database's version is read from its own properties, and the merged
|
|
13
|
+
# index tags every entry with the versions it appears in.
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
require 'pathname'
|
|
17
|
+
require 'fileutils'
|
|
18
|
+
require 'json'
|
|
19
|
+
require 'optparse'
|
|
20
|
+
|
|
21
|
+
require 'bitclust'
|
|
22
|
+
require 'bitclust/subcommand'
|
|
23
|
+
require 'bitclust/search_index_generator'
|
|
24
|
+
|
|
25
|
+
module BitClust
|
|
26
|
+
module Subcommands
|
|
27
|
+
class SearchpageCommand < Subcommand
|
|
28
|
+
# search_init.js is the in-page dropdown wiring used by statichtml
|
|
29
|
+
# layouts; this page ships its own wiring (search_page.js) instead.
|
|
30
|
+
VENDORED_JS_FILES = %w[
|
|
31
|
+
search_navigation.js search_ranker.js search_controller.js
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
super
|
|
36
|
+
@outputdir = nil
|
|
37
|
+
@themedir = srcdir_root + "theme/default"
|
|
38
|
+
@templatedir = srcdir_root + "data/bitclust/searchpage"
|
|
39
|
+
@suffix = ".html"
|
|
40
|
+
@fs_casesensitive = false
|
|
41
|
+
@verbose = false
|
|
42
|
+
@parser.banner = "Usage: #{File.basename($0, '.*')} searchpage [options] <dbpath>..."
|
|
43
|
+
@parser.on('-o', '--outputdir=PATH', 'Output directory.') {|path|
|
|
44
|
+
@outputdir = Pathname.new(path)
|
|
45
|
+
}
|
|
46
|
+
@parser.on('--themedir=PATH', 'Theme directory.') {|path|
|
|
47
|
+
@themedir = Pathname.new(path)
|
|
48
|
+
}
|
|
49
|
+
@parser.on('--fs-casesensitive', 'Filesystem is case-sensitive.') {
|
|
50
|
+
@fs_casesensitive = true
|
|
51
|
+
}
|
|
52
|
+
@parser.on('--verbose', 'Show progress.') {
|
|
53
|
+
@verbose = true
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# DB paths are taken as positional arguments (one per version), so the
|
|
58
|
+
# global --database option is not required.
|
|
59
|
+
def needs_database?
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def exec(argv, options)
|
|
64
|
+
outputdir = @outputdir or error("no --outputdir given")
|
|
65
|
+
error("no database given (pass one path per version)") if argv.empty?
|
|
66
|
+
|
|
67
|
+
generator = SearchIndexGenerator.new(suffix: @suffix,
|
|
68
|
+
fs_casesensitive: @fs_casesensitive)
|
|
69
|
+
version_indexes = argv.map {|path|
|
|
70
|
+
db = MethodDatabase.new(path)
|
|
71
|
+
fdb = FunctionDatabase.new(path) if File.directory?(File.join(path, 'function'))
|
|
72
|
+
version = db.properties['version'] or
|
|
73
|
+
error("#{path}: no version property (not a bitclust database?)")
|
|
74
|
+
$stderr.puts "indexing #{path} (#{version})" if @verbose
|
|
75
|
+
[version, generator.build_index(db, fdb)] #: [String, Array[SearchIndexGenerator::entry]]
|
|
76
|
+
}
|
|
77
|
+
versions = version_indexes.map {|version, _| version }
|
|
78
|
+
.sort_by {|v| Gem::Version.new(v) }
|
|
79
|
+
|
|
80
|
+
jsdir = outputdir + "js"
|
|
81
|
+
FileUtils.mkdir_p(jsdir)
|
|
82
|
+
(jsdir + "search_data.js").write(SearchIndexGenerator.merged_js(version_indexes))
|
|
83
|
+
VENDORED_JS_FILES.each do |js|
|
|
84
|
+
FileUtils.cp(@themedir + "js" + js, jsdir.to_s, :preserve => true)
|
|
85
|
+
end
|
|
86
|
+
# Ship the MIT notice for the vendored Aliki files alongside them.
|
|
87
|
+
FileUtils.cp(@themedir + "js" + "NOTICE", jsdir.to_s, :preserve => true)
|
|
88
|
+
FileUtils.cp(@themedir + "js" + "search_page.js", jsdir.to_s, :preserve => true)
|
|
89
|
+
FileUtils.cp(@themedir + "search.css", outputdir.to_s, :preserve => true)
|
|
90
|
+
(outputdir + "index.html").write(render_page(versions))
|
|
91
|
+
$stderr.puts "generated search page for #{versions.join(', ')}" if @verbose
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def render_page(versions)
|
|
97
|
+
template = (@templatedir + "index.html").read
|
|
98
|
+
template.sub('%%SEARCH_VERSIONS%%', JSON.generate(versions))
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'pathname'
|
|
2
3
|
require 'erb'
|
|
3
4
|
require 'find'
|
|
4
5
|
require 'pp'
|
|
5
6
|
require 'optparse'
|
|
6
7
|
require 'yaml'
|
|
7
|
-
require 'webrick'
|
|
8
8
|
require 'uri'
|
|
9
9
|
|
|
10
10
|
require 'bitclust'
|
|
@@ -15,7 +15,6 @@ module BitClust
|
|
|
15
15
|
class ServerCommand < Subcommand
|
|
16
16
|
def initialize
|
|
17
17
|
super
|
|
18
|
-
require 'webrick'
|
|
19
18
|
require 'uri'
|
|
20
19
|
|
|
21
20
|
@params = {
|
|
@@ -108,15 +107,20 @@ module BitClust
|
|
|
108
107
|
exit 1
|
|
109
108
|
end
|
|
110
109
|
if @pid_file
|
|
111
|
-
if File.exist?(@pid_file)
|
|
110
|
+
if File.exist?(@pid_file || raise)
|
|
112
111
|
$stderr.puts "There is still #{@pid_file}. Is another process running?"
|
|
113
112
|
exit 1
|
|
114
113
|
end
|
|
115
|
-
@pid_file = File.expand_path(@pid_file)
|
|
114
|
+
@pid_file = File.expand_path(@pid_file || raise)
|
|
116
115
|
end
|
|
117
116
|
end
|
|
118
117
|
|
|
119
118
|
def exec(argv, options)
|
|
119
|
+
begin
|
|
120
|
+
require 'webrick'
|
|
121
|
+
rescue LoadError
|
|
122
|
+
abort "webrick is not found. You may need to `gem install webrick` to install webrick."
|
|
123
|
+
end
|
|
120
124
|
require 'bitclust/app'
|
|
121
125
|
if @debugp
|
|
122
126
|
@params[:Logger] = WEBrick::Log.new($stderr, WEBrick::Log::DEBUG)
|
|
@@ -129,7 +133,7 @@ module BitClust
|
|
|
129
133
|
@params[:Logger] = WEBrick::Log.new($stderr, WEBrick::Log::INFO)
|
|
130
134
|
@params[:AccessLog] = []
|
|
131
135
|
end
|
|
132
|
-
basepath = URI.parse(@baseurl).path
|
|
136
|
+
basepath = URI.parse(@baseurl || raise).path or raise
|
|
133
137
|
server = WEBrick::HTTPServer.new(@params)
|
|
134
138
|
|
|
135
139
|
if @autop
|
|
@@ -168,17 +172,19 @@ module BitClust
|
|
|
168
172
|
server.mount File.join(basepath, 'theme/'), WEBrick::HTTPServlet::FileHandler, @themedir
|
|
169
173
|
|
|
170
174
|
if @debugp
|
|
171
|
-
trap(:INT) { server.shutdown }
|
|
175
|
+
Signal.trap(:INT) { server.shutdown }
|
|
172
176
|
else
|
|
173
177
|
WEBrick::Daemon.start do
|
|
174
|
-
trap(:TERM) {
|
|
178
|
+
Signal.trap(:TERM) {
|
|
175
179
|
server.shutdown
|
|
176
180
|
begin
|
|
177
|
-
|
|
181
|
+
pid_file = @pid_file
|
|
182
|
+
File.unlink pid_file if pid_file
|
|
178
183
|
rescue Errno::ENOENT
|
|
179
184
|
end
|
|
180
185
|
}
|
|
181
|
-
|
|
186
|
+
pid_file = @pid_file
|
|
187
|
+
File.open(pid_file, 'w') {|f| f.write Process.pid } if pid_file
|
|
182
188
|
end
|
|
183
189
|
end
|
|
184
190
|
exit if $".include?("exerb/mkexy.rb")
|
|
@@ -205,7 +211,7 @@ module BitClust
|
|
|
205
211
|
end
|
|
206
212
|
|
|
207
213
|
def load_config_file
|
|
208
|
-
home_directory = Pathname(ENV
|
|
214
|
+
home_directory = Pathname(ENV.fetch('HOME'))
|
|
209
215
|
config_path = home_directory + ".bitclust/config"
|
|
210
216
|
if config_path.exist?
|
|
211
217
|
config = YAML.load_file(config_path)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'pathname'
|
|
2
3
|
require 'erb'
|
|
3
4
|
require 'find'
|
|
@@ -19,7 +20,7 @@ module BitClust
|
|
|
19
20
|
@prepare = nil
|
|
20
21
|
@cleanup = nil
|
|
21
22
|
@purge = nil
|
|
22
|
-
@versions = ["
|
|
23
|
+
@versions = ["3.3.0", "3.4.0", "4.0.0"]
|
|
23
24
|
@update = true
|
|
24
25
|
@parser.banner = "Usage: #{File.basename($0, '.*')} setup [options]"
|
|
25
26
|
@parser.on('--prepare', 'Prepare config file and checkout repository. Do not create database.') {
|
|
@@ -39,6 +40,11 @@ module BitClust
|
|
|
39
40
|
}
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
# DB は ~/.bitclust 配下に自前で作るのでグローバル --database は不要
|
|
44
|
+
def needs_database?
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
42
48
|
def exec(argv, options)
|
|
43
49
|
purge if @purge
|
|
44
50
|
prepare
|
|
@@ -48,18 +54,18 @@ module BitClust
|
|
|
48
54
|
prefix = "#{@config[:database_prefix]}-#{version}"
|
|
49
55
|
FileUtils.rm_rf(prefix) if @cleanup
|
|
50
56
|
init_argv = ["version=#{version}", "encoding=#{@config[:encoding]}"]
|
|
51
|
-
|
|
57
|
+
# @type var init_options: Subcommand::options
|
|
58
|
+
init_options = { :prefix => prefix, :capi => false }
|
|
52
59
|
InitCommand.new.exec(init_argv, init_options)
|
|
53
|
-
update_method_database(prefix, ["--
|
|
54
|
-
|
|
55
|
-
update_function_database(prefix, update_argv)
|
|
60
|
+
update_method_database(prefix, ["--markdowntree=#{@config[:mdtree]}"])
|
|
61
|
+
update_function_database(prefix, ["--markdowntree=#{@config[:capi_mdtree]}"])
|
|
56
62
|
end
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
private
|
|
60
66
|
|
|
61
67
|
def purge
|
|
62
|
-
home_directory = Pathname(ENV
|
|
68
|
+
home_directory = Pathname(ENV.fetch("HOME"))
|
|
63
69
|
config_dir = home_directory + ".bitclust"
|
|
64
70
|
print "Remove all generated files..."
|
|
65
71
|
FileUtils.rm_rf(config_dir.to_s)
|
|
@@ -68,7 +74,7 @@ module BitClust
|
|
|
68
74
|
end
|
|
69
75
|
|
|
70
76
|
def prepare
|
|
71
|
-
home_directory = Pathname(ENV
|
|
77
|
+
home_directory = Pathname(ENV.fetch("HOME")).expand_path
|
|
72
78
|
config_dir = home_directory + ".bitclust"
|
|
73
79
|
config_dir.mkpath
|
|
74
80
|
config_path = config_dir + "config"
|
|
@@ -78,17 +84,24 @@ module BitClust
|
|
|
78
84
|
:encoding => "utf-8",
|
|
79
85
|
:versions => @versions,
|
|
80
86
|
:default_version => @versions.max,
|
|
81
|
-
:
|
|
82
|
-
:
|
|
87
|
+
:mdtree => (rubydoc_dir + "manual/api").to_s,
|
|
88
|
+
:capi_mdtree => (rubydoc_dir + "manual/capi").to_s,
|
|
83
89
|
:baseurl => "http://localhost:10080",
|
|
84
90
|
:port => "10080",
|
|
85
91
|
:pid_file => "/tmp/bitclust.pid",
|
|
86
92
|
}
|
|
87
93
|
if config_path.exist?
|
|
88
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
|
|
89
102
|
unless @config[:versions].sort == @versions.sort
|
|
90
103
|
print("overwrite config file? > [y/N]")
|
|
91
|
-
if /\Ay\z/i =~ $stdin.gets
|
|
104
|
+
if /\Ay\z/i =~ $stdin.gets&.chomp
|
|
92
105
|
@config[:versions] = @versions
|
|
93
106
|
@config[:default_version] = @versions.max
|
|
94
107
|
generate_config(config_path, @config)
|
|
@@ -131,6 +144,7 @@ module BitClust
|
|
|
131
144
|
end
|
|
132
145
|
|
|
133
146
|
def update_method_database(prefix, argv)
|
|
147
|
+
# @type var options: Subcommand::options
|
|
134
148
|
options = {
|
|
135
149
|
:prefix => prefix,
|
|
136
150
|
:capi => false,
|
|
@@ -141,6 +155,7 @@ module BitClust
|
|
|
141
155
|
end
|
|
142
156
|
|
|
143
157
|
def update_function_database(prefix, argv)
|
|
158
|
+
# @type var options: Subcommand::options
|
|
144
159
|
options = {
|
|
145
160
|
:prefix => prefix,
|
|
146
161
|
:capi => true,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
# Copyright (c) 2006-2007 Minero Aoki
|
|
2
3
|
#
|
|
3
4
|
# This program is free software.
|
|
@@ -11,6 +12,7 @@ require 'bitclust/nameutils'
|
|
|
11
12
|
require 'bitclust/subcommand'
|
|
12
13
|
require 'bitclust/progress_bar'
|
|
13
14
|
require 'bitclust/silent_progress_bar'
|
|
15
|
+
require 'bitclust/search_index_generator'
|
|
14
16
|
|
|
15
17
|
module BitClust
|
|
16
18
|
module Subcommands
|
|
@@ -44,7 +46,7 @@ module BitClust
|
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def method_url(spec)
|
|
47
|
-
cname, tmark, mname =
|
|
49
|
+
cname, tmark, mname = split_method_spec(spec)
|
|
48
50
|
filename = html_filename(encodename_package(mname), @suffix)
|
|
49
51
|
@bitclust_html_base +
|
|
50
52
|
"/method/#{encodename_package(cname)}/#{typemark2char(tmark)}/#{filename}"
|
|
@@ -89,7 +91,7 @@ module BitClust
|
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
def edit_url(location)
|
|
92
|
-
"#{@edit_base_url}/#{location.file}
|
|
94
|
+
"#{@edit_base_url}/#{location.file}".sub(/([^:])\/\/+/, "\\1/")
|
|
93
95
|
end
|
|
94
96
|
|
|
95
97
|
def encodename_package(str)
|
|
@@ -119,6 +121,10 @@ module BitClust
|
|
|
119
121
|
@gtm_tracking_id = nil
|
|
120
122
|
@meta_robots_content = ["noindex"]
|
|
121
123
|
@stop_on_syntax_error = true
|
|
124
|
+
@eol_warning = false
|
|
125
|
+
@run_ruby_wasm = nil
|
|
126
|
+
@sitemap_baseurl = nil
|
|
127
|
+
@sitemap_paths = []
|
|
122
128
|
@parser.banner = "Usage: #{File.basename($0, '.*')} statichtml [options]"
|
|
123
129
|
@parser.on('-o', '--outputdir=PATH', 'Output directory') do |path|
|
|
124
130
|
begin
|
|
@@ -155,6 +161,15 @@ module BitClust
|
|
|
155
161
|
@parser.on('--meta-robots-content=VALUE1,VALUE2,...', Array, 'HTML <meta> element: <meta name="robots" content="VALUE1,VALUE2..."') do |values|
|
|
156
162
|
@meta_robots_content = values
|
|
157
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
|
|
158
173
|
@parser.on('--no-stop-on-syntax-error', 'Do not stop on syntax error') do |boolean|
|
|
159
174
|
@stop_on_syntax_error = boolean
|
|
160
175
|
end
|
|
@@ -172,8 +187,10 @@ module BitClust
|
|
|
172
187
|
manager = ScreenManager.new(@manager_config)
|
|
173
188
|
|
|
174
189
|
db.transaction do
|
|
175
|
-
methods = {}
|
|
190
|
+
methods = {} #: Hash[String, Array[MethodEntry]]
|
|
176
191
|
db.methods.each_with_index do |entry, i|
|
|
192
|
+
next if entry.undefined?
|
|
193
|
+
|
|
177
194
|
entry.names.each do |name|
|
|
178
195
|
method_name = entry.klass.name + entry.typemark + name
|
|
179
196
|
(methods[method_name] ||= []) << entry
|
|
@@ -190,22 +207,33 @@ module BitClust
|
|
|
190
207
|
end
|
|
191
208
|
|
|
192
209
|
@urlmapper.bitclust_html_base = '..'
|
|
193
|
-
|
|
210
|
+
library_index_path = @outputdir + "library/#{html_filename("index", @suffix)}"
|
|
211
|
+
create_file(library_index_path,
|
|
194
212
|
manager.library_index_screen(db.libraries.sort, {:database => db}).body,
|
|
195
213
|
:verbose => @verbose)
|
|
196
|
-
|
|
214
|
+
record_sitemap_path(library_index_path)
|
|
215
|
+
class_index_path = @outputdir + "class/#{html_filename("index", @suffix)}"
|
|
216
|
+
create_file(class_index_path,
|
|
197
217
|
manager.class_index_screen(db.classes.sort, {:database => db}).body,
|
|
198
218
|
:verbose => @verbose)
|
|
199
|
-
|
|
219
|
+
record_sitemap_path(class_index_path)
|
|
220
|
+
function_index_path = @outputdir + "function/#{html_filename("index", @suffix)}"
|
|
221
|
+
create_file(function_index_path,
|
|
200
222
|
manager.function_index_screen(fdb.functions.sort, { :database => fdb }).body,
|
|
201
223
|
:verbose => @verbose)
|
|
224
|
+
record_sitemap_path(function_index_path)
|
|
202
225
|
create_index_html(@outputdir)
|
|
226
|
+
create_search_index(@outputdir, db, fdb)
|
|
227
|
+
if baseurl = @sitemap_baseurl
|
|
228
|
+
create_sitemap(@outputdir, baseurl)
|
|
229
|
+
end
|
|
203
230
|
FileUtils.cp(@manager_config[:themedir] + @manager_config[:css_url],
|
|
204
231
|
@outputdir.to_s, :verbose => @verbose, :preserve => true)
|
|
205
232
|
FileUtils.cp(@manager_config[:themedir] + "syntax-highlight.css",
|
|
206
233
|
@outputdir.to_s, :verbose => @verbose, :preserve => true)
|
|
207
234
|
FileUtils.cp(@manager_config[:themedir] + "script.js",
|
|
208
235
|
@outputdir.to_s, :verbose => @verbose, :preserve => true)
|
|
236
|
+
copy_run_ruby_wasm_script if @run_ruby_wasm
|
|
209
237
|
FileUtils.cp(@manager_config[:themedir] + @manager_config[:favicon_url],
|
|
210
238
|
@outputdir.to_s, :verbose => @verbose, :preserve => true)
|
|
211
239
|
Dir.mktmpdir do |tmpdir|
|
|
@@ -237,6 +265,8 @@ module BitClust
|
|
|
237
265
|
:gtm_tracking_id => @gtm_tracking_id,
|
|
238
266
|
:meta_robots_content => @meta_robots_content,
|
|
239
267
|
:stop_on_syntax_error => @stop_on_syntax_error,
|
|
268
|
+
:eol_warning => @eol_warning,
|
|
269
|
+
:run_ruby_wasm => @run_ruby_wasm,
|
|
240
270
|
}
|
|
241
271
|
@manager_config[:urlmapper] = URLMapperEx.new(@manager_config)
|
|
242
272
|
@urlmapper = @manager_config[:urlmapper]
|
|
@@ -287,6 +317,101 @@ HERE
|
|
|
287
317
|
}
|
|
288
318
|
end
|
|
289
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
|
+
'&' => '&',
|
|
355
|
+
'<' => '<',
|
|
356
|
+
'>' => '>',
|
|
357
|
+
'"' => '"',
|
|
358
|
+
"'" => ''',
|
|
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
|
+
|
|
290
415
|
def create_html_file(entry, manager, outputdir, db)
|
|
291
416
|
e = entry.is_a?(Array) ? entry.sort.first : entry
|
|
292
417
|
case e.type_id
|
|
@@ -325,6 +450,7 @@ HERE
|
|
|
325
450
|
path.open('w') do |f|
|
|
326
451
|
f.write(html)
|
|
327
452
|
end
|
|
453
|
+
record_sitemap_path(path)
|
|
328
454
|
end
|
|
329
455
|
|
|
330
456
|
def create_file(path, str, options = {})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'pathname'
|
|
2
3
|
require 'erb'
|
|
3
4
|
require 'find'
|
|
@@ -15,11 +16,15 @@ module BitClust
|
|
|
15
16
|
def initialize
|
|
16
17
|
super
|
|
17
18
|
@root = nil
|
|
19
|
+
@markdowntree = nil
|
|
18
20
|
@library = nil
|
|
19
21
|
@parser.banner = "Usage: #{File.basename($0, '.*')} update [<file>...]"
|
|
20
22
|
@parser.on('--stdlibtree=ROOT', 'Process stdlib source directory tree.') {|path|
|
|
21
23
|
@root = path
|
|
22
24
|
}
|
|
25
|
+
@parser.on('--markdowntree=ROOT', 'Process Markdown source directory tree.') {|path|
|
|
26
|
+
@markdowntree = path
|
|
27
|
+
}
|
|
23
28
|
@parser.on('--library-name=NAME', 'Use NAME for library name in file mode.') {|name|
|
|
24
29
|
@library = name
|
|
25
30
|
}
|
|
@@ -27,25 +32,129 @@ module BitClust
|
|
|
27
32
|
|
|
28
33
|
def parse(argv)
|
|
29
34
|
super
|
|
30
|
-
if not @root and argv.empty?
|
|
35
|
+
if not @root and not @markdowntree and argv.empty?
|
|
31
36
|
error "no input file given"
|
|
32
37
|
end
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
def exec(argv, options)
|
|
36
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
|
|
37
56
|
@db.transaction {
|
|
38
57
|
if @root
|
|
39
|
-
|
|
58
|
+
db = @db
|
|
59
|
+
db.is_a?(MethodDatabase) or raise
|
|
60
|
+
db.update_by_stdlibtree(@root || raise)
|
|
40
61
|
end
|
|
41
62
|
argv.each do |path|
|
|
42
63
|
@db.update_by_file path, @library || guess_library_name(path)
|
|
43
64
|
end
|
|
44
65
|
}
|
|
66
|
+
remap_source_locations if @markdowntree
|
|
67
|
+
ensure
|
|
68
|
+
FileUtils.remove_entry(@bridge_dir) if @bridge_dir
|
|
45
69
|
end
|
|
46
70
|
|
|
47
71
|
private
|
|
48
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
|
+
|
|
49
158
|
def guess_library_name(path)
|
|
50
159
|
if %r<(\A|/)src/> =~ path
|
|
51
160
|
path.sub(%r<.*(\A|/)src/>, '').sub(/\.rd\z/, '')
|