docyard 0.3.0 → 0.4.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -1
  3. data/README.md +55 -33
  4. data/lib/docyard/build/asset_bundler.rb +139 -0
  5. data/lib/docyard/build/file_copier.rb +105 -0
  6. data/lib/docyard/build/sitemap_generator.rb +57 -0
  7. data/lib/docyard/build/static_generator.rb +141 -0
  8. data/lib/docyard/builder.rb +104 -0
  9. data/lib/docyard/cli.rb +19 -0
  10. data/lib/docyard/components/table_wrapper_processor.rb +18 -0
  11. data/lib/docyard/config.rb +4 -2
  12. data/lib/docyard/icons/phosphor.rb +1 -0
  13. data/lib/docyard/initializer.rb +80 -14
  14. data/lib/docyard/markdown.rb +13 -0
  15. data/lib/docyard/preview_server.rb +72 -0
  16. data/lib/docyard/rack_application.rb +1 -1
  17. data/lib/docyard/renderer.rb +17 -3
  18. data/lib/docyard/sidebar/config_parser.rb +180 -0
  19. data/lib/docyard/sidebar/item.rb +58 -0
  20. data/lib/docyard/sidebar/renderer.rb +33 -6
  21. data/lib/docyard/sidebar_builder.rb +45 -1
  22. data/lib/docyard/templates/assets/css/components/callout.css +1 -1
  23. data/lib/docyard/templates/assets/css/components/code-block.css +2 -2
  24. data/lib/docyard/templates/assets/css/components/navigation.css +65 -7
  25. data/lib/docyard/templates/assets/css/components/tabs.css +3 -2
  26. data/lib/docyard/templates/assets/css/components/theme-toggle.css +8 -0
  27. data/lib/docyard/templates/assets/css/markdown.css +20 -11
  28. data/lib/docyard/templates/assets/js/components/navigation.js +221 -0
  29. data/lib/docyard/templates/assets/js/theme.js +2 -185
  30. data/lib/docyard/templates/config/docyard.yml.erb +32 -10
  31. data/lib/docyard/templates/layouts/default.html.erb +1 -1
  32. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +46 -12
  33. data/lib/docyard/templates/markdown/guides/configuration.md.erb +202 -0
  34. data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +247 -0
  35. data/lib/docyard/templates/markdown/index.md.erb +55 -59
  36. data/lib/docyard/templates/partials/_nav_group.html.erb +10 -4
  37. data/lib/docyard/templates/partials/_nav_leaf.html.erb +9 -1
  38. data/lib/docyard/version.rb +1 -1
  39. data/lib/docyard.rb +8 -0
  40. metadata +55 -10
  41. data/lib/docyard/templates/markdown/components/callouts.md.erb +0 -204
  42. data/lib/docyard/templates/markdown/components/icons.md.erb +0 -125
  43. data/lib/docyard/templates/markdown/components/tabs.md.erb +0 -686
  44. data/lib/docyard/templates/markdown/configuration.md.erb +0 -202
  45. data/lib/docyard/templates/markdown/core-concepts/file-structure.md.erb +0 -61
  46. data/lib/docyard/templates/markdown/core-concepts/markdown.md.erb +0 -90
  47. data/lib/docyard/templates/markdown/getting-started/introduction.md.erb +0 -30
  48. data/lib/docyard/templates/markdown/getting-started/quick-start.md.erb +0 -56
  49. data/lib/docyard/templates/partials/_icons.html.erb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eab96fe7bde5b0471a5a60996f818f5909d159e34a96034506e5095730e64988
4
- data.tar.gz: 319e4251a18b40c28960c2854079420feac035e472bf9f5f03845cccfc310879
3
+ metadata.gz: db5ddabfe4a3b7bf25c24876b915188bd4768ca543772ad0a8cfc971ee791fee
4
+ data.tar.gz: d906b1c2ab33102c9ade6e2cb23f6d067fdbf240d6a4a8e2aa27a52a9ba62b42
5
5
  SHA512:
6
- metadata.gz: 25c8f7d91d19d3905141894e7cf8f2968b7d7d20343f9fda48e5f105f952ba3f627227063a888dd70b7c17187950ac83e649196afbbde215e643e5f723157c1f
7
- data.tar.gz: 338baa09ab053ffba20f745263f6bed124276ce707d8b2adc4d96ebf0a18b0b195f7097edfba8ffcde9265abe7cda3eaefd6710f01e3bf71b3836b87c21eb429
6
+ metadata.gz: 2abc1d991cade7059cfaa0b54c2b6f88b33625937e8073cee9d8f61948149567a209a378efd225e9fe85b45b7579ef4e1d81599be5a8cbee970e9cb6e5218a3c
7
+ data.tar.gz: dcd2baa65fcdd4c00825c6f895c035ffd53603ec22503233b600925084b6512b651c9a009687682f1f5cab3a0c8d8e3d0a5c7586634d6da174e4e4c5a95f9a53
data/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2025-01-16
11
+
12
+ ### Added
13
+ - **Static site generation** - Build system with `docyard build` command (#27)
14
+ - **Preview server** - Test builds locally with `docyard preview` command (#27)
15
+ - **Asset bundling** - CSS/JS minification with content hashing for cache busting (#27)
16
+ - **SEO files** - Automatic generation of sitemap.xml and robots.txt (#27)
17
+ - **Base URL support** - Deploy to subdirectories with configurable base_url (#27)
18
+ - **Sidebar customization** - Config-driven navigation with custom ordering, icons, and external links (#26)
19
+ - **Improved init templates** - Practical, helpful templates showcasing all features (#28)
20
+ - **Clean init output** - Minimal, helpful success message with clear next steps (#28)
21
+
22
+ ### Changed
23
+ - Init command now creates focused, practical templates (4 files vs 9 previously) (#28)
24
+ - Templates now only include implemented features (no images/HTML/escaping) (#28)
25
+ - Config file (docyard.yml) is cleaner with better comments and examples (#28)
26
+
27
+ ### Fixed
28
+ - Code block CSS transition performance with GPU acceleration (#25)
29
+ - Component CSS accessibility and performance improvements (#24)
30
+ - Table responsive styling with proper wrapper element (#23)
31
+
10
32
  ## [0.3.0] - 2025-01-09
11
33
 
12
34
  ### Added
@@ -63,7 +85,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
63
85
  - Initial gem structure
64
86
  - Project scaffolding
65
87
 
66
- [Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.3.0...HEAD
88
+ [Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.4.0...HEAD
89
+ [0.4.0]: https://github.com/sanifhimani/docyard/compare/v0.3.0...v0.4.0
67
90
  [0.3.0]: https://github.com/sanifhimani/docyard/compare/v0.2.0...v0.3.0
68
91
  [0.2.0]: https://github.com/sanifhimani/docyard/compare/v0.1.0...v0.2.0
69
92
  [0.1.0]: https://github.com/sanifhimani/docyard/compare/v0.0.1...v0.1.0
data/README.md CHANGED
@@ -5,41 +5,57 @@
5
5
 
6
6
  > Documentation generator for Ruby
7
7
 
8
- **Early development** - Core features and components work, but missing search and build command. See [roadmap](#roadmap).
8
+ Build beautiful documentation sites with hot reload, dark mode, and powerful markdown components.
9
9
 
10
10
  ## Features
11
11
 
12
- - **Configuration system** - Optional `docyard.yml` for site metadata, branding, and build settings
12
+ ### Core
13
+ - **Static site generation** - Build static sites with `docyard build`
14
+ - **Hot reload** - Changes appear instantly while you write
13
15
  - **Dark mode** - Beautiful light/dark theme with system preference detection
16
+ - **Configuration system** - Optional `docyard.yml` for site metadata, branding, and build settings
17
+ - **Custom branding** - Logo and favicon with light/dark mode support
18
+ - **Base URL support** - Deploy to subdirectories or custom paths
19
+
20
+ ### Navigation
14
21
  - **Sidebar navigation** - Automatic sidebar with nested folders and collapsible sections
15
- - **Hot reload** - Changes appear instantly while you write
22
+ - **Sidebar customization** - Custom ordering, icons, and external links via config
23
+ - **Active page highlighting** - Always know where you are
24
+
25
+ ### Markdown
16
26
  - **GitHub Flavored Markdown** - Tables, task lists, strikethrough
17
- - **Syntax highlighting** - 100+ languages via Rouge
27
+ - **Syntax highlighting** - 100+ languages via Rouge with copy button
18
28
  - **Markdown components**:
19
29
  - **Callouts** - 5 types (note, tip, important, warning, danger) with GitHub alerts syntax
20
30
  - **Tabs** - Code blocks, package managers, and custom tabs with keyboard navigation
21
31
  - **Icons** - 24 Phosphor icons with `:icon:` syntax
22
- - **Code block enhancements** - Copy button with visual feedback
23
- - **Custom branding** - Logo and favicon with light/dark mode support
24
32
  - **YAML frontmatter** - Add metadata to your pages
25
- - **Customizable error pages** - Make 404/500 pages your own
33
+
34
+ ### Production
35
+ - **Asset bundling** - Minified CSS/JS with content hashing for cache busting
36
+ - **SEO** - Automatic sitemap.xml and robots.txt generation
37
+ - **Preview server** - Test production builds locally before deploying
38
+ - **Mobile responsive** - Looks great on all devices
26
39
 
27
40
  ## Quick Start
28
41
 
29
42
  ```bash
30
- # Install the gem
43
+ # Install
31
44
  gem install docyard
32
45
 
33
- # Create a new docs project
34
- mkdir my-docs && cd my-docs
46
+ # Initialize
35
47
  docyard init
36
48
 
37
- # Start the dev server
49
+ # Start dev server
38
50
  docyard serve
51
+ # → http://localhost:4200
39
52
 
40
- # Visit http://localhost:4200
53
+ # Build for production
54
+ docyard build
41
55
  ```
42
56
 
57
+ Your site is ready to deploy! Upload the `dist/` folder to any static host.
58
+
43
59
  ## Installation
44
60
 
45
61
  Add to your Gemfile:
@@ -67,21 +83,27 @@ This creates:
67
83
  docs/
68
84
  index.md # Home page
69
85
  getting-started/
70
- introduction.md # Getting started guide
71
- installation.md # Installation instructions
72
- quick-start.md # Quick start guide
73
- core-concepts/
74
- file-structure.md # File structure guide
75
- markdown.md # Markdown guide
86
+ installation.md # Installation guide
87
+ guides/
88
+ markdown-features.md # Markdown features showcase
89
+ configuration.md # Configuration guide
90
+ docyard.yml # Optional configuration
76
91
  ```
77
92
 
78
- ### Start Development Server
93
+ ### Commands
79
94
 
80
95
  ```bash
96
+ # Development server with hot reload
81
97
  docyard serve
82
-
83
- # Custom port and host
84
98
  docyard serve --port 3000 --host 0.0.0.0
99
+
100
+ # Build for production
101
+ docyard build
102
+ docyard build --no-clean # Don't clean output directory
103
+
104
+ # Preview production build
105
+ docyard preview
106
+ docyard preview --port 4001
85
107
  ```
86
108
 
87
109
  ### Writing Docs
@@ -205,18 +227,18 @@ bundle exec rubocop
205
227
 
206
228
  ## Roadmap
207
229
 
208
- **v0.3.0 - Recently shipped:**
209
- - Configuration system (docyard.yml)
210
- - Logo and favicon support
211
- - Dark mode with theme toggle
212
- - Icon system (24 Phosphor icons)
213
- - Callouts/Admonitions
214
- - Tabs component
215
- - Copy button for code blocks
216
-
217
- **Next up (v0.4.0):**
218
- - Sidebar customization
219
- - Static site generation (`docyard build`)
230
+ **v0.4.0 - Just shipped:**
231
+ - Static site generation with `docyard build`
232
+ - Asset bundling with minification and cache busting
233
+ - SEO files (sitemap.xml, robots.txt)
234
+ - Preview server for testing builds
235
+ - Sidebar customization via config
236
+ - Improved init templates
237
+
238
+ **Next up:**
239
+ - Search functionality
240
+ - Table of contents
241
+ - More markdown components
220
242
 
221
243
  ## Contributing
222
244
 
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cssminify"
4
+ require "terser"
5
+ require "digest"
6
+
7
+ module Docyard
8
+ module Build
9
+ class AssetBundler
10
+ ASSETS_PATH = File.join(__dir__, "..", "templates", "assets")
11
+
12
+ attr_reader :config, :verbose
13
+
14
+ def initialize(config, verbose: false)
15
+ @config = config
16
+ @verbose = verbose
17
+ end
18
+
19
+ def bundle
20
+ puts "\nBundling assets..."
21
+
22
+ css_hash = bundle_css
23
+ js_hash = bundle_js
24
+
25
+ update_html_references(css_hash, js_hash)
26
+
27
+ 2
28
+ end
29
+
30
+ private
31
+
32
+ def bundle_css
33
+ log " Bundling CSS..."
34
+
35
+ main_css = File.read(File.join(ASSETS_PATH, "css", "main.css"))
36
+ css_content = resolve_css_imports(main_css)
37
+ minified = CSSminify.compress(css_content)
38
+ hash = generate_hash(minified)
39
+
40
+ write_bundled_asset(minified, hash, "css")
41
+ log_compression_stats(css_content, minified, "CSS")
42
+
43
+ hash
44
+ end
45
+
46
+ def resolve_css_imports(css_content)
47
+ css_content.gsub(/@import url\('([^']+)'\);/) do |match|
48
+ import_file = Regexp.last_match(1)
49
+
50
+ if import_file == "components.css"
51
+ concatenate_component_css
52
+ else
53
+ file_path = File.join(ASSETS_PATH, "css", import_file)
54
+ File.exist?(file_path) ? File.read(file_path) : match
55
+ end
56
+ end
57
+ end
58
+
59
+ def concatenate_component_css
60
+ components_dir = File.join(ASSETS_PATH, "css", "components")
61
+ return "" unless Dir.exist?(components_dir)
62
+
63
+ css_files = Dir.glob(File.join(components_dir, "*.css"))
64
+ css_files.map { |file| File.read(file) }.join("\n\n")
65
+ end
66
+
67
+ def bundle_js
68
+ log " Bundling JS..."
69
+
70
+ theme_js = File.read(File.join(ASSETS_PATH, "js", "theme.js"))
71
+ components_js = concatenate_component_js
72
+ js_content = [theme_js, components_js].join("\n")
73
+ minified = Terser.compile(js_content)
74
+ hash = generate_hash(minified)
75
+
76
+ write_bundled_asset(minified, hash, "js")
77
+ log_compression_stats(js_content, minified, "JS")
78
+
79
+ hash
80
+ end
81
+
82
+ def concatenate_component_js
83
+ components_dir = File.join(ASSETS_PATH, "js", "components")
84
+ return "" unless Dir.exist?(components_dir)
85
+
86
+ js_files = Dir.glob(File.join(components_dir, "*.js"))
87
+ js_files.map { |file| File.read(file) }.join("\n\n")
88
+ end
89
+
90
+ def generate_hash(content)
91
+ Digest::MD5.hexdigest(content)[0..7]
92
+ end
93
+
94
+ def update_html_references(css_hash, js_hash)
95
+ html_files = Dir.glob(File.join(config.build.output_dir, "**", "*.html"))
96
+ base_url = normalize_base_url(config.build.base_url)
97
+
98
+ html_files.each do |file|
99
+ content = replace_asset_references(File.read(file), css_hash, js_hash, base_url)
100
+ File.write(file, content)
101
+ end
102
+
103
+ log " [✓] Updated asset references in #{html_files.size} HTML files"
104
+ end
105
+
106
+ def replace_asset_references(content, css_hash, js_hash, base_url)
107
+ content.gsub(%r{/assets/css/main\.css}, "#{base_url}assets/bundle.#{css_hash}.css")
108
+ .gsub(%r{/assets/js/theme\.js}, "#{base_url}assets/bundle.#{js_hash}.js")
109
+ .gsub(%r{/assets/js/components\.js}, "")
110
+ .gsub(%r{<script src="/assets/js/reload\.js"></script>}, "")
111
+ end
112
+
113
+ def write_bundled_asset(content, hash, extension)
114
+ filename = "bundle.#{hash}.#{extension}"
115
+ output_path = File.join(config.build.output_dir, "assets", filename)
116
+ FileUtils.mkdir_p(File.dirname(output_path))
117
+ File.write(output_path, content)
118
+ end
119
+
120
+ def log_compression_stats(original, minified, label)
121
+ original_size = (original.bytesize / 1024.0).round(1)
122
+ minified_size = (minified.bytesize / 1024.0).round(1)
123
+ reduction = (((original_size - minified_size) / original_size) * 100).round(0)
124
+ log " [✓] #{label}: #{original_size} KB -> #{minified_size} KB (-#{reduction}%)"
125
+ end
126
+
127
+ def normalize_base_url(url)
128
+ return "/" if url.nil? || url.empty? || url == "/"
129
+
130
+ url = "/#{url}" unless url.start_with?("/")
131
+ url.end_with?("/") ? url : "#{url}/"
132
+ end
133
+
134
+ def log(message)
135
+ puts message
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Build
5
+ class FileCopier
6
+ attr_reader :config, :verbose
7
+
8
+ def initialize(config, verbose: false)
9
+ @config = config
10
+ @verbose = verbose
11
+ end
12
+
13
+ def copy
14
+ puts "\nCopying static assets..."
15
+
16
+ count = 0
17
+ count += copy_user_assets
18
+ count += copy_branding_assets
19
+
20
+ log "[✓] Copied #{count} static files"
21
+ count
22
+ end
23
+
24
+ private
25
+
26
+ def copy_user_assets
27
+ user_assets_dir = "docs/assets"
28
+ return 0 unless Dir.exist?(user_assets_dir)
29
+
30
+ output_assets_dir = File.join(config.build.output_dir, "assets")
31
+ FileUtils.mkdir_p(output_assets_dir)
32
+
33
+ files = find_user_asset_files(user_assets_dir)
34
+ files.each { |file| copy_single_asset(file, "docs/assets/", output_assets_dir) }
35
+
36
+ log "[✓] Copied #{files.size} user assets from docs/assets/" if files.any?
37
+ files.size
38
+ end
39
+
40
+ def find_user_asset_files(assets_dir)
41
+ Dir.glob(File.join(assets_dir, "**", "*")).select { |f| File.file?(f) }
42
+ end
43
+
44
+ def copy_single_asset(file, prefix, output_dir)
45
+ relative_path = file.delete_prefix(prefix)
46
+ dest_path = File.join(output_dir, relative_path)
47
+
48
+ FileUtils.mkdir_p(File.dirname(dest_path))
49
+ FileUtils.cp(file, dest_path)
50
+
51
+ log " Copied: #{relative_path}" if verbose
52
+ end
53
+
54
+ def copy_branding_assets
55
+ count = 0
56
+ count += copy_default_branding_assets
57
+ count += copy_user_branding_assets
58
+ log "[✓] Copied #{count} branding assets" if count.positive?
59
+ count
60
+ end
61
+
62
+ def copy_default_branding_assets
63
+ templates_assets = File.join(__dir__, "..", "templates", "assets")
64
+ count = 0
65
+
66
+ ["logo.svg", "logo-dark.svg", "favicon.svg"].each do |asset_file|
67
+ source_path = File.join(templates_assets, asset_file)
68
+ next unless File.exist?(source_path)
69
+
70
+ dest_path = File.join(config.build.output_dir, "assets", asset_file)
71
+ FileUtils.mkdir_p(File.dirname(dest_path))
72
+ FileUtils.cp(source_path, dest_path)
73
+
74
+ log " Copied default branding: #{asset_file}" if verbose
75
+ count += 1
76
+ end
77
+
78
+ count
79
+ end
80
+
81
+ def copy_user_branding_assets
82
+ %w[logo logo_dark favicon].sum { |asset_key| copy_single_branding_asset(asset_key) }
83
+ end
84
+
85
+ def copy_single_branding_asset(asset_key)
86
+ asset_path = config.branding.send(asset_key)
87
+ return 0 if asset_path.nil? || asset_path.start_with?("http://", "https://")
88
+
89
+ full_path = File.join("docs", asset_path)
90
+ return 0 unless File.exist?(full_path)
91
+
92
+ dest_path = File.join(config.build.output_dir, "assets", asset_path)
93
+ FileUtils.mkdir_p(File.dirname(dest_path))
94
+ FileUtils.cp(full_path, dest_path)
95
+
96
+ log " Copied user branding: #{asset_path}" if verbose
97
+ 1
98
+ end
99
+
100
+ def log(message)
101
+ puts message
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module Docyard
6
+ module Build
7
+ class SitemapGenerator
8
+ attr_reader :config
9
+
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def generate
15
+ urls = collect_urls
16
+ sitemap_content = build_sitemap(urls)
17
+
18
+ output_path = File.join(config.build.output_dir, "sitemap.xml")
19
+ File.write(output_path, sitemap_content)
20
+
21
+ puts "[✓] Generated sitemap.xml (#{urls.size} URLs)"
22
+ end
23
+
24
+ private
25
+
26
+ def collect_urls
27
+ html_files = Dir.glob(File.join(config.build.output_dir, "**", "index.html"))
28
+
29
+ html_files.map do |file|
30
+ relative_path = file.delete_prefix(config.build.output_dir).delete_suffix("/index.html")
31
+ url_path = relative_path.empty? ? "/" : relative_path
32
+ lastmod = File.mtime(file).utc.iso8601
33
+
34
+ { loc: url_path, lastmod: lastmod }
35
+ end
36
+ end
37
+
38
+ def build_sitemap(urls)
39
+ base_url = config.build.base_url
40
+ base_url = base_url.chop if base_url.end_with?("/")
41
+
42
+ xml = ['<?xml version="1.0" encoding="UTF-8"?>']
43
+ xml << '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
44
+
45
+ urls.each do |url|
46
+ xml << " <url>"
47
+ xml << " <loc>#{base_url}#{url[:loc]}</loc>"
48
+ xml << " <lastmod>#{url[:lastmod]}</lastmod>"
49
+ xml << " </url>"
50
+ end
51
+
52
+ xml << "</urlset>"
53
+ xml.join("\n")
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-progressbar"
4
+
5
+ module Docyard
6
+ module Build
7
+ class StaticGenerator
8
+ attr_reader :config, :verbose, :renderer
9
+
10
+ def initialize(config, verbose: false)
11
+ @config = config
12
+ @verbose = verbose
13
+ @renderer = Renderer.new(base_url: config.build.base_url)
14
+ end
15
+
16
+ def generate
17
+ markdown_files = collect_markdown_files
18
+ puts "\n[✓] Found #{markdown_files.size} markdown files"
19
+
20
+ progress = TTY::ProgressBar.new(
21
+ "Generating pages [:bar] :current/:total (:percent%)",
22
+ total: markdown_files.size,
23
+ width: 50
24
+ )
25
+
26
+ markdown_files.each do |file_path|
27
+ generate_page(file_path)
28
+ progress.advance
29
+ end
30
+
31
+ markdown_files.size
32
+ end
33
+
34
+ private
35
+
36
+ def collect_markdown_files
37
+ Dir.glob(File.join("docs", "**", "*.md"))
38
+ end
39
+
40
+ def generate_page(markdown_file_path)
41
+ output_path = determine_output_path(markdown_file_path)
42
+ current_path = determine_current_path(markdown_file_path)
43
+
44
+ sidebar_html = build_sidebar(current_path)
45
+ html_content = renderer.render_file(
46
+ markdown_file_path,
47
+ sidebar_html: sidebar_html,
48
+ branding: branding_options
49
+ )
50
+
51
+ FileUtils.mkdir_p(File.dirname(output_path))
52
+ File.write(output_path, html_content)
53
+
54
+ log "Generated: #{output_path}" if verbose
55
+ end
56
+
57
+ def determine_output_path(markdown_file_path)
58
+ relative_path = markdown_file_path.delete_prefix("docs/")
59
+ base_name = File.basename(relative_path, ".md")
60
+ dir_name = File.dirname(relative_path)
61
+
62
+ output_dir = config.build.output_dir
63
+
64
+ if base_name == "index"
65
+ File.join(output_dir, dir_name, "index.html")
66
+ else
67
+ File.join(output_dir, dir_name, base_name, "index.html")
68
+ end
69
+ end
70
+
71
+ def determine_current_path(markdown_file_path)
72
+ relative_path = markdown_file_path.delete_prefix("docs/")
73
+ base_name = File.basename(relative_path, ".md")
74
+ dir_name = File.dirname(relative_path)
75
+
76
+ if base_name == "index"
77
+ dir_name == "." ? "/" : "/#{dir_name}"
78
+ else
79
+ dir_name == "." ? "/#{base_name}" : "/#{dir_name}/#{base_name}"
80
+ end
81
+ end
82
+
83
+ def build_sidebar(current_path)
84
+ SidebarBuilder.new(
85
+ docs_path: "docs",
86
+ current_path: current_path,
87
+ config: config
88
+ ).to_html
89
+ end
90
+
91
+ def branding_options
92
+ default_branding.merge(config_branding_options)
93
+ end
94
+
95
+ def default_branding
96
+ {
97
+ site_title: Constants::DEFAULT_SITE_TITLE,
98
+ site_description: "",
99
+ logo: Constants::DEFAULT_LOGO_PATH,
100
+ logo_dark: Constants::DEFAULT_LOGO_DARK_PATH,
101
+ favicon: nil,
102
+ display_logo: true,
103
+ display_title: true
104
+ }
105
+ end
106
+
107
+ def config_branding_options
108
+ site = config.site
109
+ branding = config.branding
110
+
111
+ {
112
+ site_title: site.title || Constants::DEFAULT_SITE_TITLE,
113
+ site_description: site.description || "",
114
+ logo: resolve_logo(branding.logo, branding.logo_dark),
115
+ logo_dark: resolve_logo_dark(branding.logo, branding.logo_dark),
116
+ favicon: branding.favicon
117
+ }.merge(appearance_options(branding.appearance))
118
+ end
119
+
120
+ def appearance_options(appearance)
121
+ appearance ||= {}
122
+ {
123
+ display_logo: appearance["logo"] != false,
124
+ display_title: appearance["title"] != false
125
+ }
126
+ end
127
+
128
+ def resolve_logo(logo, logo_dark)
129
+ logo || logo_dark || Constants::DEFAULT_LOGO_PATH
130
+ end
131
+
132
+ def resolve_logo_dark(logo, logo_dark)
133
+ logo_dark || logo || Constants::DEFAULT_LOGO_DARK_PATH
134
+ end
135
+
136
+ def log(message)
137
+ puts message if verbose
138
+ end
139
+ end
140
+ end
141
+ end