docyard 0.1.0 → 0.3.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/.rubocop.yml +3 -0
- data/CHANGELOG.md +41 -1
- data/LICENSE.vscode-icons +42 -0
- data/README.md +57 -8
- data/lib/docyard/asset_handler.rb +33 -0
- data/lib/docyard/components/base_processor.rb +24 -0
- data/lib/docyard/components/callout_processor.rb +121 -0
- data/lib/docyard/components/code_block_processor.rb +55 -0
- data/lib/docyard/components/code_detector.rb +59 -0
- data/lib/docyard/components/icon_detector.rb +57 -0
- data/lib/docyard/components/icon_processor.rb +51 -0
- data/lib/docyard/components/registry.rb +34 -0
- data/lib/docyard/components/tabs_parser.rb +60 -0
- data/lib/docyard/components/tabs_processor.rb +44 -0
- data/lib/docyard/config/validator.rb +171 -0
- data/lib/docyard/config.rb +133 -0
- data/lib/docyard/constants.rb +28 -0
- data/lib/docyard/errors.rb +54 -0
- data/lib/docyard/file_watcher.rb +2 -2
- data/lib/docyard/icons/LICENSE.phosphor +21 -0
- data/lib/docyard/icons/file_types.rb +92 -0
- data/lib/docyard/icons/phosphor.rb +63 -0
- data/lib/docyard/icons.rb +40 -0
- data/lib/docyard/initializer.rb +27 -2
- data/lib/docyard/language_mapping.rb +52 -0
- data/lib/docyard/logging.rb +43 -0
- data/lib/docyard/markdown.rb +14 -3
- data/lib/docyard/rack_application.rb +100 -13
- data/lib/docyard/renderer.rb +40 -5
- data/lib/docyard/router.rb +13 -8
- data/lib/docyard/routing/resolution_result.rb +31 -0
- data/lib/docyard/server.rb +5 -2
- data/lib/docyard/sidebar/file_system_scanner.rb +77 -0
- data/lib/docyard/sidebar/renderer.rb +110 -0
- data/lib/docyard/sidebar/title_extractor.rb +25 -0
- data/lib/docyard/sidebar/tree_builder.rb +59 -0
- data/lib/docyard/sidebar_builder.rb +58 -0
- data/lib/docyard/templates/assets/css/code.css +362 -0
- data/lib/docyard/templates/assets/css/components/callout.css +169 -0
- data/lib/docyard/templates/assets/css/components/code-block.css +196 -0
- data/lib/docyard/templates/assets/css/components/icon.css +16 -0
- data/lib/docyard/templates/assets/css/components/logo.css +44 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +258 -0
- data/lib/docyard/templates/assets/css/components/tabs.css +298 -0
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +61 -0
- data/lib/docyard/templates/assets/css/layout.css +283 -0
- data/lib/docyard/templates/assets/css/main.css +10 -4
- data/lib/docyard/templates/assets/css/markdown.css +200 -0
- data/lib/docyard/templates/assets/css/reset.css +63 -0
- data/lib/docyard/templates/assets/css/typography.css +97 -0
- data/lib/docyard/templates/assets/css/variables.css +205 -0
- data/lib/docyard/templates/assets/favicon.svg +16 -0
- data/lib/docyard/templates/assets/js/components/code-block.js +162 -0
- data/lib/docyard/templates/assets/js/components/tabs.js +338 -0
- data/lib/docyard/templates/assets/js/theme.js +209 -1
- data/lib/docyard/templates/assets/logo-dark.svg +4 -0
- data/lib/docyard/templates/assets/logo.svg +12 -0
- data/lib/docyard/templates/config/docyard.yml.erb +20 -0
- data/lib/docyard/templates/layouts/default.html.erb +69 -19
- data/lib/docyard/templates/markdown/components/callouts.md.erb +204 -0
- data/lib/docyard/templates/markdown/components/icons.md.erb +125 -0
- data/lib/docyard/templates/markdown/components/tabs.md.erb +686 -0
- data/lib/docyard/templates/markdown/configuration.md.erb +202 -0
- data/lib/docyard/templates/markdown/core-concepts/file-structure.md.erb +61 -0
- data/lib/docyard/templates/markdown/core-concepts/markdown.md.erb +90 -0
- data/lib/docyard/templates/markdown/getting-started/installation.md.erb +43 -0
- data/lib/docyard/templates/markdown/getting-started/introduction.md.erb +30 -0
- data/lib/docyard/templates/markdown/getting-started/quick-start.md.erb +56 -0
- data/lib/docyard/templates/markdown/index.md.erb +78 -14
- data/lib/docyard/templates/partials/_callout.html.erb +11 -0
- data/lib/docyard/templates/partials/_code_block.html.erb +6 -0
- data/lib/docyard/templates/partials/_icon.html.erb +1 -0
- data/lib/docyard/templates/partials/_icon_file_extension.html.erb +1 -0
- data/lib/docyard/templates/partials/_icons.html.erb +11 -0
- data/lib/docyard/templates/partials/_nav_group.html.erb +7 -0
- data/lib/docyard/templates/partials/_nav_item.html.erb +3 -0
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +1 -0
- data/lib/docyard/templates/partials/_nav_list.html.erb +3 -0
- data/lib/docyard/templates/partials/_nav_section.html.erb +6 -0
- data/lib/docyard/templates/partials/_sidebar.html.erb +6 -0
- data/lib/docyard/templates/partials/_sidebar_footer.html.erb +11 -0
- data/lib/docyard/templates/partials/_tabs.html.erb +40 -0
- data/lib/docyard/templates/partials/_theme_toggle.html.erb +13 -0
- data/lib/docyard/utils/path_resolver.rb +30 -0
- data/lib/docyard/utils/text_formatter.rb +22 -0
- data/lib/docyard/version.rb +1 -1
- data/lib/docyard.rb +16 -4
- metadata +71 -3
- data/lib/docyard/templates/assets/css/syntax.css +0 -116
- data/lib/docyard/templates/markdown/getting-started.md.erb +0 -40
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "icon_detector"
|
|
4
|
+
require "kramdown"
|
|
5
|
+
require "kramdown-parser-gfm"
|
|
6
|
+
|
|
7
|
+
module Docyard
|
|
8
|
+
module Components
|
|
9
|
+
class TabsParser
|
|
10
|
+
def self.parse(content)
|
|
11
|
+
new(content).parse
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(content)
|
|
15
|
+
@content = content
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def parse
|
|
19
|
+
sections.filter_map { |section| parse_section(section) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
attr_reader :content
|
|
25
|
+
|
|
26
|
+
def sections
|
|
27
|
+
content.split(/^==[ \t]+/)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse_section(section)
|
|
31
|
+
return nil if section.strip.empty?
|
|
32
|
+
|
|
33
|
+
parts = section.split("\n", 2)
|
|
34
|
+
tab_name = parts[0]&.strip
|
|
35
|
+
return nil if tab_name.nil? || tab_name.empty?
|
|
36
|
+
|
|
37
|
+
tab_content = parts[1]&.strip || ""
|
|
38
|
+
icon_data = IconDetector.detect(tab_name, tab_content)
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
name: icon_data[:name],
|
|
42
|
+
content: render_markdown(tab_content),
|
|
43
|
+
icon: icon_data[:icon],
|
|
44
|
+
icon_source: icon_data[:icon_source]
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def render_markdown(markdown_content)
|
|
49
|
+
return "" if markdown_content.empty?
|
|
50
|
+
|
|
51
|
+
Kramdown::Document.new(
|
|
52
|
+
markdown_content,
|
|
53
|
+
input: "GFM",
|
|
54
|
+
hard_wrap: false,
|
|
55
|
+
syntax_highlighter: "rouge"
|
|
56
|
+
).to_html
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../renderer"
|
|
4
|
+
require_relative "base_processor"
|
|
5
|
+
require_relative "tabs_parser"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
|
|
8
|
+
module Docyard
|
|
9
|
+
module Components
|
|
10
|
+
class TabsProcessor < BaseProcessor
|
|
11
|
+
self.priority = 15
|
|
12
|
+
|
|
13
|
+
def preprocess(content)
|
|
14
|
+
return content unless content.include?(":::tabs")
|
|
15
|
+
|
|
16
|
+
content.gsub(/^:::[ \t]*tabs[ \t]*\n(.*?)^:::[ \t]*$/m) do
|
|
17
|
+
process_tabs_block(Regexp.last_match(1))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def process_tabs_block(tabs_content)
|
|
24
|
+
tabs = TabsParser.parse(tabs_content)
|
|
25
|
+
return "" if tabs.empty?
|
|
26
|
+
|
|
27
|
+
wrap_in_nomarkdown(render_tabs(tabs))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def render_tabs(tabs)
|
|
31
|
+
Renderer.new.render_partial(
|
|
32
|
+
"_tabs", {
|
|
33
|
+
tabs: tabs,
|
|
34
|
+
group_id: SecureRandom.hex(4)
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def wrap_in_nomarkdown(html)
|
|
40
|
+
"{::nomarkdown}\n#{html}\n{:/nomarkdown}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
class Config
|
|
5
|
+
class Validator
|
|
6
|
+
def initialize(config_data)
|
|
7
|
+
@config = config_data
|
|
8
|
+
@errors = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def validate!
|
|
12
|
+
validate_site_section
|
|
13
|
+
validate_branding_section
|
|
14
|
+
validate_build_section
|
|
15
|
+
|
|
16
|
+
raise ConfigError, format_errors if @errors.any?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def validate_site_section
|
|
22
|
+
site = @config["site"]
|
|
23
|
+
|
|
24
|
+
validate_string(site["title"], "site.title")
|
|
25
|
+
validate_string(site["description"], "site.description")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def validate_branding_section
|
|
29
|
+
branding = @config["branding"]
|
|
30
|
+
return unless branding
|
|
31
|
+
|
|
32
|
+
validate_file_path_or_url(branding["logo"], "branding.logo")
|
|
33
|
+
validate_file_path_or_url(branding["logo_dark"], "branding.logo_dark")
|
|
34
|
+
validate_file_path_or_url(branding["favicon"], "branding.favicon")
|
|
35
|
+
|
|
36
|
+
appearance = branding["appearance"] || {}
|
|
37
|
+
validate_boolean(appearance["logo"], "branding.appearance.logo")
|
|
38
|
+
validate_boolean(appearance["title"], "branding.appearance.title")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def validate_build_section
|
|
42
|
+
build = @config["build"]
|
|
43
|
+
|
|
44
|
+
validate_string(build["output_dir"], "build.output_dir")
|
|
45
|
+
validate_no_slashes(build["output_dir"], "build.output_dir")
|
|
46
|
+
validate_string(build["base_url"], "build.base_url")
|
|
47
|
+
validate_starts_with_slash(build["base_url"], "build.base_url")
|
|
48
|
+
validate_boolean(build["clean"], "build.clean")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_string(value, field_name)
|
|
52
|
+
return if value.nil?
|
|
53
|
+
return if value.is_a?(String)
|
|
54
|
+
|
|
55
|
+
add_error(
|
|
56
|
+
field: field_name,
|
|
57
|
+
error: "must be a string",
|
|
58
|
+
got: value.class.name,
|
|
59
|
+
fix: "Change to a string value"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate_boolean(value, field_name)
|
|
64
|
+
return if [true, false].include?(value)
|
|
65
|
+
|
|
66
|
+
add_error(
|
|
67
|
+
field: field_name,
|
|
68
|
+
error: "must be true or false",
|
|
69
|
+
got: value.inspect,
|
|
70
|
+
fix: "Change to true or false"
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def validate_file_path(value, field_name)
|
|
75
|
+
return if value.nil?
|
|
76
|
+
return add_file_path_type_error(value, field_name) unless value.is_a?(String)
|
|
77
|
+
|
|
78
|
+
file_path = if File.absolute_path?(value)
|
|
79
|
+
value
|
|
80
|
+
else
|
|
81
|
+
File.join("docs", value)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
return if File.exist?(file_path)
|
|
85
|
+
|
|
86
|
+
add_file_not_found_error(value, field_name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def add_file_path_type_error(value, field_name)
|
|
90
|
+
add_error(
|
|
91
|
+
field: field_name,
|
|
92
|
+
error: "must be a file path (string)",
|
|
93
|
+
got: value.class.name,
|
|
94
|
+
fix: "Change to a string file path"
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add_file_not_found_error(value, field_name)
|
|
99
|
+
add_error(
|
|
100
|
+
field: field_name,
|
|
101
|
+
error: "file not found",
|
|
102
|
+
got: value,
|
|
103
|
+
fix: "Place the file in docs/ directory and use a relative path (e.g., 'assets/logo.svg')"
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def validate_no_slashes(value, field_name)
|
|
108
|
+
return if value.nil?
|
|
109
|
+
return unless value.is_a?(String)
|
|
110
|
+
return unless value.include?("/") || value.include?("\\")
|
|
111
|
+
|
|
112
|
+
add_error(
|
|
113
|
+
field: field_name,
|
|
114
|
+
error: "cannot contain slashes",
|
|
115
|
+
got: value,
|
|
116
|
+
fix: "Use a simple directory name like 'dist' or '_site'"
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def validate_starts_with_slash(value, field_name)
|
|
121
|
+
return if value.nil?
|
|
122
|
+
return if value.start_with?("/")
|
|
123
|
+
|
|
124
|
+
add_error(
|
|
125
|
+
field: field_name,
|
|
126
|
+
error: "must start with /",
|
|
127
|
+
got: value,
|
|
128
|
+
fix: "Change to '/#{value}'"
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def validate_file_path_or_url(value, field_name)
|
|
133
|
+
return if value.nil?
|
|
134
|
+
return add_file_path_type_error(value, field_name) unless value.is_a?(String)
|
|
135
|
+
|
|
136
|
+
return if url?(value)
|
|
137
|
+
|
|
138
|
+
file_path = if File.absolute_path?(value)
|
|
139
|
+
value
|
|
140
|
+
else
|
|
141
|
+
File.join("docs", value)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
return if File.exist?(file_path)
|
|
145
|
+
|
|
146
|
+
add_file_not_found_error(value, field_name)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def url?(value)
|
|
150
|
+
value.match?(%r{\Ahttps?://})
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def add_error(error_data)
|
|
154
|
+
@errors << error_data
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def format_errors
|
|
158
|
+
message = "Error in docyard.yml:\n\n"
|
|
159
|
+
|
|
160
|
+
@errors.each do |err|
|
|
161
|
+
message += " Field: #{err[:field]}\n"
|
|
162
|
+
message += " Error: #{err[:error]}\n"
|
|
163
|
+
message += " Got: #{err[:got]}\n"
|
|
164
|
+
message += " Fix: #{err[:fix]}\n\n"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
message.chomp
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require_relative "config/validator"
|
|
5
|
+
require_relative "constants"
|
|
6
|
+
|
|
7
|
+
module Docyard
|
|
8
|
+
class Config
|
|
9
|
+
DEFAULT_CONFIG = {
|
|
10
|
+
"site" => {
|
|
11
|
+
"title" => Constants::DEFAULT_SITE_TITLE,
|
|
12
|
+
"description" => ""
|
|
13
|
+
},
|
|
14
|
+
"branding" => {
|
|
15
|
+
"logo" => nil,
|
|
16
|
+
"logo_dark" => nil,
|
|
17
|
+
"favicon" => nil,
|
|
18
|
+
"appearance" => {
|
|
19
|
+
"logo" => true,
|
|
20
|
+
"title" => true
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"build" => {
|
|
24
|
+
"output_dir" => "dist",
|
|
25
|
+
"base_url" => "/",
|
|
26
|
+
"clean" => true
|
|
27
|
+
},
|
|
28
|
+
"sidebar" => nil
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
attr_reader :data, :file_path
|
|
32
|
+
|
|
33
|
+
def self.load(project_root = Dir.pwd)
|
|
34
|
+
new(project_root)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize(project_root = Dir.pwd)
|
|
38
|
+
@project_root = project_root
|
|
39
|
+
@file_path = File.join(project_root, "docyard.yml")
|
|
40
|
+
@data = load_config_data
|
|
41
|
+
validate!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def file_exists?
|
|
45
|
+
File.exist?(file_path)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def site
|
|
49
|
+
@site ||= ConfigSection.new(data["site"])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def branding
|
|
53
|
+
@branding ||= ConfigSection.new(data["branding"])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build
|
|
57
|
+
@build ||= ConfigSection.new(data["build"])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def sidebar
|
|
61
|
+
data["sidebar"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def load_config_data
|
|
67
|
+
if file_exists?
|
|
68
|
+
load_and_merge_config
|
|
69
|
+
else
|
|
70
|
+
deep_dup(DEFAULT_CONFIG)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def load_and_merge_config
|
|
75
|
+
yaml_content = YAML.load_file(file_path)
|
|
76
|
+
deep_merge(deep_dup(DEFAULT_CONFIG), yaml_content || {})
|
|
77
|
+
rescue Psych::SyntaxError => e
|
|
78
|
+
raise ConfigError, build_yaml_error_message(e)
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
raise ConfigError, "Error loading docyard.yml: #{e.message}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def deep_merge(hash1, hash2)
|
|
84
|
+
hash1.merge(hash2) do |_key, v1, v2|
|
|
85
|
+
if v2.nil?
|
|
86
|
+
v1
|
|
87
|
+
elsif v1.is_a?(Hash) && v2.is_a?(Hash)
|
|
88
|
+
deep_merge(v1, v2)
|
|
89
|
+
else
|
|
90
|
+
v2
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def deep_dup(hash)
|
|
96
|
+
hash.transform_values { |value| value.is_a?(Hash) ? deep_dup(value) : value }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_yaml_error_message(error)
|
|
100
|
+
message = "Invalid YAML in docyard.yml:\n\n"
|
|
101
|
+
message += " #{error.message}\n\n"
|
|
102
|
+
message += "Fix: Check YAML syntax"
|
|
103
|
+
message += " at line #{error.line}" if error.respond_to?(:line)
|
|
104
|
+
message
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def validate!
|
|
108
|
+
Validator.new(data).validate!
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class ConfigSection
|
|
113
|
+
def initialize(data)
|
|
114
|
+
@data = data || {}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def appearance
|
|
118
|
+
@data["appearance"]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def method_missing(method, *args)
|
|
122
|
+
return @data[method.to_s] if args.empty?
|
|
123
|
+
|
|
124
|
+
super
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def respond_to_missing?(method, include_private = false)
|
|
128
|
+
@data.key?(method.to_s) || super
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class ConfigError < StandardError; end
|
|
133
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Constants
|
|
5
|
+
CONTENT_TYPE_HTML = "text/html; charset=utf-8"
|
|
6
|
+
CONTENT_TYPE_JSON = "application/json; charset=utf-8"
|
|
7
|
+
CONTENT_TYPE_CSS = "text/css; charset=utf-8"
|
|
8
|
+
CONTENT_TYPE_JS = "application/javascript; charset=utf-8"
|
|
9
|
+
|
|
10
|
+
RELOAD_ENDPOINT = "/_docyard/reload"
|
|
11
|
+
ASSETS_PREFIX = "/assets/"
|
|
12
|
+
|
|
13
|
+
INDEX_FILE = "index"
|
|
14
|
+
INDEX_TITLE = "Home"
|
|
15
|
+
|
|
16
|
+
MARKDOWN_EXTENSION = ".md"
|
|
17
|
+
HTML_EXTENSION = ".html"
|
|
18
|
+
|
|
19
|
+
STATUS_OK = 200
|
|
20
|
+
STATUS_NOT_FOUND = 404
|
|
21
|
+
STATUS_INTERNAL_ERROR = 500
|
|
22
|
+
|
|
23
|
+
DEFAULT_SITE_TITLE = "Documentation"
|
|
24
|
+
DEFAULT_LOGO_PATH = "assets/logo.svg"
|
|
25
|
+
DEFAULT_LOGO_DARK_PATH = "assets/logo-dark.svg"
|
|
26
|
+
DEFAULT_FAVICON_PATH = "assets/favicon.svg"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class FileNotFoundError < Error
|
|
7
|
+
attr_reader :path
|
|
8
|
+
|
|
9
|
+
def initialize(path)
|
|
10
|
+
@path = path
|
|
11
|
+
super("File not found: #{path}")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class InvalidPathError < Error; end
|
|
16
|
+
|
|
17
|
+
class MarkdownParseError < Error
|
|
18
|
+
attr_reader :file_path, :original_error
|
|
19
|
+
|
|
20
|
+
def initialize(file_path, original_error)
|
|
21
|
+
@file_path = file_path
|
|
22
|
+
@original_error = original_error
|
|
23
|
+
super("Failed to parse markdown file #{file_path}: #{original_error.message}")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class TemplateRenderError < Error
|
|
28
|
+
attr_reader :template_path, :original_error
|
|
29
|
+
|
|
30
|
+
def initialize(template_path, original_error)
|
|
31
|
+
@template_path = template_path
|
|
32
|
+
@original_error = original_error
|
|
33
|
+
super("Failed to render template #{template_path}: #{original_error.message}")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class ReloadCheckError < Error
|
|
38
|
+
attr_reader :original_error
|
|
39
|
+
|
|
40
|
+
def initialize(original_error)
|
|
41
|
+
@original_error = original_error
|
|
42
|
+
super("Reload check failed: #{original_error.message}")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class AssetNotFoundError < Error
|
|
47
|
+
attr_reader :asset_path
|
|
48
|
+
|
|
49
|
+
def initialize(asset_path)
|
|
50
|
+
@asset_path = asset_path
|
|
51
|
+
super("Asset not found: #{asset_path}")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/docyard/file_watcher.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Docyard
|
|
|
23
23
|
def stop
|
|
24
24
|
@listener&.stop
|
|
25
25
|
rescue StandardError => e
|
|
26
|
-
|
|
26
|
+
Docyard.logger.error "Error stopping file watcher: #{e.class} - #{e.message}"
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def changed_since?(timestamp)
|
|
@@ -36,7 +36,7 @@ module Docyard
|
|
|
36
36
|
return if modified.empty? && added.empty? && removed.empty?
|
|
37
37
|
|
|
38
38
|
@last_modified_time = Time.now
|
|
39
|
-
|
|
39
|
+
Docyard.logger.info "Files changed, triggering reload..."
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020-2023 Phosphor Icons
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|