docyard 0.9.0 → 1.0.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/CHANGELOG.md +43 -0
- data/README.md +8 -253
- data/exe/docyard +6 -0
- data/lib/docyard/build/asset_bundler.rb +2 -2
- data/lib/docyard/build/file_copier.rb +12 -5
- data/lib/docyard/build/llms_txt_generator.rb +103 -0
- data/lib/docyard/build/sitemap_generator.rb +1 -1
- data/lib/docyard/build/static_generator.rb +115 -79
- data/lib/docyard/builder.rb +6 -2
- data/lib/docyard/cli.rb +14 -4
- data/lib/docyard/components/processors/callout_processor.rb +1 -1
- data/lib/docyard/components/processors/code_block_extended_fence_postprocessor.rb +24 -0
- data/lib/docyard/components/processors/code_block_extended_fence_preprocessor.rb +44 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +11 -1
- data/lib/docyard/components/processors/code_block_processor.rb +5 -24
- data/lib/docyard/components/processors/code_group_processor.rb +6 -22
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +1 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +1 -2
- data/lib/docyard/components/processors/icon_processor.rb +8 -2
- data/lib/docyard/components/processors/include_processor.rb +10 -10
- data/lib/docyard/components/processors/video_embed_processor.rb +14 -3
- data/lib/docyard/components/support/code_block/feature_extractor.rb +3 -1
- data/lib/docyard/components/support/code_block/icon_detector.rb +5 -12
- data/lib/docyard/components/support/code_block/line_number_resolver.rb +30 -0
- data/lib/docyard/components/support/code_detector.rb +2 -12
- data/lib/docyard/components/support/code_group/html_builder.rb +2 -6
- data/lib/docyard/components/support/tabs/icon_detector.rb +6 -2
- data/lib/docyard/components/support/tabs/parser.rb +6 -23
- data/lib/docyard/config/analytics_resolver.rb +24 -0
- data/lib/docyard/config/branding_resolver.rb +58 -27
- data/lib/docyard/config/key_validator.rb +30 -0
- data/lib/docyard/config/logo_detector.rb +8 -8
- data/lib/docyard/config/schema.rb +39 -0
- data/lib/docyard/config/section.rb +21 -0
- data/lib/docyard/config/validation_helpers.rb +83 -0
- data/lib/docyard/config/validator.rb +45 -144
- data/lib/docyard/config/validators/navigation.rb +43 -0
- data/lib/docyard/config/validators/section.rb +114 -0
- data/lib/docyard/config.rb +46 -102
- data/lib/docyard/constants.rb +59 -0
- data/lib/docyard/{utils/errors.rb → errors.rb} +6 -0
- data/lib/docyard/initializer.rb +100 -49
- data/lib/docyard/navigation/page_navigation_builder.rb +65 -0
- data/lib/docyard/navigation/sidebar/auto_builder.rb +107 -0
- data/lib/docyard/navigation/sidebar/cache.rb +96 -0
- data/lib/docyard/navigation/sidebar/config_builder.rb +179 -0
- data/lib/docyard/navigation/sidebar/distributed_builder.rb +145 -0
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +69 -3
- data/lib/docyard/navigation/sidebar/renderer.rb +12 -1
- data/lib/docyard/navigation/sidebar_builder.rb +43 -81
- data/lib/docyard/rendering/branding_variables.rb +65 -0
- data/lib/docyard/rendering/icon_helpers.rb +14 -1
- data/lib/docyard/rendering/icons/devicons.rb +63 -0
- data/lib/docyard/rendering/icons.rb +26 -27
- data/lib/docyard/rendering/markdown.rb +5 -23
- data/lib/docyard/rendering/og_helpers.rb +36 -0
- data/lib/docyard/rendering/renderer.rb +87 -59
- data/lib/docyard/rendering/template_resolver.rb +14 -0
- data/lib/docyard/routing/fallback_resolver.rb +3 -3
- data/lib/docyard/search/build_indexer.rb +2 -2
- data/lib/docyard/search/dev_indexer.rb +36 -28
- data/lib/docyard/search/pagefind_support.rb +1 -1
- data/lib/docyard/server/asset_handler.rb +39 -15
- data/lib/docyard/server/dev_server.rb +90 -55
- data/lib/docyard/server/file_watcher.rb +68 -18
- data/lib/docyard/server/pagefind_handler.rb +1 -1
- data/lib/docyard/server/preview_server.rb +29 -33
- data/lib/docyard/server/rack_application.rb +38 -70
- data/lib/docyard/server/router.rb +11 -7
- data/lib/docyard/server/sse_server.rb +157 -0
- data/lib/docyard/server/static_file_app.rb +42 -0
- data/lib/docyard/templates/assets/css/components/banner.css +31 -0
- data/lib/docyard/templates/assets/css/components/breadcrumbs.css +2 -1
- data/lib/docyard/templates/assets/css/components/callout.css +26 -6
- data/lib/docyard/templates/assets/css/components/code-block.css +4 -2
- data/lib/docyard/templates/assets/css/components/code-group.css +20 -7
- data/lib/docyard/templates/assets/css/components/feedback.css +126 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +5 -4
- data/lib/docyard/templates/assets/css/components/icon.css +5 -0
- data/lib/docyard/templates/assets/css/components/nav-menu.css +20 -4
- data/lib/docyard/templates/assets/css/components/navigation.css +25 -3
- data/lib/docyard/templates/assets/css/components/page-actions.css +131 -0
- data/lib/docyard/templates/assets/css/components/prev-next.css +14 -7
- data/lib/docyard/templates/assets/css/components/search.css +6 -10
- data/lib/docyard/templates/assets/css/components/tab-bar.css +7 -4
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +57 -11
- data/lib/docyard/templates/assets/css/components/tabs.css +12 -4
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +3 -1
- data/lib/docyard/templates/assets/css/landing.css +82 -13
- data/lib/docyard/templates/assets/css/layout.css +17 -0
- data/lib/docyard/templates/assets/css/markdown.css +22 -2
- data/lib/docyard/templates/assets/css/variables.css +13 -1
- data/lib/docyard/templates/assets/js/components/code-group.js +4 -1
- data/lib/docyard/templates/assets/js/components/copy-page.js +115 -0
- data/lib/docyard/templates/assets/js/components/feedback.js +66 -0
- data/lib/docyard/templates/assets/js/components/file-tree.js +5 -5
- data/lib/docyard/templates/assets/js/components/navigation.js +3 -3
- data/lib/docyard/templates/assets/js/components/search.js +3 -3
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +12 -6
- data/lib/docyard/templates/assets/js/components/tabs.js +45 -22
- data/lib/docyard/templates/assets/js/components/tooltip.js +4 -4
- data/lib/docyard/templates/assets/js/hot-reload.js +44 -0
- data/lib/docyard/templates/errors/404.html.erb +114 -5
- data/lib/docyard/templates/errors/500.html.erb +173 -10
- data/lib/docyard/templates/init/_sidebar.yml +36 -0
- data/lib/docyard/templates/init/docyard.yml +36 -0
- data/lib/docyard/templates/init/pages/components.md +146 -0
- data/lib/docyard/templates/init/pages/getting-started.md +94 -0
- data/lib/docyard/templates/init/pages/index.md +22 -0
- data/lib/docyard/templates/layouts/default.html.erb +10 -0
- data/lib/docyard/templates/layouts/splash.html.erb +14 -1
- data/lib/docyard/templates/partials/_analytics.html.erb +24 -0
- data/lib/docyard/templates/partials/_banner.html.erb +1 -1
- data/lib/docyard/templates/partials/_code_block.html.erb +1 -1
- data/lib/docyard/templates/partials/_feedback.html.erb +14 -0
- data/lib/docyard/templates/partials/_footer.html.erb +1 -1
- data/lib/docyard/templates/partials/_head.html.erb +79 -4
- data/lib/docyard/templates/partials/_icon_library.html.erb +8 -0
- data/lib/docyard/templates/partials/_page_actions.html.erb +21 -0
- data/lib/docyard/templates/partials/_scripts.html.erb +6 -3
- data/lib/docyard/templates/partials/_tabs.html.erb +4 -1
- data/lib/docyard/utils/git_info.rb +157 -0
- data/lib/docyard/utils/hash_utils.rb +31 -0
- data/lib/docyard/utils/html_helpers.rb +8 -0
- data/lib/docyard/utils/logging.rb +44 -3
- data/lib/docyard/utils/path_resolver.rb +0 -10
- data/lib/docyard/utils/path_utils.rb +73 -0
- data/lib/docyard/version.rb +1 -1
- data/lib/docyard.rb +2 -2
- metadata +77 -47
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- data/.github/pull_request_template.md +0 -14
- data/.github/workflows/ci.yml +0 -49
- data/.rubocop.yml +0 -42
- data/CODE_OF_CONDUCT.md +0 -132
- data/CONTRIBUTING.md +0 -55
- data/LICENSE.vscode-icons +0 -42
- data/Rakefile +0 -8
- data/lib/docyard/config/constants.rb +0 -31
- data/lib/docyard/navigation/sidebar/children_discoverer.rb +0 -51
- data/lib/docyard/navigation/sidebar/config_parser.rb +0 -208
- data/lib/docyard/navigation/sidebar/file_resolver.rb +0 -90
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +0 -78
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +0 -71
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +0 -51
- data/lib/docyard/navigation/sidebar/path_prefixer.rb +0 -34
- data/lib/docyard/navigation/sidebar/sorter.rb +0 -21
- data/lib/docyard/navigation/sidebar/title_extractor.rb +0 -25
- data/lib/docyard/navigation/sidebar/tree_builder.rb +0 -140
- data/lib/docyard/rendering/icons/LICENSE.phosphor +0 -21
- data/lib/docyard/rendering/icons/file_types.rb +0 -79
- data/lib/docyard/rendering/icons/phosphor.rb +0 -93
- data/lib/docyard/rendering/language_mapping.rb +0 -52
- data/lib/docyard/templates/assets/js/reload.js +0 -98
- data/lib/docyard/templates/partials/_icon.html.erb +0 -1
- data/lib/docyard/templates/partials/_icon_file_extension.html.erb +0 -1
- data/sig/docyard.rbs +0 -4
|
@@ -1,65 +1,111 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "parallel"
|
|
3
4
|
require "tty-progressbar"
|
|
4
5
|
require_relative "../rendering/template_resolver"
|
|
5
|
-
require_relative "../navigation/
|
|
6
|
-
require_relative "../navigation/
|
|
6
|
+
require_relative "../navigation/page_navigation_builder"
|
|
7
|
+
require_relative "../navigation/sidebar/cache"
|
|
8
|
+
require_relative "../utils/path_utils"
|
|
9
|
+
require_relative "../utils/git_info"
|
|
7
10
|
|
|
8
11
|
module Docyard
|
|
9
12
|
module Build
|
|
10
13
|
class StaticGenerator
|
|
11
|
-
|
|
14
|
+
PARALLEL_THRESHOLD = 10
|
|
15
|
+
|
|
16
|
+
attr_reader :config, :verbose, :sidebar_cache
|
|
12
17
|
|
|
13
18
|
def initialize(config, verbose: false)
|
|
14
19
|
@config = config
|
|
15
20
|
@verbose = verbose
|
|
16
|
-
@
|
|
21
|
+
@sidebar_cache = nil
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
def generate
|
|
25
|
+
build_sidebar_cache
|
|
26
|
+
Utils::GitInfo.prefetch_timestamps(docs_path) if show_last_updated?
|
|
20
27
|
copy_custom_landing_page if custom_landing_page?
|
|
21
28
|
|
|
22
29
|
markdown_files = collect_markdown_files
|
|
23
|
-
|
|
30
|
+
Docyard.logger.info("\n[✓] Found #{markdown_files.size} markdown files")
|
|
31
|
+
|
|
32
|
+
generate_all_pages(markdown_files)
|
|
33
|
+
generate_error_page
|
|
34
|
+
|
|
35
|
+
markdown_files.size
|
|
36
|
+
ensure
|
|
37
|
+
Utils::GitInfo.clear_cache
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
24
41
|
|
|
42
|
+
def generate_all_pages(markdown_files)
|
|
25
43
|
progress = TTY::ProgressBar.new(
|
|
26
44
|
"Generating pages [:bar] :current/:total (:percent)",
|
|
27
45
|
total: markdown_files.size,
|
|
28
46
|
width: 50
|
|
29
47
|
)
|
|
48
|
+
mutex = Mutex.new
|
|
30
49
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
progress
|
|
50
|
+
Logging.start_buffering
|
|
51
|
+
if markdown_files.size >= PARALLEL_THRESHOLD
|
|
52
|
+
generate_pages_in_parallel(markdown_files, progress, mutex)
|
|
53
|
+
else
|
|
54
|
+
generate_pages_sequentially(markdown_files, progress)
|
|
34
55
|
end
|
|
35
|
-
|
|
36
|
-
markdown_files.size
|
|
56
|
+
Logging.flush_warnings
|
|
37
57
|
end
|
|
38
58
|
|
|
39
|
-
private
|
|
40
|
-
|
|
41
59
|
def custom_landing_page?
|
|
42
|
-
File.file?("
|
|
60
|
+
File.file?(File.join(docs_path, "index.html"))
|
|
43
61
|
end
|
|
44
62
|
|
|
45
63
|
def copy_custom_landing_page
|
|
46
64
|
output_path = File.join(config.build.output, "index.html")
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
safe_file_write(output_path) do
|
|
66
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
67
|
+
FileUtils.cp(File.join(docs_path, "index.html"), output_path)
|
|
68
|
+
end
|
|
49
69
|
log "[✓] Copied custom landing page (index.html)"
|
|
50
70
|
end
|
|
51
71
|
|
|
52
72
|
def collect_markdown_files
|
|
53
|
-
files = Dir.glob(File.join(
|
|
54
|
-
files.reject! { |f| f == "
|
|
73
|
+
files = Dir.glob(File.join(docs_path, "**", "*.md"))
|
|
74
|
+
files.reject! { |f| f == File.join(docs_path, "index.md") } if custom_landing_page?
|
|
55
75
|
files
|
|
56
76
|
end
|
|
57
77
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
def generate_pages_in_parallel(markdown_files, progress, mutex)
|
|
79
|
+
Parallel.each(markdown_files, in_threads: Parallel.processor_count) do |file_path|
|
|
80
|
+
generate_page(file_path, thread_local_renderer)
|
|
81
|
+
mutex.synchronize { progress.advance }
|
|
82
|
+
ensure
|
|
83
|
+
Thread.current[:docyard_build_renderer] = nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def generate_pages_sequentially(markdown_files, progress)
|
|
88
|
+
renderer = build_renderer
|
|
89
|
+
markdown_files.each do |file_path|
|
|
90
|
+
generate_page(file_path, renderer)
|
|
91
|
+
progress.advance
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def thread_local_renderer
|
|
96
|
+
Thread.current[:docyard_build_renderer] ||= build_renderer
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_renderer
|
|
100
|
+
Renderer.new(base_url: config.build.base, config: config)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def generate_page(markdown_file_path, renderer)
|
|
104
|
+
relative_path = markdown_file_path.delete_prefix("#{docs_path}/")
|
|
105
|
+
output_path = Utils::PathUtils.markdown_to_html_output(relative_path, config.build.output)
|
|
106
|
+
current_path = Utils::PathUtils.markdown_file_to_url(markdown_file_path, docs_path)
|
|
61
107
|
|
|
62
|
-
html_content = render_markdown_file(markdown_file_path, current_path)
|
|
108
|
+
html_content = render_markdown_file(markdown_file_path, current_path, renderer)
|
|
63
109
|
html_content = apply_search_exclusion(html_content, current_path)
|
|
64
110
|
write_output(output_path, html_content)
|
|
65
111
|
end
|
|
@@ -79,10 +125,10 @@ module Docyard
|
|
|
79
125
|
end
|
|
80
126
|
end
|
|
81
127
|
|
|
82
|
-
def render_markdown_file(markdown_file_path, current_path)
|
|
128
|
+
def render_markdown_file(markdown_file_path, current_path, renderer)
|
|
83
129
|
markdown = Markdown.new(File.read(markdown_file_path))
|
|
84
130
|
template_resolver = TemplateResolver.new(markdown.frontmatter, config.data)
|
|
85
|
-
branding =
|
|
131
|
+
branding = BrandingResolver.new(config).resolve
|
|
86
132
|
|
|
87
133
|
navigation = build_navigation_html(template_resolver, current_path, markdown, branding[:header_ctas])
|
|
88
134
|
renderer.render_file(markdown_file_path, **navigation, branding: branding,
|
|
@@ -91,82 +137,72 @@ module Docyard
|
|
|
91
137
|
end
|
|
92
138
|
|
|
93
139
|
def build_navigation_html(template_resolver, current_path, markdown, header_ctas)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
breadcrumbs: build_breadcrumbs(sidebar_builder.tree, current_path)
|
|
101
|
-
}
|
|
140
|
+
navigation_builder.build(
|
|
141
|
+
current_path: current_path,
|
|
142
|
+
markdown: markdown,
|
|
143
|
+
header_ctas: header_ctas,
|
|
144
|
+
show_sidebar: template_resolver.show_sidebar?
|
|
145
|
+
)
|
|
102
146
|
end
|
|
103
147
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
148
|
+
def navigation_builder
|
|
149
|
+
@navigation_builder ||= Navigation::PageNavigationBuilder.new(
|
|
150
|
+
docs_path: docs_path,
|
|
151
|
+
config: config,
|
|
152
|
+
sidebar_cache: sidebar_cache
|
|
153
|
+
)
|
|
108
154
|
end
|
|
109
155
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
output_dir = config.build.output
|
|
116
|
-
|
|
117
|
-
if base_name == "index"
|
|
118
|
-
File.join(output_dir, dir_name, "index.html")
|
|
119
|
-
else
|
|
120
|
-
File.join(output_dir, dir_name, base_name, "index.html")
|
|
156
|
+
def write_output(output_path, html_content)
|
|
157
|
+
safe_file_write(output_path) do
|
|
158
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
159
|
+
File.write(output_path, html_content)
|
|
121
160
|
end
|
|
161
|
+
log "Generated: #{output_path}" if verbose
|
|
122
162
|
end
|
|
123
163
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
164
|
+
def safe_file_write(path, &block)
|
|
165
|
+
block.call
|
|
166
|
+
rescue Errno::EACCES => e
|
|
167
|
+
raise BuildError, "Permission denied writing to #{path}: #{e.message}"
|
|
168
|
+
rescue Errno::ENOSPC => e
|
|
169
|
+
raise BuildError, "Disk full, cannot write to #{path}: #{e.message}"
|
|
170
|
+
rescue Errno::EROFS => e
|
|
171
|
+
raise BuildError, "Read-only filesystem, cannot write to #{path}: #{e.message}"
|
|
172
|
+
rescue SystemCallError => e
|
|
173
|
+
raise BuildError, "Failed to write #{path}: #{e.message}"
|
|
134
174
|
end
|
|
135
175
|
|
|
136
|
-
def
|
|
137
|
-
|
|
138
|
-
docs_path:
|
|
139
|
-
|
|
140
|
-
config: config,
|
|
141
|
-
header_ctas: header_ctas
|
|
176
|
+
def build_sidebar_cache
|
|
177
|
+
@sidebar_cache = Sidebar::Cache.new(
|
|
178
|
+
docs_path: docs_path,
|
|
179
|
+
config: config
|
|
142
180
|
)
|
|
181
|
+
@sidebar_cache.build
|
|
143
182
|
end
|
|
144
183
|
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
sidebar_tree: sidebar_builder.tree,
|
|
148
|
-
current_path: current_path,
|
|
149
|
-
frontmatter: markdown.frontmatter,
|
|
150
|
-
config: {}
|
|
151
|
-
).to_html
|
|
184
|
+
def show_last_updated?
|
|
185
|
+
config.repo.url && config.repo.last_updated != false
|
|
152
186
|
end
|
|
153
187
|
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
BreadcrumbBuilder.new(sidebar_tree: sidebar_tree, current_path: current_path)
|
|
188
|
+
def docs_path
|
|
189
|
+
config.source
|
|
158
190
|
end
|
|
159
191
|
|
|
160
|
-
def
|
|
161
|
-
|
|
192
|
+
def log(message)
|
|
193
|
+
Docyard.logger.info(message) if verbose
|
|
162
194
|
end
|
|
163
195
|
|
|
164
|
-
def
|
|
165
|
-
|
|
196
|
+
def generate_error_page
|
|
197
|
+
output_path = File.join(config.build.output, "404.html")
|
|
198
|
+
html_content = load_error_page_content
|
|
199
|
+
safe_file_write(output_path) { File.write(output_path, html_content) }
|
|
200
|
+
log "[✓] Generated 404.html"
|
|
166
201
|
end
|
|
167
202
|
|
|
168
|
-
def
|
|
169
|
-
|
|
203
|
+
def load_error_page_content
|
|
204
|
+
error_page = File.join(docs_path, "404.html")
|
|
205
|
+
File.exist?(error_page) ? File.read(error_page) : build_renderer.render_not_found
|
|
170
206
|
end
|
|
171
207
|
end
|
|
172
208
|
end
|
data/lib/docyard/builder.rb
CHANGED
|
@@ -68,6 +68,10 @@ module Docyard
|
|
|
68
68
|
sitemap_gen = Build::SitemapGenerator.new(config)
|
|
69
69
|
sitemap_gen.generate
|
|
70
70
|
|
|
71
|
+
require_relative "build/llms_txt_generator"
|
|
72
|
+
llms_gen = Build::LlmsTxtGenerator.new(config)
|
|
73
|
+
llms_gen.generate
|
|
74
|
+
|
|
71
75
|
File.write(File.join(config.build.output, "robots.txt"), robots_txt_content)
|
|
72
76
|
log "[+] Generated robots.txt"
|
|
73
77
|
end
|
|
@@ -104,11 +108,11 @@ module Docyard
|
|
|
104
108
|
end
|
|
105
109
|
|
|
106
110
|
def log(message)
|
|
107
|
-
|
|
111
|
+
Docyard.logger.info(message)
|
|
108
112
|
end
|
|
109
113
|
|
|
110
114
|
def error(message)
|
|
111
|
-
|
|
115
|
+
Docyard.logger.error(message)
|
|
112
116
|
end
|
|
113
117
|
end
|
|
114
118
|
end
|
data/lib/docyard/cli.rb
CHANGED
|
@@ -13,9 +13,11 @@ module Docyard
|
|
|
13
13
|
puts "docyard #{Docyard::VERSION}"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
desc "init", "Initialize a new docyard project"
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
desc "init [PROJECT_NAME]", "Initialize a new docyard project"
|
|
17
|
+
method_option :force, type: :boolean, default: false, aliases: "-f",
|
|
18
|
+
desc: "Overwrite existing files"
|
|
19
|
+
def init(project_name = nil)
|
|
20
|
+
initializer = Docyard::Initializer.new(project_name, force: options[:force])
|
|
19
21
|
exit(1) unless initializer.run
|
|
20
22
|
end
|
|
21
23
|
|
|
@@ -29,6 +31,9 @@ module Docyard
|
|
|
29
31
|
verbose: options[:verbose]
|
|
30
32
|
)
|
|
31
33
|
exit(1) unless builder.build
|
|
34
|
+
rescue ConfigError => e
|
|
35
|
+
Docyard.logger.error(e.message)
|
|
36
|
+
exit(1)
|
|
32
37
|
end
|
|
33
38
|
|
|
34
39
|
desc "preview", "Preview the built site locally"
|
|
@@ -45,12 +50,17 @@ module Docyard
|
|
|
45
50
|
desc: "Enable search indexing (slower startup)"
|
|
46
51
|
def serve
|
|
47
52
|
require_relative "server/dev_server"
|
|
48
|
-
|
|
53
|
+
config = Docyard::Config.load
|
|
54
|
+
server = Docyard::DevServer.new(
|
|
49
55
|
port: options[:port],
|
|
50
56
|
host: options[:host],
|
|
57
|
+
docs_path: config.source,
|
|
51
58
|
search: options[:search]
|
|
52
59
|
)
|
|
53
60
|
server.start
|
|
61
|
+
rescue ConfigError => e
|
|
62
|
+
Docyard.logger.error(e.message)
|
|
63
|
+
exit(1)
|
|
54
64
|
end
|
|
55
65
|
end
|
|
56
66
|
end
|
|
@@ -112,7 +112,7 @@ module Docyard
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def render_callout_html(type, title, content_html, icon_name)
|
|
115
|
-
icon_svg = Icons.render(icon_name, "
|
|
115
|
+
icon_svg = Icons.render(icon_name, "regular") || ""
|
|
116
116
|
renderer = Renderer.new
|
|
117
117
|
|
|
118
118
|
renderer.render_partial(
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_processor"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Components
|
|
7
|
+
module Processors
|
|
8
|
+
# Restores placeholders after all code block processing.
|
|
9
|
+
# Runs after CodeBlockProcessor to ensure the HTML is fully rendered.
|
|
10
|
+
class CodeBlockExtendedFencePostprocessor < BaseProcessor
|
|
11
|
+
self.priority = 25
|
|
12
|
+
|
|
13
|
+
BACKTICK_PLACEHOLDER = "\u200B\u200B\u200B"
|
|
14
|
+
CODE_MARKER_PLACEHOLDER = "\u200B!\u200Bcode"
|
|
15
|
+
|
|
16
|
+
def postprocess(html)
|
|
17
|
+
html
|
|
18
|
+
.gsub(BACKTICK_PLACEHOLDER, "`")
|
|
19
|
+
.gsub(CODE_MARKER_PLACEHOLDER, "[!code")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_processor"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Components
|
|
7
|
+
module Processors
|
|
8
|
+
# Handles 4+ backtick code fences (extended fences) for showing raw syntax.
|
|
9
|
+
# Escapes special markers so they display as raw text instead of being processed.
|
|
10
|
+
# Example: ```` (4 backticks) wrapping ```js shows the raw markdown syntax.
|
|
11
|
+
class CodeBlockExtendedFencePreprocessor < BaseProcessor
|
|
12
|
+
self.priority = 1
|
|
13
|
+
|
|
14
|
+
# Match 4+ backticks, capture language, content, and closing backticks
|
|
15
|
+
EXTENDED_FENCE_REGEX = /^(`{4,})(\w*)[^\n]*\n(.*?)^\1/m
|
|
16
|
+
|
|
17
|
+
# Placeholders using zero-width spaces
|
|
18
|
+
BACKTICK_PLACEHOLDER = "\u200B\u200B\u200B"
|
|
19
|
+
CODE_MARKER_PLACEHOLDER = "\u200B!\u200Bcode"
|
|
20
|
+
|
|
21
|
+
def preprocess(content)
|
|
22
|
+
content.gsub(EXTENDED_FENCE_REGEX) { |_| process_extended_fence(Regexp.last_match) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def process_extended_fence(match)
|
|
28
|
+
lang = match[2]
|
|
29
|
+
code = match[3].chomp
|
|
30
|
+
|
|
31
|
+
# Replace backticks and code markers with placeholders
|
|
32
|
+
# This prevents other preprocessors from matching/processing them
|
|
33
|
+
escaped_code = code
|
|
34
|
+
.gsub("`", BACKTICK_PLACEHOLDER)
|
|
35
|
+
.gsub("[!code", CODE_MARKER_PLACEHOLDER)
|
|
36
|
+
|
|
37
|
+
# Output as regular 3-backtick fence so it goes through normal processing
|
|
38
|
+
lang_spec = lang.empty? ? "text" : lang
|
|
39
|
+
"```#{lang_spec}\n#{escaped_code}\n```"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../base_processor"
|
|
4
|
+
require_relative "../../rendering/icons"
|
|
4
5
|
|
|
5
6
|
module Docyard
|
|
6
7
|
module Components
|
|
@@ -11,6 +12,7 @@ module Docyard
|
|
|
11
12
|
CODE_FENCE_REGEX = /^```(\w+)(?:\s*\[([^\]]+)\])?(:\S+)?(?:\s*\{([^}\n]+)\})?/
|
|
12
13
|
TABS_BLOCK_REGEX = /^:::[ \t]*tabs[ \t]*\n.*?^:::[ \t]*$/m
|
|
13
14
|
CODE_GROUP_BLOCK_REGEX = /^:::[ \t]*code-group[ \t]*\n.*?^:::[ \t]*$/m
|
|
15
|
+
EXCLUDED_LANGUAGES = %w[filetree].freeze
|
|
14
16
|
|
|
15
17
|
def preprocess(content)
|
|
16
18
|
context[:code_block_options] ||= []
|
|
@@ -40,8 +42,16 @@ module Docyard
|
|
|
40
42
|
position = match.begin(0)
|
|
41
43
|
return match[0] if inside_special_block?(position)
|
|
42
44
|
|
|
45
|
+
original_lang = match[1]
|
|
46
|
+
return match[0] if excluded_language?(original_lang)
|
|
47
|
+
|
|
43
48
|
store_code_block_options(match)
|
|
44
|
-
|
|
49
|
+
highlight_lang = Icons.highlight_language(original_lang)
|
|
50
|
+
"```#{highlight_lang}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def excluded_language?(lang)
|
|
54
|
+
EXCLUDED_LANGUAGES.include?(lang&.downcase)
|
|
45
55
|
end
|
|
46
56
|
|
|
47
57
|
def inside_special_block?(position)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../rendering/icons"
|
|
4
|
-
require_relative "../../rendering/language_mapping"
|
|
5
4
|
require_relative "../../rendering/renderer"
|
|
6
5
|
require_relative "../base_processor"
|
|
7
6
|
require_relative "../support/code_block/icon_detector"
|
|
8
7
|
require_relative "../support/code_block/line_wrapper"
|
|
8
|
+
require_relative "../support/code_block/line_number_resolver"
|
|
9
9
|
require_relative "../support/tabs/range_finder"
|
|
10
10
|
|
|
11
11
|
module Docyard
|
|
@@ -18,6 +18,7 @@ module Docyard
|
|
|
18
18
|
self.priority = 20
|
|
19
19
|
|
|
20
20
|
CodeBlockIconDetector = Support::CodeBlock::IconDetector
|
|
21
|
+
LineNumbers = Support::CodeBlock::LineNumberResolver
|
|
21
22
|
TabsRangeFinder = Support::Tabs::RangeFinder
|
|
22
23
|
|
|
23
24
|
def postprocess(html)
|
|
@@ -36,7 +37,6 @@ module Docyard
|
|
|
36
37
|
@focus_lines = context[:code_block_focus_lines] || []
|
|
37
38
|
@error_lines = context[:code_block_error_lines] || []
|
|
38
39
|
@warning_lines = context[:code_block_warning_lines] || []
|
|
39
|
-
@global_line_numbers = context.dig(:config, "markdown", "lineNumbers") || false
|
|
40
40
|
@tabs_ranges = TabsRangeFinder.find_ranges(html)
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -80,8 +80,8 @@ module Docyard
|
|
|
80
80
|
def extract_block_data(inner_html)
|
|
81
81
|
opts = current_block_options
|
|
82
82
|
code_text = extract_code_text(inner_html)
|
|
83
|
-
start_line =
|
|
84
|
-
show_line_numbers =
|
|
83
|
+
start_line = LineNumbers.start_line(opts[:option])
|
|
84
|
+
show_line_numbers = LineNumbers.enabled?(opts[:option])
|
|
85
85
|
title_data = CodeBlockIconDetector.detect(opts[:title], opts[:lang])
|
|
86
86
|
|
|
87
87
|
build_block_data(code_text, opts, show_line_numbers, start_line, title_data)
|
|
@@ -106,7 +106,7 @@ module Docyard
|
|
|
106
106
|
error_lines: @error_lines[@block_index] || {},
|
|
107
107
|
warning_lines: @warning_lines[@block_index] || {},
|
|
108
108
|
show_line_numbers: show_line_numbers,
|
|
109
|
-
line_numbers: show_line_numbers ?
|
|
109
|
+
line_numbers: show_line_numbers ? LineNumbers.generate_numbers(code_text, start_line) : [],
|
|
110
110
|
start_line: start_line,
|
|
111
111
|
title: title_data[:title],
|
|
112
112
|
icon: title_data[:icon],
|
|
@@ -123,25 +123,6 @@ module Docyard
|
|
|
123
123
|
wrap_code_block(original_html, block_data)
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
-
def determine_line_numbers(block_option)
|
|
127
|
-
return false if block_option == ":no-line-numbers"
|
|
128
|
-
return true if block_option&.start_with?(":line-numbers")
|
|
129
|
-
|
|
130
|
-
@global_line_numbers
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def extract_start_line(block_option)
|
|
134
|
-
return 1 unless block_option&.include?("=")
|
|
135
|
-
|
|
136
|
-
block_option.split("=").last.to_i
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def generate_line_numbers(code_text, start_line)
|
|
140
|
-
line_count = code_text.lines.count
|
|
141
|
-
line_count = 1 if line_count.zero?
|
|
142
|
-
(start_line...(start_line + line_count)).to_a
|
|
143
|
-
end
|
|
144
|
-
|
|
145
126
|
def inside_tabs?(position)
|
|
146
127
|
@tabs_ranges.any? { |range| range.cover?(position) }
|
|
147
128
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../base_processor"
|
|
4
4
|
require_relative "../support/code_block/feature_extractor"
|
|
5
5
|
require_relative "../support/code_block/line_wrapper"
|
|
6
|
+
require_relative "../support/code_block/line_number_resolver"
|
|
6
7
|
require_relative "../support/code_group/html_builder"
|
|
7
8
|
require_relative "../support/markdown_code_block_helper"
|
|
8
9
|
require_relative "../../rendering/icons"
|
|
@@ -26,6 +27,7 @@ module Docyard
|
|
|
26
27
|
|
|
27
28
|
CodeBlockFeatureExtractor = Support::CodeBlock::FeatureExtractor
|
|
28
29
|
CodeBlockLineWrapper = Support::CodeBlock::LineWrapper
|
|
30
|
+
LineNumbers = Support::CodeBlock::LineNumberResolver
|
|
29
31
|
CodeGroupHtmlBuilder = Support::CodeGroup::HtmlBuilder
|
|
30
32
|
|
|
31
33
|
def preprocess(content)
|
|
@@ -136,7 +138,7 @@ module Docyard
|
|
|
136
138
|
focus_lines: block_data[:focus_lines] || {},
|
|
137
139
|
error_lines: block_data[:error_lines] || {},
|
|
138
140
|
warning_lines: block_data[:warning_lines] || {},
|
|
139
|
-
start_line:
|
|
141
|
+
start_line: LineNumbers.start_line(block_data[:option])
|
|
140
142
|
}
|
|
141
143
|
end
|
|
142
144
|
|
|
@@ -145,13 +147,13 @@ module Docyard
|
|
|
145
147
|
end
|
|
146
148
|
|
|
147
149
|
def base_locals(processed_html, code_text, block_data)
|
|
148
|
-
show_ln =
|
|
149
|
-
start =
|
|
150
|
+
show_ln = LineNumbers.enabled?(block_data[:option])
|
|
151
|
+
start = LineNumbers.start_line(block_data[:option])
|
|
150
152
|
|
|
151
153
|
{
|
|
152
154
|
code_block_html: processed_html, code_text: escape_html_attribute(code_text),
|
|
153
155
|
copy_icon: Icons.render("copy", "regular") || "", show_line_numbers: show_ln,
|
|
154
|
-
line_numbers: show_ln ?
|
|
156
|
+
line_numbers: show_ln ? LineNumbers.generate_numbers(code_text, start) : [], start_line: start
|
|
155
157
|
}
|
|
156
158
|
end
|
|
157
159
|
|
|
@@ -167,24 +169,6 @@ module Docyard
|
|
|
167
169
|
{ title: nil, icon: nil, icon_source: nil }
|
|
168
170
|
end
|
|
169
171
|
|
|
170
|
-
def line_numbers_enabled?(option)
|
|
171
|
-
return false if option == ":no-line-numbers"
|
|
172
|
-
return true if option&.start_with?(":line-numbers")
|
|
173
|
-
|
|
174
|
-
false
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def extract_start_line(option)
|
|
178
|
-
return 1 unless option&.include?("=")
|
|
179
|
-
|
|
180
|
-
option.split("=").last.to_i
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def generate_line_numbers(code_text, start_line)
|
|
184
|
-
count = [code_text.lines.count, 1].max
|
|
185
|
-
(start_line...(start_line + count)).to_a
|
|
186
|
-
end
|
|
187
|
-
|
|
188
172
|
def extract_code_text(html)
|
|
189
173
|
CGI.unescapeHTML(html.gsub(/<[^>]+>/, "")).strip
|
|
190
174
|
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../base_processor"
|
|
4
4
|
require_relative "../support/markdown_code_block_helper"
|
|
5
|
-
require_relative "../../rendering/icons"
|
|
6
5
|
|
|
7
6
|
module Docyard
|
|
8
7
|
module Components
|
|
@@ -135,7 +134,7 @@ module Docyard
|
|
|
135
134
|
|
|
136
135
|
def icon_for(type)
|
|
137
136
|
icon_name = type == :folder ? "folder-open" : "file-text"
|
|
138
|
-
|
|
137
|
+
%(<i class="ph ph-#{icon_name}" aria-hidden="true"></i>)
|
|
139
138
|
end
|
|
140
139
|
|
|
141
140
|
def escape_html(text)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../rendering/icons"
|
|
4
3
|
require_relative "../base_processor"
|
|
5
4
|
|
|
6
5
|
module Docyard
|
|
@@ -10,6 +9,7 @@ module Docyard
|
|
|
10
9
|
self.priority = 20
|
|
11
10
|
|
|
12
11
|
ICON_PATTERN = /:([a-z][a-z0-9-]*):(?:([a-z]+):)?/i
|
|
12
|
+
VALID_WEIGHTS = %w[regular bold fill light thin duotone].freeze
|
|
13
13
|
|
|
14
14
|
def postprocess(html)
|
|
15
15
|
segments = split_preserving_code_blocks(html)
|
|
@@ -44,9 +44,15 @@ module Docyard
|
|
|
44
44
|
content.gsub(ICON_PATTERN) do
|
|
45
45
|
icon_name = Regexp.last_match(1)
|
|
46
46
|
weight = Regexp.last_match(2) || "regular"
|
|
47
|
-
|
|
47
|
+
render_icon(icon_name, weight)
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
|
+
|
|
51
|
+
def render_icon(name, weight)
|
|
52
|
+
weight = "regular" unless VALID_WEIGHTS.include?(weight)
|
|
53
|
+
weight_class = weight == "regular" ? "ph" : "ph-#{weight}"
|
|
54
|
+
%(<i class="#{weight_class} ph-#{name}" aria-hidden="true"></i>)
|
|
55
|
+
end
|
|
50
56
|
end
|
|
51
57
|
end
|
|
52
58
|
end
|