jekyll-obsidian-site 0.2.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +133 -0
- data/website/_config.yml +21 -0
- data/website/_includes/website/archive-timeline.html +8 -0
- data/website/_includes/website/blog-ledger.html +19 -0
- data/website/_includes/website/blog-navigation.html +7 -0
- data/website/_includes/website/comments.html +22 -0
- data/website/_includes/website/contact-icon.html +14 -0
- data/website/_includes/website/context-tags.html +7 -0
- data/website/_includes/website/context.html +3 -0
- data/website/_includes/website/dialogs.html +71 -0
- data/website/_includes/website/docs-navigation.html +7 -0
- data/website/_includes/website/docs-tree.html +8 -0
- data/website/_includes/website/footer.html +14 -0
- data/website/_includes/website/github-icon.html +3 -0
- data/website/_includes/website/head.html +41 -0
- data/website/_includes/website/header.html +35 -0
- data/website/_includes/website/language-switcher.html +16 -0
- data/website/_includes/website/local-graph.html +25 -0
- data/website/_includes/website/note-header.html +35 -0
- data/website/_includes/website/outline.html +9 -0
- data/website/_includes/website/page-actions.html +34 -0
- data/website/_includes/website/portfolio-grid.html +36 -0
- data/website/_includes/website/portfolio-topics.html +4 -0
- data/website/_includes/website/post-cards.html +16 -0
- data/website/_includes/website/related-articles.html +7 -0
- data/website/_includes/website/relations.html +16 -0
- data/website/_includes/website/source-actions.html +28 -0
- data/website/_includes/website/system-page.html +31 -0
- data/website/_includes/website/tab-collection.html +28 -0
- data/website/_includes/website/tag-filter.html +12 -0
- data/website/_layouts/website-docs.html +20 -0
- data/website/_layouts/website-minimal.html +93 -0
- data/website/_layouts/website-redirect.html +17 -0
- data/website/assets/chunks/abnfDiagram-N423BO3Z-TSORAEI2.js +1 -0
- data/website/assets/chunks/architecture-TIHT7OUA-OUC5PEC7.js +1 -0
- data/website/assets/chunks/architectureDiagram-T3A2C74G-6P5MB2DJ.js +36 -0
- data/website/assets/chunks/blockDiagram-VBNYF7ZC-ZWINYROZ.js +132 -0
- data/website/assets/chunks/c4Diagram-5PPSVZJV-R5RMEAOT.js +10 -0
- data/website/assets/chunks/chunk-22DRK7VS.js +1 -0
- data/website/assets/chunks/chunk-25REBZ7M.js +2 -0
- data/website/assets/chunks/chunk-2RQCUDTG.js +1 -0
- data/website/assets/chunks/chunk-34FHPXU4.js +1 -0
- data/website/assets/chunks/chunk-57XXJOQN.js +10 -0
- data/website/assets/chunks/chunk-6DCNDVBX.js +2 -0
- data/website/assets/chunks/chunk-6DHFEWGX.js +1 -0
- data/website/assets/chunks/chunk-6VQ47JJS.js +2 -0
- data/website/assets/chunks/chunk-7CSIRG7W.js +127 -0
- data/website/assets/chunks/chunk-A43YELH5.js +1 -0
- data/website/assets/chunks/chunk-AIX6IH4O.js +1 -0
- data/website/assets/chunks/chunk-B3K7RH5O.js +88 -0
- data/website/assets/chunks/chunk-CBGUD2NP.js +1 -0
- data/website/assets/chunks/chunk-D3WOCELE.js +1 -0
- data/website/assets/chunks/chunk-D6FURU6X.js +1 -0
- data/website/assets/chunks/chunk-DGEGT77F.js +1 -0
- data/website/assets/chunks/chunk-DN52R6U7.js +1 -0
- data/website/assets/chunks/chunk-E3T3IFAC.js +1 -0
- data/website/assets/chunks/chunk-EIBFAW37.js +1 -0
- data/website/assets/chunks/chunk-ERWOH47R.js +1 -0
- data/website/assets/chunks/chunk-ETOVTYWF.js +1 -0
- data/website/assets/chunks/chunk-GHD3PFWA.js +231 -0
- data/website/assets/chunks/chunk-GMBKQ7NZ.js +1 -0
- data/website/assets/chunks/chunk-GQM25EPP.js +156 -0
- data/website/assets/chunks/chunk-GSOU4UTT.js +1 -0
- data/website/assets/chunks/chunk-HEMELP7J.js +1 -0
- data/website/assets/chunks/chunk-HZ5I5NJO.js +1 -0
- data/website/assets/chunks/chunk-IP56FOSI.js +70 -0
- data/website/assets/chunks/chunk-L7BMR7WL.js +1 -0
- data/website/assets/chunks/chunk-LGUG3SQB.js +1 -0
- data/website/assets/chunks/chunk-LOMHZYS2.js +1 -0
- data/website/assets/chunks/chunk-MII4VZEB.js +3 -0
- data/website/assets/chunks/chunk-MYMJLHQL.js +62 -0
- data/website/assets/chunks/chunk-NBH7YOBD.js +15 -0
- data/website/assets/chunks/chunk-NCOVM3YF.js +1 -0
- data/website/assets/chunks/chunk-OAGTSILX.js +1 -0
- data/website/assets/chunks/chunk-OCX5OCIU.js +321 -0
- data/website/assets/chunks/chunk-OE3NOVIH.js +1 -0
- data/website/assets/chunks/chunk-PLP5XUTJ.js +1 -0
- data/website/assets/chunks/chunk-PNOERP6G.js +1 -0
- data/website/assets/chunks/chunk-QBPQ7DPE.js +1 -0
- data/website/assets/chunks/chunk-RWFFDL53.js +1 -0
- data/website/assets/chunks/chunk-SEGIW3MP.js +1 -0
- data/website/assets/chunks/chunk-SZDMZNUS.js +1 -0
- data/website/assets/chunks/chunk-T2QBAPVT.js +1 -0
- data/website/assets/chunks/chunk-UE533OZ7.js +1 -0
- data/website/assets/chunks/chunk-UTWOJE2A.js +1 -0
- data/website/assets/chunks/chunk-VOCDYA3H.js +1 -0
- data/website/assets/chunks/chunk-VYZZPKGK.js +161 -0
- data/website/assets/chunks/chunk-WWLZIRS6.js +1 -0
- data/website/assets/chunks/chunk-XNXYDBKX.js +32 -0
- data/website/assets/chunks/chunk-XQUIO562.js +1 -0
- data/website/assets/chunks/chunk-Y2XVFW7T.js +2 -0
- data/website/assets/chunks/chunk-YORIST7Z.js +1 -0
- data/website/assets/chunks/chunk-Z2WBQPVY.js +206 -0
- data/website/assets/chunks/classDiagram-JCYQIIEL-OFDVDDIM.js +1 -0
- data/website/assets/chunks/classDiagram-v2-OCEON4UE-E36B6BGQ.js +1 -0
- data/website/assets/chunks/cose-bilkent-JH36ORCC-CBAW64AB.js +1 -0
- data/website/assets/chunks/cynefin-VYW2F7L2-CBO6DLNV.js +1 -0
- data/website/assets/chunks/cynefinDiagram-MW4NZA55-DFINLOUY.js +62 -0
- data/website/assets/chunks/dagre-VZM6K2ZE-BZFBSZAH.js +4 -0
- data/website/assets/chunks/diagram-7IWD3JNH-XQHTVOU2.js +30 -0
- data/website/assets/chunks/diagram-B4RE2ZJO-FTSXBZJR.js +3 -0
- data/website/assets/chunks/diagram-LBJQPF4R-ZSZW4OLH.js +24 -0
- data/website/assets/chunks/diagram-Q27KOJAE-2IV5TVCJ.js +24 -0
- data/website/assets/chunks/diagram-UB23O5K3-D6PDUTFL.js +41 -0
- data/website/assets/chunks/ebnfDiagram-BXEA7PRR-C6MAER3M.js +1 -0
- data/website/assets/chunks/erDiagram-JOGREHBK-EDAD2H5M.js +85 -0
- data/website/assets/chunks/eventmodeling-45OFAUF4-4K577DMF.js +1 -0
- data/website/assets/chunks/flowDiagram-UKHOOZJN-545F4YX3.js +1 -0
- data/website/assets/chunks/ganttDiagram-PKOTCBZU-7A7EKMY3.js +292 -0
- data/website/assets/chunks/gitGraph-TEB2WS4Q-3WUB7WYQ.js +1 -0
- data/website/assets/chunks/gitGraphDiagram-DS77QQ5N-A5UWV5FF.js +106 -0
- data/website/assets/chunks/info-DKCQHKI2-EOOHI7XS.js +1 -0
- data/website/assets/chunks/infoDiagram-6WML65LV-CWIOOWJP.js +2 -0
- data/website/assets/chunks/ishikawaDiagram-WSZJBQD7-CEASQVDL.js +70 -0
- data/website/assets/chunks/journeyDiagram-NVQOT4AX-LYG3S2AW.js +139 -0
- data/website/assets/chunks/kanban-definition-27J2QSJJ-HCC6KAYI.js +89 -0
- data/website/assets/chunks/katex-F4MQ6FB3.js +257 -0
- data/website/assets/chunks/mindmap-definition-FAOFIHXS-JMGSM3RE.js +96 -0
- data/website/assets/chunks/packet-7NZHBO7P-W3SJA3MY.js +1 -0
- data/website/assets/chunks/pegDiagram-VL7TDLO6-4JO3Y3Z3.js +1 -0
- data/website/assets/chunks/pie-RZYD4A2V-YOIQBTY7.js +1 -0
- data/website/assets/chunks/pieDiagram-7S7Q4E2Y-VFQ7SE46.js +39 -0
- data/website/assets/chunks/quadrantDiagram-CIZ2JOQS-JISDI2S6.js +7 -0
- data/website/assets/chunks/radar-I7S5WNFK-NMKP5AZO.js +1 -0
- data/website/assets/chunks/railroad-3IZDKUUU-7MOIV7CI.js +1 -0
- data/website/assets/chunks/railroad-abnf-AHOZXSZD-HXNH5SLO.js +1 -0
- data/website/assets/chunks/railroad-ebnf-EBAXGLYW-OWDJEWMK.js +1 -0
- data/website/assets/chunks/railroad-peg-LSFZ7HO6-ASSINGP5.js +1 -0
- data/website/assets/chunks/railroadDiagram-AXF67PYL-AFV5VP5F.js +1 -0
- data/website/assets/chunks/requirementDiagram-LRYGKXZP-RNRPEHHN.js +84 -0
- data/website/assets/chunks/sankeyDiagram-W5VNT64P-O54R2L3E.js +40 -0
- data/website/assets/chunks/sequenceDiagram-SI44F4Z6-42WISKJ7.js +162 -0
- data/website/assets/chunks/sizeCapture-X5ZJPWSS-LRN2Q37T.js +1 -0
- data/website/assets/chunks/stateDiagram-OKZ733FA-BWIBJXIR.js +1 -0
- data/website/assets/chunks/stateDiagram-v2-UEYNNEHI-L73LHL3L.js +1 -0
- data/website/assets/chunks/swimlanes-SLNWSIFB-ECOAE7LQ.js +1 -0
- data/website/assets/chunks/swimlanesDiagram-ULZ7WXOC-3CKKKFZZ.js +8 -0
- data/website/assets/chunks/timeline-definition-Z64GVDOM-HR2OL5G7.js +120 -0
- data/website/assets/chunks/treeView-QDETBFTQ-C6D5WFJA.js +1 -0
- data/website/assets/chunks/treemap-6X3UGDF4-JFCDMSCA.js +1 -0
- data/website/assets/chunks/vennDiagram-T6HMQDX7-62WAAPTF.js +34 -0
- data/website/assets/chunks/wardley-OPB4EBWU-RQIOII7I.js +1 -0
- data/website/assets/chunks/wardleyDiagram-T6FBY63Y-KUXO3NHV.js +78 -0
- data/website/assets/chunks/xychartDiagram-ELKLHX3M-I54W4HJ7.js +7 -0
- data/website/assets/color-scheme-bootstrap-4H4ZR4BW.js +1 -0
- data/website/assets/docs-3WBBQT6K.css +1 -0
- data/website/assets/docs-W3D5DWQF.js +1 -0
- data/website/assets/docs-navigation-75ALFYBA.js +1 -0
- data/website/assets/graph-QWL54LWD.js +1 -0
- data/website/assets/manifest.json +314 -0
- data/website/assets/math-7JTH4OWZ.js +21 -0
- data/website/assets/mermaid-RXC6URG4.js +10 -0
- data/website/assets/minimal-3NPTDWQX.js +1 -0
- data/website/assets/minimal-E2VATXXS.css +1 -0
- data/website/assets/previews-FMHOX7UX.js +1 -0
- data/website/assets/search-BH73JZDU.js +1 -0
- data/website/assets/search-worker-SM4IM4J7.js +1 -0
- data/website/exe/jekyll-obsidian +5 -0
- data/website/lib/jekyll_obsidian/adapter.rb +733 -0
- data/website/lib/jekyll_obsidian/built_in_themes.rb +855 -0
- data/website/lib/jekyll_obsidian/cli.rb +84 -0
- data/website/lib/jekyll_obsidian/content_policy.rb +194 -0
- data/website/lib/jekyll_obsidian/destination_registry.rb +39 -0
- data/website/lib/jekyll_obsidian/directory_indexes.rb +30 -0
- data/website/lib/jekyll_obsidian/external_media.rb +371 -0
- data/website/lib/jekyll_obsidian/front_matter.rb +453 -0
- data/website/lib/jekyll_obsidian/github_markdown.rb +664 -0
- data/website/lib/jekyll_obsidian/html_publication.rb +206 -0
- data/website/lib/jekyll_obsidian/initializer.rb +68 -0
- data/website/lib/jekyll_obsidian/localized_compiler.rb +1044 -0
- data/website/lib/jekyll_obsidian/media_policy.rb +41 -0
- data/website/lib/jekyll_obsidian/ofm_scanner.rb +493 -0
- data/website/lib/jekyll_obsidian/output_text.rb +13 -0
- data/website/lib/jekyll_obsidian/preview.rb +93 -0
- data/website/lib/jekyll_obsidian/published_markdown.rb +32 -0
- data/website/lib/jekyll_obsidian/raw_html_manifest.rb +66 -0
- data/website/lib/jekyll_obsidian/runtime.rb +197 -0
- data/website/lib/jekyll_obsidian/site_compilation.rb +147 -0
- data/website/lib/jekyll_obsidian/site_navigation.rb +517 -0
- data/website/lib/jekyll_obsidian/url_builder.rb +163 -0
- data/website/lib/jekyll_obsidian/value_objects.rb +198 -0
- data/website/lib/jekyll_obsidian/vault_compiler.rb +2771 -0
- data/website/lib/jekyll_obsidian/version.rb +6 -0
- data/website/lib/jekyll_obsidian/workspace_layout.rb +104 -0
- data/website/lib/jekyll_obsidian.rb +22 -0
- data/website/scripts/audit-site.rb +120 -0
- data/website/scripts/templates/pages.yml +55 -0
- data/website/scripts/verify-site-urls.rb +585 -0
- metadata +319 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllObsidian
|
|
4
|
+
# Resolves explicitly authorized, source-backed HTML documents without
|
|
5
|
+
# exposing their routing and publication rules to themes or the Jekyll
|
|
6
|
+
# adapter.
|
|
7
|
+
module HtmlPublication
|
|
8
|
+
MANIFEST_ROUTE = RawHtmlManifest::ROUTE
|
|
9
|
+
Resolution = ImmutableRecord.define(
|
|
10
|
+
:projected_files,
|
|
11
|
+
:routes_by_source,
|
|
12
|
+
:document_sources,
|
|
13
|
+
:documents,
|
|
14
|
+
:files,
|
|
15
|
+
:diagnostics
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def resolve(snapshot:, mappings:, url_builder:)
|
|
21
|
+
return empty_resolution if mappings.nil? || mappings == {}
|
|
22
|
+
unless mappings.is_a?(Hash) && mappings.keys.all? { |key| key.is_a?(String) }
|
|
23
|
+
return failure("invalid_html_config", "website.html must be a mapping from source paths to public routes")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
entries = Array(snapshot.entries).to_h { |entry| [entry.path.to_s, entry] }
|
|
27
|
+
projected_files = []
|
|
28
|
+
routes_by_source = {}
|
|
29
|
+
document_sources = []
|
|
30
|
+
documents = []
|
|
31
|
+
diagnostics = []
|
|
32
|
+
claimed_sources = []
|
|
33
|
+
|
|
34
|
+
mappings.sort_by { |source, _route| source.to_s.b }.each do |source, route|
|
|
35
|
+
unless valid_source?(source)
|
|
36
|
+
diagnostics << diagnostic("invalid_html_source", "HTML sources must be normalized vault-relative POSIX paths", source)
|
|
37
|
+
next
|
|
38
|
+
end
|
|
39
|
+
overlapping_source = claimed_sources.find { |claimed| paths_overlap?(claimed, source) }
|
|
40
|
+
if overlapping_source
|
|
41
|
+
diagnostics << diagnostic(
|
|
42
|
+
"overlapping_html_source",
|
|
43
|
+
"HTML source overlaps the existing declaration #{overlapping_source.inspect} under NFC/case-folding",
|
|
44
|
+
source
|
|
45
|
+
)
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
claimed_sources << source
|
|
49
|
+
entry = entries[source]
|
|
50
|
+
if entry
|
|
51
|
+
unless entry.kind.to_sym == :attachment && source.end_with?(".html")
|
|
52
|
+
diagnostics << diagnostic("missing_html_source", "standalone HTML source must be an existing .html file", source)
|
|
53
|
+
next
|
|
54
|
+
end
|
|
55
|
+
normalized_route = normalize_file_route(route, url_builder)
|
|
56
|
+
unless normalized_route
|
|
57
|
+
diagnostics << diagnostic("invalid_html_route", "a standalone HTML file must map to a safe root-relative .html route", source)
|
|
58
|
+
next
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
projected_files << projected_file(entry, normalized_route, "text/html")
|
|
62
|
+
routes_by_source[source] = normalized_route
|
|
63
|
+
document_sources << source
|
|
64
|
+
documents << { "route" => normalized_route, "output" => normalized_route }
|
|
65
|
+
next
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
prefix = "#{source}/"
|
|
69
|
+
bundle_entries = entries.values.select { |candidate| candidate.path.to_s.start_with?(prefix) }
|
|
70
|
+
index = entries["#{prefix}index.html"]
|
|
71
|
+
unless index && index.kind.to_sym == :attachment
|
|
72
|
+
diagnostics << diagnostic("missing_html_source", "HTML bundle source must contain an exact index.html", source)
|
|
73
|
+
next
|
|
74
|
+
end
|
|
75
|
+
normalized_route = normalize_directory_route(route, url_builder)
|
|
76
|
+
unless normalized_route
|
|
77
|
+
diagnostics << diagnostic("invalid_html_route", "an HTML bundle must map to a safe root-relative directory route", source)
|
|
78
|
+
next
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
bundle_entries.select { |candidate| candidate.kind.to_sym == :attachment }
|
|
82
|
+
.sort_by { |candidate| candidate.path.to_s.b }
|
|
83
|
+
.each do |candidate|
|
|
84
|
+
relative = candidate.path.to_s.delete_prefix(prefix)
|
|
85
|
+
output_route = join_route(normalized_route, relative, url_builder)
|
|
86
|
+
unless output_route
|
|
87
|
+
diagnostics << diagnostic(
|
|
88
|
+
"invalid_html_source",
|
|
89
|
+
"HTML bundle member path cannot be projected to a safe public route",
|
|
90
|
+
candidate.path.to_s
|
|
91
|
+
)
|
|
92
|
+
next
|
|
93
|
+
end
|
|
94
|
+
media_type = relative.end_with?(".html") ? "text/html" : candidate.media_type
|
|
95
|
+
projected_files << projected_file(candidate, output_route, media_type)
|
|
96
|
+
routes_by_source[candidate.path.to_s] = document_route(output_route)
|
|
97
|
+
if relative.end_with?(".html")
|
|
98
|
+
document_sources << candidate.path.to_s
|
|
99
|
+
documents << { "route" => document_route(output_route), "output" => output_route }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
Resolution.new(
|
|
105
|
+
projected_files: projected_files.sort_by(&:route),
|
|
106
|
+
routes_by_source: routes_by_source.sort.to_h,
|
|
107
|
+
document_sources: document_sources.sort,
|
|
108
|
+
documents: documents.sort_by { |document| document.fetch("route") },
|
|
109
|
+
files: projected_files.map(&:route).sort,
|
|
110
|
+
diagnostics: diagnostics.freeze
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def manifest(resolution)
|
|
115
|
+
return nil if resolution.documents.empty?
|
|
116
|
+
|
|
117
|
+
GeneratedFile.new(
|
|
118
|
+
route: MANIFEST_ROUTE,
|
|
119
|
+
content: RawHtmlManifest.generate(documents: resolution.documents, files: resolution.files),
|
|
120
|
+
media_type: "application/json"
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def empty_resolution
|
|
125
|
+
Resolution.new(projected_files: [], routes_by_source: {}, document_sources: [], documents: [], files: [], diagnostics: [])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def failure(code, message)
|
|
129
|
+
Resolution.new(
|
|
130
|
+
projected_files: [],
|
|
131
|
+
routes_by_source: {},
|
|
132
|
+
document_sources: [],
|
|
133
|
+
documents: [],
|
|
134
|
+
files: [],
|
|
135
|
+
diagnostics: [diagnostic(code, message)]
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def valid_source?(source)
|
|
140
|
+
return false if source.empty? || source == "." || source.start_with?("/", "\\") || source.include?("\\")
|
|
141
|
+
return false if source != source.unicode_normalize(:nfc)
|
|
142
|
+
return false if source == "_translations" || source.start_with?("_translations/")
|
|
143
|
+
|
|
144
|
+
source.split("/", -1).none? { |segment| segment.empty? || segment == "." || segment == ".." }
|
|
145
|
+
rescue EncodingError
|
|
146
|
+
false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def normalize_file_route(route, url_builder)
|
|
150
|
+
normalized = url_builder.validate_file_route(route, extension: ".html")
|
|
151
|
+
normalized if normalized && allowed_route?(normalized, url_builder)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def normalize_directory_route(route, url_builder)
|
|
155
|
+
normalized = url_builder.validate_permalink(route)
|
|
156
|
+
normalized if normalized && normalized != "/" && allowed_route?(normalized, url_builder)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def allowed_route?(route, url_builder)
|
|
160
|
+
key = url_builder.collision_key(route)
|
|
161
|
+
return false if key.start_with?("/assets/website/", "/assets/vault/")
|
|
162
|
+
|
|
163
|
+
baseurl = url_builder.baseurl
|
|
164
|
+
baseurl.empty? || !key.start_with?("#{url_builder.collision_key(baseurl)}")
|
|
165
|
+
rescue ArgumentError, EncodingError
|
|
166
|
+
false
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def projected_file(entry, route, media_type)
|
|
170
|
+
ProjectedFile.new(
|
|
171
|
+
source_path: entry.path,
|
|
172
|
+
route: route,
|
|
173
|
+
media_type: media_type,
|
|
174
|
+
size: entry.size,
|
|
175
|
+
device: entry.device,
|
|
176
|
+
inode: entry.inode,
|
|
177
|
+
mtime_ns: entry.mtime_ns
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def paths_overlap?(first, second)
|
|
182
|
+
left = first.unicode_normalize(:nfc).downcase(:fold)
|
|
183
|
+
right = second.unicode_normalize(:nfc).downcase(:fold)
|
|
184
|
+
left == right || left.start_with?("#{right}/") || right.start_with?("#{left}/")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def join_route(root, relative, url_builder)
|
|
188
|
+
encoded = relative.unicode_normalize(:nfc).split("/").map { |segment| URI.encode_uri_component(segment) }
|
|
189
|
+
route = "#{root}#{encoded.join('/')}"
|
|
190
|
+
url_builder.collision_key(route)
|
|
191
|
+
route
|
|
192
|
+
rescue ArgumentError, EncodingError, URI::InvalidURIError
|
|
193
|
+
nil
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def document_route(output_route)
|
|
197
|
+
return output_route unless output_route.end_with?("/index.html")
|
|
198
|
+
|
|
199
|
+
output_route.delete_suffix("index.html")
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def diagnostic(code, message, path = nil)
|
|
203
|
+
Diagnostic.new(severity: :error, code: code, message: message, path: path, span: nil)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require_relative "version"
|
|
6
|
+
require_relative "workspace_layout"
|
|
7
|
+
|
|
8
|
+
module JekyllObsidian
|
|
9
|
+
class Initializer
|
|
10
|
+
def self.run(root:, source: "docs", theme: "minimal")
|
|
11
|
+
root = File.realpath(root)
|
|
12
|
+
source = WorkspaceLayout.relative_source(source)
|
|
13
|
+
raise ArgumentError, "theme must be minimal or docs" unless %w[minimal docs].include?(theme)
|
|
14
|
+
|
|
15
|
+
content_root = File.join(root, source)
|
|
16
|
+
WorkspaceLayout.assert_path!(root, content_root, "website.source", missing: true)
|
|
17
|
+
if File.exist?(content_root) && !File.directory?(content_root)
|
|
18
|
+
raise ArgumentError, "website.source must be a directory: #{source}"
|
|
19
|
+
end
|
|
20
|
+
template_root = File.expand_path("../../scripts/templates", __dir__)
|
|
21
|
+
files = {
|
|
22
|
+
".github/jekyll-obsidian.yml" => YAML.dump({
|
|
23
|
+
"title" => "My Site", "website" => { "source" => source, "theme" => theme }
|
|
24
|
+
}).delete_prefix("---\n"),
|
|
25
|
+
".github/workflows/pages.yml" => File.read(File.join(template_root, "pages.yml"))
|
|
26
|
+
.gsub("__JEKYLL_OBSIDIAN_VERSION__", VERSION)
|
|
27
|
+
}
|
|
28
|
+
if !File.directory?(content_root) || Dir.empty?(content_root)
|
|
29
|
+
files["#{source}/index.md"] = "---\npublish: true\ntitle: Welcome\n---\n\n# Welcome\n\nYour site is ready for Markdown.\n"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
pending = files.reject do |relative, content|
|
|
33
|
+
path = File.join(root, relative)
|
|
34
|
+
WorkspaceLayout.assert_path!(root, path, relative, missing: true)
|
|
35
|
+
next false unless File.exist?(path)
|
|
36
|
+
unless File.file?(path) && File.binread(path) == content
|
|
37
|
+
raise ArgumentError, "#{relative} already exists. Edit it directly; init does not overwrite files."
|
|
38
|
+
end
|
|
39
|
+
true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
created_files = []
|
|
43
|
+
created_directories = []
|
|
44
|
+
begin
|
|
45
|
+
pending.each do |relative, content|
|
|
46
|
+
path = File.join(root, relative)
|
|
47
|
+
missing = []
|
|
48
|
+
parent = File.dirname(path)
|
|
49
|
+
until File.directory?(parent)
|
|
50
|
+
missing << parent
|
|
51
|
+
parent = File.dirname(parent)
|
|
52
|
+
end
|
|
53
|
+
missing.reverse_each do |directory|
|
|
54
|
+
Dir.mkdir(directory)
|
|
55
|
+
created_directories << directory
|
|
56
|
+
end
|
|
57
|
+
File.open(path, File::WRONLY | File::CREAT | File::EXCL, 0o644) { |file| file.write(content) }
|
|
58
|
+
created_files << path
|
|
59
|
+
end
|
|
60
|
+
rescue StandardError
|
|
61
|
+
created_files.reverse_each { |path| File.unlink(path) }
|
|
62
|
+
created_directories.reverse_each { |path| Dir.rmdir(path) if Dir.empty?(path) }
|
|
63
|
+
raise
|
|
64
|
+
end
|
|
65
|
+
pending.keys
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|