docyard 0.9.0 → 1.0.1
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 +57 -1
- data/README.md +8 -253
- data/exe/docyard +6 -0
- data/lib/docyard/build/asset_bundler.rb +24 -2
- data/lib/docyard/build/error_page_generator.rb +33 -0
- data/lib/docyard/build/file_copier.rb +12 -5
- data/lib/docyard/build/file_writer.rb +19 -0
- data/lib/docyard/build/llms_txt_generator.rb +103 -0
- data/lib/docyard/build/root_fallback_generator.rb +66 -0
- data/lib/docyard/build/sitemap_generator.rb +1 -1
- data/lib/docyard/build/static_generator.rb +119 -81
- 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/breadcrumb_builder.rb +45 -6
- 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 +96 -61
- 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 +39 -71
- 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/heading-anchor.css +2 -2
- 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 +9 -6
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +63 -17
- 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 +32 -16
- data/lib/docyard/templates/assets/css/markdown.css +22 -2
- data/lib/docyard/templates/assets/css/variables.css +14 -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 +125 -5
- data/lib/docyard/templates/errors/500.html.erb +184 -10
- data/lib/docyard/templates/errors/redirect.html.erb +12 -0
- 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 +80 -5
- 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 +81 -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
data/lib/docyard/config.rb
CHANGED
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
|
+
require_relative "config/section"
|
|
5
|
+
require_relative "config/schema"
|
|
4
6
|
require_relative "config/validator"
|
|
5
|
-
require_relative "
|
|
7
|
+
require_relative "constants"
|
|
8
|
+
require_relative "utils/hash_utils"
|
|
6
9
|
|
|
7
10
|
module Docyard
|
|
8
11
|
class Config
|
|
12
|
+
SIDEBAR_MODES = %w[config auto distributed].freeze
|
|
13
|
+
|
|
9
14
|
DEFAULT_CONFIG = {
|
|
10
15
|
"title" => Constants::DEFAULT_SITE_TITLE,
|
|
11
16
|
"description" => "",
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
17
|
+
"url" => nil,
|
|
18
|
+
"og_image" => nil,
|
|
19
|
+
"twitter" => nil,
|
|
20
|
+
"source" => "docs",
|
|
21
|
+
"branding" => { "logo" => nil, "favicon" => nil, "credits" => true, "copyright" => nil, "color" => nil },
|
|
18
22
|
"socials" => {},
|
|
19
23
|
"tabs" => [],
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
"navigation" => {
|
|
30
|
-
"cta" => [],
|
|
31
|
-
"breadcrumbs" => true
|
|
32
|
-
},
|
|
33
|
-
"announcement" => nil
|
|
24
|
+
"sidebar" => "config",
|
|
25
|
+
"build" => { "output" => "dist", "base" => "/" },
|
|
26
|
+
"search" => { "enabled" => true, "placeholder" => "Search...", "exclude" => [] },
|
|
27
|
+
"navigation" => { "cta" => [], "breadcrumbs" => true },
|
|
28
|
+
"announcement" => nil,
|
|
29
|
+
"repo" => { "url" => nil, "branch" => "main", "edit_path" => nil, "edit_link" => true,
|
|
30
|
+
"last_updated" => true },
|
|
31
|
+
"analytics" => { "google" => nil, "plausible" => nil, "fathom" => nil, "script" => nil },
|
|
32
|
+
"feedback" => { "enabled" => false, "question" => "Was this page helpful?" }
|
|
34
33
|
}.freeze
|
|
35
34
|
|
|
36
35
|
attr_reader :data, :file_path
|
|
@@ -50,87 +49,50 @@ module Docyard
|
|
|
50
49
|
File.exist?(file_path)
|
|
51
50
|
end
|
|
52
51
|
|
|
53
|
-
def title
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def search
|
|
78
|
-
@search ||= ConfigSection.new(data["search"])
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def navigation
|
|
82
|
-
@navigation ||= ConfigSection.new(data["navigation"])
|
|
83
|
-
end
|
|
52
|
+
def title = data["title"]
|
|
53
|
+
def description = data["description"]
|
|
54
|
+
def url = data["url"]
|
|
55
|
+
def og_image = data["og_image"]
|
|
56
|
+
def twitter = data["twitter"]
|
|
57
|
+
def source = data["source"]
|
|
58
|
+
def public_dir = File.join(source, "public")
|
|
59
|
+
def socials = data["socials"]
|
|
60
|
+
def tabs = data["tabs"]
|
|
61
|
+
def sidebar = data["sidebar"]
|
|
62
|
+
|
|
63
|
+
def sidebar_config? = sidebar == "config"
|
|
64
|
+
def sidebar_auto? = sidebar == "auto"
|
|
65
|
+
def sidebar_distributed? = sidebar == "distributed"
|
|
66
|
+
|
|
67
|
+
def branding = @branding ||= Section.new(data["branding"])
|
|
68
|
+
def build = @build ||= Section.new(data["build"])
|
|
69
|
+
def search = @search ||= Section.new(data["search"])
|
|
70
|
+
def navigation = @navigation ||= Section.new(data["navigation"])
|
|
71
|
+
def repo = @repo ||= Section.new(data["repo"])
|
|
72
|
+
def analytics = @analytics ||= Section.new(data["analytics"])
|
|
73
|
+
def feedback = @feedback ||= Section.new(data["feedback"])
|
|
84
74
|
|
|
85
75
|
def announcement
|
|
86
|
-
@announcement ||= data["announcement"] ?
|
|
76
|
+
@announcement ||= data["announcement"] ? Section.new(data["announcement"]) : nil
|
|
87
77
|
end
|
|
88
78
|
|
|
89
79
|
private
|
|
90
80
|
|
|
91
81
|
def load_config_data
|
|
92
|
-
|
|
93
|
-
load_and_merge_config
|
|
94
|
-
else
|
|
95
|
-
deep_dup(DEFAULT_CONFIG)
|
|
96
|
-
end
|
|
82
|
+
file_exists? ? load_and_merge_config : Utils::HashUtils.deep_dup(DEFAULT_CONFIG)
|
|
97
83
|
end
|
|
98
84
|
|
|
99
85
|
def load_and_merge_config
|
|
100
86
|
yaml_content = YAML.load_file(file_path)
|
|
101
|
-
deep_merge(deep_dup(DEFAULT_CONFIG), yaml_content || {})
|
|
87
|
+
Utils::HashUtils.deep_merge(Utils::HashUtils.deep_dup(DEFAULT_CONFIG), yaml_content || {})
|
|
102
88
|
rescue Psych::SyntaxError => e
|
|
103
89
|
raise ConfigError, build_yaml_error_message(e)
|
|
104
90
|
rescue StandardError => e
|
|
105
91
|
raise ConfigError, "Error loading docyard.yml: #{e.message}"
|
|
106
92
|
end
|
|
107
93
|
|
|
108
|
-
def deep_merge(hash1, hash2)
|
|
109
|
-
hash1.merge(hash2) do |_key, v1, v2|
|
|
110
|
-
if v2.nil?
|
|
111
|
-
v1
|
|
112
|
-
elsif v1.is_a?(Hash) && v2.is_a?(Hash)
|
|
113
|
-
deep_merge(v1, v2)
|
|
114
|
-
else
|
|
115
|
-
v2
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def deep_dup(hash)
|
|
121
|
-
hash.transform_values do |value|
|
|
122
|
-
case value
|
|
123
|
-
when Hash then deep_dup(value)
|
|
124
|
-
when Array then value.map { |v| v.is_a?(Hash) ? deep_dup(v) : v }
|
|
125
|
-
else value
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
94
|
def build_yaml_error_message(error)
|
|
131
|
-
message = "Invalid YAML in docyard.yml:\n\n"
|
|
132
|
-
message += " #{error.message}\n\n"
|
|
133
|
-
message += "Fix: Check YAML syntax"
|
|
95
|
+
message = "Invalid YAML in docyard.yml:\n\n #{error.message}\n\nFix: Check YAML syntax"
|
|
134
96
|
message += " at line #{error.line}" if error.respond_to?(:line)
|
|
135
97
|
message
|
|
136
98
|
end
|
|
@@ -139,22 +101,4 @@ module Docyard
|
|
|
139
101
|
Validator.new(data).validate!
|
|
140
102
|
end
|
|
141
103
|
end
|
|
142
|
-
|
|
143
|
-
class ConfigSection
|
|
144
|
-
def initialize(data)
|
|
145
|
-
@data = data || {}
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def method_missing(method, *args)
|
|
149
|
-
return @data[method.to_s] if args.empty?
|
|
150
|
-
|
|
151
|
-
super
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def respond_to_missing?(method, include_private = false)
|
|
155
|
-
@data.key?(method.to_s) || super
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
class ConfigError < StandardError; end
|
|
160
104
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Constants
|
|
5
|
+
CONTENT_TYPE_HTML = "text/html; charset=utf-8"
|
|
6
|
+
|
|
7
|
+
DOCYARD_ASSETS_PREFIX = "/_docyard/"
|
|
8
|
+
PAGEFIND_PREFIX = "/_docyard/pagefind/"
|
|
9
|
+
|
|
10
|
+
INDEX_FILE = "index"
|
|
11
|
+
MARKDOWN_EXTENSION = ".md"
|
|
12
|
+
|
|
13
|
+
STATUS_OK = 200
|
|
14
|
+
STATUS_REDIRECT = 302
|
|
15
|
+
STATUS_NOT_FOUND = 404
|
|
16
|
+
STATUS_INTERNAL_ERROR = 500
|
|
17
|
+
|
|
18
|
+
DEFAULT_SITE_TITLE = "Documentation"
|
|
19
|
+
DEFAULT_LOGO_PATH = "_docyard/logo.svg"
|
|
20
|
+
DEFAULT_LOGO_DARK_PATH = "_docyard/logo-dark.svg"
|
|
21
|
+
DEFAULT_FAVICON_PATH = "_docyard/favicon.svg"
|
|
22
|
+
|
|
23
|
+
SOCIAL_ICON_MAP = {
|
|
24
|
+
"github" => "github-logo",
|
|
25
|
+
"x" => "x-logo",
|
|
26
|
+
"twitter" => "x-logo",
|
|
27
|
+
"discord" => "discord-logo",
|
|
28
|
+
"slack" => "slack-logo",
|
|
29
|
+
"linkedin" => "linkedin-logo",
|
|
30
|
+
"youtube" => "youtube-logo",
|
|
31
|
+
"twitch" => "twitch-logo",
|
|
32
|
+
"instagram" => "instagram-logo",
|
|
33
|
+
"facebook" => "facebook-logo",
|
|
34
|
+
"tiktok" => "tiktok-logo",
|
|
35
|
+
"reddit" => "reddit-logo",
|
|
36
|
+
"mastodon" => "mastodon-logo",
|
|
37
|
+
"threads" => "threads-logo",
|
|
38
|
+
"pinterest" => "pinterest-logo",
|
|
39
|
+
"medium" => "medium-logo",
|
|
40
|
+
"gitlab" => "gitlab-logo",
|
|
41
|
+
"figma" => "figma-logo",
|
|
42
|
+
"dribbble" => "dribbble-logo",
|
|
43
|
+
"behance" => "behance-logo",
|
|
44
|
+
"codepen" => "codepen-logo",
|
|
45
|
+
"codesandbox" => "codesandbox-logo",
|
|
46
|
+
"notion" => "notion-logo",
|
|
47
|
+
"spotify" => "spotify-logo",
|
|
48
|
+
"soundcloud" => "soundcloud-logo",
|
|
49
|
+
"whatsapp" => "whatsapp-logo",
|
|
50
|
+
"telegram" => "telegram-logo",
|
|
51
|
+
"snapchat" => "snapchat-logo",
|
|
52
|
+
"patreon" => "patreon-logo",
|
|
53
|
+
"paypal" => "paypal-logo",
|
|
54
|
+
"stripe" => "stripe-logo",
|
|
55
|
+
"google-podcasts" => "google-podcasts-logo",
|
|
56
|
+
"apple-podcasts" => "apple-podcasts-logo"
|
|
57
|
+
}.freeze
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
module Docyard
|
|
4
4
|
class Error < StandardError; end
|
|
5
5
|
|
|
6
|
+
class ConfigError < Error; end
|
|
7
|
+
|
|
8
|
+
class SidebarConfigError < Error; end
|
|
9
|
+
|
|
6
10
|
class FileNotFoundError < Error
|
|
7
11
|
attr_reader :path
|
|
8
12
|
|
|
@@ -51,4 +55,6 @@ module Docyard
|
|
|
51
55
|
super("Asset not found: #{asset_path}")
|
|
52
56
|
end
|
|
53
57
|
end
|
|
58
|
+
|
|
59
|
+
class BuildError < Error; end
|
|
54
60
|
end
|
data/lib/docyard/initializer.rb
CHANGED
|
@@ -5,19 +5,21 @@ require "fileutils"
|
|
|
5
5
|
module Docyard
|
|
6
6
|
class Initializer
|
|
7
7
|
DOCS_DIR = "docs"
|
|
8
|
-
|
|
8
|
+
TEMPLATES_DIR = File.join(__dir__, "templates", "init")
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
attr_reader :project_name, :project_path, :docs_path, :force
|
|
11
|
+
|
|
12
|
+
def initialize(project_name = nil, force: false)
|
|
13
|
+
@project_name = project_name
|
|
14
|
+
@project_path = project_name ? File.join(".", project_name) : "."
|
|
15
|
+
@docs_path = File.join(@project_path, DOCS_DIR)
|
|
16
|
+
@force = force
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
def run
|
|
16
|
-
|
|
17
|
-
print_already_exists_error
|
|
18
|
-
return
|
|
19
|
-
end
|
|
19
|
+
def run # rubocop:disable Naming/PredicateMethod
|
|
20
|
+
return false unless check_existing_files
|
|
20
21
|
|
|
22
|
+
create_project_directory if project_name
|
|
21
23
|
create_structure
|
|
22
24
|
print_success
|
|
23
25
|
true
|
|
@@ -25,74 +27,123 @@ module Docyard
|
|
|
25
27
|
|
|
26
28
|
private
|
|
27
29
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
+
def check_existing_files # rubocop:disable Naming/PredicateMethod
|
|
31
|
+
return true if force
|
|
32
|
+
return true unless files_exist?
|
|
33
|
+
|
|
34
|
+
print_existing_files_warning
|
|
35
|
+
return true if user_confirms_overwrite?
|
|
36
|
+
|
|
37
|
+
print_abort_message
|
|
38
|
+
false
|
|
30
39
|
end
|
|
31
40
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
create_index_file
|
|
35
|
-
create_example_config
|
|
41
|
+
def files_exist?
|
|
42
|
+
File.exist?(docs_path) || File.exist?(config_path)
|
|
36
43
|
end
|
|
37
44
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
title: Welcome
|
|
43
|
-
---
|
|
45
|
+
def config_path
|
|
46
|
+
File.join(project_path, "docyard.yml")
|
|
47
|
+
end
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
def user_confirms_overwrite?
|
|
50
|
+
print "\nOverwrite existing files? [y/N] "
|
|
51
|
+
response = $stdin.gets&.strip&.downcase
|
|
52
|
+
%w[y yes].include?(response)
|
|
53
|
+
end
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
def print_existing_files_warning
|
|
56
|
+
puts ""
|
|
57
|
+
puts "\e[33mWarning:\e[0m Existing files found:"
|
|
58
|
+
puts " - #{docs_path}/" if File.exist?(docs_path)
|
|
59
|
+
puts " - #{config_path}" if File.exist?(config_path)
|
|
50
60
|
end
|
|
51
61
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
def print_abort_message
|
|
63
|
+
puts ""
|
|
64
|
+
puts "Aborted. Use \e[1m--force\e[0m to overwrite existing files."
|
|
65
|
+
end
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
def create_project_directory
|
|
68
|
+
FileUtils.mkdir_p(project_path)
|
|
69
|
+
end
|
|
58
70
|
|
|
59
|
-
|
|
71
|
+
def create_structure
|
|
72
|
+
FileUtils.mkdir_p(docs_path)
|
|
73
|
+
FileUtils.mkdir_p(File.join(docs_path, "public"))
|
|
74
|
+
create_config_file
|
|
75
|
+
create_sidebar_file
|
|
76
|
+
create_starter_pages
|
|
60
77
|
end
|
|
61
78
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
79
|
+
def create_config_file
|
|
80
|
+
template = File.read(File.join(TEMPLATES_DIR, "docyard.yml"))
|
|
81
|
+
content = template.gsub("{{PROJECT_NAME}}", display_name)
|
|
82
|
+
File.write(config_path, content)
|
|
65
83
|
end
|
|
66
84
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
def create_sidebar_file
|
|
86
|
+
template = File.read(File.join(TEMPLATES_DIR, "_sidebar.yml"))
|
|
87
|
+
File.write(File.join(docs_path, "_sidebar.yml"), template)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def create_starter_pages
|
|
91
|
+
pages_dir = File.join(TEMPLATES_DIR, "pages")
|
|
92
|
+
Dir.glob(File.join(pages_dir, "*.md")).each do |template_path|
|
|
93
|
+
filename = File.basename(template_path)
|
|
94
|
+
content = File.read(template_path).gsub("{{PROJECT_NAME}}", display_name)
|
|
95
|
+
File.write(File.join(docs_path, filename), content)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def display_name
|
|
100
|
+
return "My Documentation" unless project_name
|
|
101
|
+
|
|
102
|
+
project_name.split(/[-_]/).map(&:capitalize).join(" ")
|
|
71
103
|
end
|
|
72
104
|
|
|
73
|
-
def
|
|
105
|
+
def print_success
|
|
74
106
|
puts ""
|
|
75
|
-
puts "Docyard initialized successfully"
|
|
107
|
+
puts "\e[32m#{success_icon} Docyard project initialized successfully!\e[0m"
|
|
76
108
|
puts ""
|
|
109
|
+
print_created_structure
|
|
110
|
+
print_next_steps
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def success_icon
|
|
114
|
+
"\u2714"
|
|
77
115
|
end
|
|
78
116
|
|
|
79
|
-
def
|
|
80
|
-
puts "Created
|
|
117
|
+
def print_created_structure
|
|
118
|
+
puts "Created:"
|
|
81
119
|
puts ""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
120
|
+
if project_name
|
|
121
|
+
puts " \e[1m#{project_name}/\e[0m"
|
|
122
|
+
puts " \u251C\u2500\u2500 docyard.yml"
|
|
123
|
+
puts " \u2514\u2500\u2500 docs/"
|
|
124
|
+
else
|
|
125
|
+
puts " \e[1mdocyard.yml\e[0m"
|
|
126
|
+
puts " \e[1mdocs/\e[0m"
|
|
127
|
+
end
|
|
128
|
+
puts " \u251C\u2500\u2500 _sidebar.yml"
|
|
129
|
+
puts " \u251C\u2500\u2500 index.md"
|
|
130
|
+
puts " \u251C\u2500\u2500 getting-started.md"
|
|
131
|
+
puts " \u251C\u2500\u2500 components.md"
|
|
132
|
+
puts " \u2514\u2500\u2500 public/"
|
|
85
133
|
puts ""
|
|
86
134
|
end
|
|
87
135
|
|
|
88
136
|
def print_next_steps
|
|
89
137
|
puts "Next steps:"
|
|
90
138
|
puts ""
|
|
91
|
-
|
|
92
|
-
|
|
139
|
+
if project_name
|
|
140
|
+
puts " \e[1mcd #{project_name}\e[0m"
|
|
141
|
+
puts ""
|
|
142
|
+
end
|
|
143
|
+
puts " Start the development server:"
|
|
144
|
+
puts " \e[1m$ docyard serve\e[0m"
|
|
93
145
|
puts ""
|
|
94
|
-
puts "
|
|
95
|
-
puts " docyard build"
|
|
146
|
+
puts " Then open \e[4mhttp://localhost:4200\e[0m in your browser"
|
|
96
147
|
puts ""
|
|
97
148
|
end
|
|
98
149
|
end
|
|
@@ -76,9 +76,10 @@ module Docyard
|
|
|
76
76
|
def search_in_ancestors(node, path, title, href)
|
|
77
77
|
return unless node[:children]&.any?
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
return unless path_is_ancestor?(
|
|
79
|
+
section_path = derive_section_path(node, href)
|
|
80
|
+
return unless path_is_ancestor?(section_path)
|
|
81
81
|
|
|
82
|
+
effective_href = resolve_section_href(node, href, section_path)
|
|
82
83
|
result = find_breadcrumb_path(
|
|
83
84
|
node[:children],
|
|
84
85
|
path + [Item.new(title: title, href: effective_href, current: false)]
|
|
@@ -86,14 +87,52 @@ module Docyard
|
|
|
86
87
|
result.any? ? result : nil
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
def derive_section_path(node)
|
|
90
|
-
|
|
90
|
+
def derive_section_path(node, href)
|
|
91
|
+
return href if section_has_index?(href)
|
|
92
|
+
|
|
93
|
+
derive_section_path_from_children(node[:children])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def derive_section_path_from_children(children)
|
|
97
|
+
first_child = children&.first
|
|
91
98
|
return nil unless first_child
|
|
92
99
|
|
|
93
100
|
child_path = first_child[:path]
|
|
94
|
-
return nil
|
|
101
|
+
return nil unless navigable_path?(child_path)
|
|
102
|
+
|
|
103
|
+
parent_dir = File.dirname(child_path)
|
|
104
|
+
root_parent?(parent_dir) ? child_path : parent_dir
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def root_parent?(parent_dir)
|
|
108
|
+
parent_dir == "/" || parent_dir.empty?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def resolve_section_href(node, href, section_path)
|
|
112
|
+
return href if section_has_index?(href)
|
|
113
|
+
|
|
114
|
+
find_first_navigable_child(node[:children]) || section_path
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def section_has_index?(path)
|
|
118
|
+
path && !path.empty? && path != "/"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def find_first_navigable_child(children)
|
|
122
|
+
return nil unless children&.any?
|
|
123
|
+
|
|
124
|
+
children.each do |child|
|
|
125
|
+
return child[:path] if navigable_path?(child[:path])
|
|
126
|
+
|
|
127
|
+
nested = find_first_navigable_child(child[:children])
|
|
128
|
+
return nested if nested
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
95
133
|
|
|
96
|
-
|
|
134
|
+
def navigable_path?(path)
|
|
135
|
+
path && !path.empty? && path != "/"
|
|
97
136
|
end
|
|
98
137
|
|
|
99
138
|
def search_in_children(node, path)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "sidebar_builder"
|
|
4
|
+
require_relative "prev_next_builder"
|
|
5
|
+
require_relative "breadcrumb_builder"
|
|
6
|
+
|
|
7
|
+
module Docyard
|
|
8
|
+
module Navigation
|
|
9
|
+
class PageNavigationBuilder
|
|
10
|
+
def initialize(docs_path:, config:, sidebar_cache: nil)
|
|
11
|
+
@docs_path = docs_path
|
|
12
|
+
@config = config
|
|
13
|
+
@sidebar_cache = sidebar_cache
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build(current_path:, markdown:, header_ctas: [], show_sidebar: true)
|
|
17
|
+
return empty_navigation unless show_sidebar
|
|
18
|
+
|
|
19
|
+
sidebar_builder = build_sidebar(current_path, header_ctas)
|
|
20
|
+
{
|
|
21
|
+
sidebar_html: sidebar_builder.to_html,
|
|
22
|
+
prev_next_html: build_prev_next(sidebar_builder, current_path, markdown),
|
|
23
|
+
breadcrumbs: build_breadcrumbs(sidebar_builder.tree, current_path)
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :docs_path, :config, :sidebar_cache
|
|
30
|
+
|
|
31
|
+
def empty_navigation
|
|
32
|
+
{ sidebar_html: "", prev_next_html: "", breadcrumbs: nil }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build_sidebar(current_path, header_ctas)
|
|
36
|
+
SidebarBuilder.new(
|
|
37
|
+
docs_path: docs_path,
|
|
38
|
+
current_path: current_path,
|
|
39
|
+
config: config,
|
|
40
|
+
header_ctas: header_ctas,
|
|
41
|
+
sidebar_cache: sidebar_cache
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_prev_next(sidebar_builder, current_path, markdown)
|
|
46
|
+
PrevNextBuilder.new(
|
|
47
|
+
sidebar_tree: sidebar_builder.tree,
|
|
48
|
+
current_path: current_path,
|
|
49
|
+
frontmatter: markdown.frontmatter,
|
|
50
|
+
config: {}
|
|
51
|
+
).to_html
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def build_breadcrumbs(sidebar_tree, current_path)
|
|
55
|
+
return nil unless breadcrumbs_enabled?
|
|
56
|
+
|
|
57
|
+
BreadcrumbBuilder.new(sidebar_tree: sidebar_tree, current_path: current_path)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def breadcrumbs_enabled?
|
|
61
|
+
config&.navigation&.breadcrumbs != false
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|