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.
Files changed (191) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +133 -0
  4. data/website/_config.yml +21 -0
  5. data/website/_includes/website/archive-timeline.html +8 -0
  6. data/website/_includes/website/blog-ledger.html +19 -0
  7. data/website/_includes/website/blog-navigation.html +7 -0
  8. data/website/_includes/website/comments.html +22 -0
  9. data/website/_includes/website/contact-icon.html +14 -0
  10. data/website/_includes/website/context-tags.html +7 -0
  11. data/website/_includes/website/context.html +3 -0
  12. data/website/_includes/website/dialogs.html +71 -0
  13. data/website/_includes/website/docs-navigation.html +7 -0
  14. data/website/_includes/website/docs-tree.html +8 -0
  15. data/website/_includes/website/footer.html +14 -0
  16. data/website/_includes/website/github-icon.html +3 -0
  17. data/website/_includes/website/head.html +41 -0
  18. data/website/_includes/website/header.html +35 -0
  19. data/website/_includes/website/language-switcher.html +16 -0
  20. data/website/_includes/website/local-graph.html +25 -0
  21. data/website/_includes/website/note-header.html +35 -0
  22. data/website/_includes/website/outline.html +9 -0
  23. data/website/_includes/website/page-actions.html +34 -0
  24. data/website/_includes/website/portfolio-grid.html +36 -0
  25. data/website/_includes/website/portfolio-topics.html +4 -0
  26. data/website/_includes/website/post-cards.html +16 -0
  27. data/website/_includes/website/related-articles.html +7 -0
  28. data/website/_includes/website/relations.html +16 -0
  29. data/website/_includes/website/source-actions.html +28 -0
  30. data/website/_includes/website/system-page.html +31 -0
  31. data/website/_includes/website/tab-collection.html +28 -0
  32. data/website/_includes/website/tag-filter.html +12 -0
  33. data/website/_layouts/website-docs.html +20 -0
  34. data/website/_layouts/website-minimal.html +93 -0
  35. data/website/_layouts/website-redirect.html +17 -0
  36. data/website/assets/chunks/abnfDiagram-N423BO3Z-TSORAEI2.js +1 -0
  37. data/website/assets/chunks/architecture-TIHT7OUA-OUC5PEC7.js +1 -0
  38. data/website/assets/chunks/architectureDiagram-T3A2C74G-6P5MB2DJ.js +36 -0
  39. data/website/assets/chunks/blockDiagram-VBNYF7ZC-ZWINYROZ.js +132 -0
  40. data/website/assets/chunks/c4Diagram-5PPSVZJV-R5RMEAOT.js +10 -0
  41. data/website/assets/chunks/chunk-22DRK7VS.js +1 -0
  42. data/website/assets/chunks/chunk-25REBZ7M.js +2 -0
  43. data/website/assets/chunks/chunk-2RQCUDTG.js +1 -0
  44. data/website/assets/chunks/chunk-34FHPXU4.js +1 -0
  45. data/website/assets/chunks/chunk-57XXJOQN.js +10 -0
  46. data/website/assets/chunks/chunk-6DCNDVBX.js +2 -0
  47. data/website/assets/chunks/chunk-6DHFEWGX.js +1 -0
  48. data/website/assets/chunks/chunk-6VQ47JJS.js +2 -0
  49. data/website/assets/chunks/chunk-7CSIRG7W.js +127 -0
  50. data/website/assets/chunks/chunk-A43YELH5.js +1 -0
  51. data/website/assets/chunks/chunk-AIX6IH4O.js +1 -0
  52. data/website/assets/chunks/chunk-B3K7RH5O.js +88 -0
  53. data/website/assets/chunks/chunk-CBGUD2NP.js +1 -0
  54. data/website/assets/chunks/chunk-D3WOCELE.js +1 -0
  55. data/website/assets/chunks/chunk-D6FURU6X.js +1 -0
  56. data/website/assets/chunks/chunk-DGEGT77F.js +1 -0
  57. data/website/assets/chunks/chunk-DN52R6U7.js +1 -0
  58. data/website/assets/chunks/chunk-E3T3IFAC.js +1 -0
  59. data/website/assets/chunks/chunk-EIBFAW37.js +1 -0
  60. data/website/assets/chunks/chunk-ERWOH47R.js +1 -0
  61. data/website/assets/chunks/chunk-ETOVTYWF.js +1 -0
  62. data/website/assets/chunks/chunk-GHD3PFWA.js +231 -0
  63. data/website/assets/chunks/chunk-GMBKQ7NZ.js +1 -0
  64. data/website/assets/chunks/chunk-GQM25EPP.js +156 -0
  65. data/website/assets/chunks/chunk-GSOU4UTT.js +1 -0
  66. data/website/assets/chunks/chunk-HEMELP7J.js +1 -0
  67. data/website/assets/chunks/chunk-HZ5I5NJO.js +1 -0
  68. data/website/assets/chunks/chunk-IP56FOSI.js +70 -0
  69. data/website/assets/chunks/chunk-L7BMR7WL.js +1 -0
  70. data/website/assets/chunks/chunk-LGUG3SQB.js +1 -0
  71. data/website/assets/chunks/chunk-LOMHZYS2.js +1 -0
  72. data/website/assets/chunks/chunk-MII4VZEB.js +3 -0
  73. data/website/assets/chunks/chunk-MYMJLHQL.js +62 -0
  74. data/website/assets/chunks/chunk-NBH7YOBD.js +15 -0
  75. data/website/assets/chunks/chunk-NCOVM3YF.js +1 -0
  76. data/website/assets/chunks/chunk-OAGTSILX.js +1 -0
  77. data/website/assets/chunks/chunk-OCX5OCIU.js +321 -0
  78. data/website/assets/chunks/chunk-OE3NOVIH.js +1 -0
  79. data/website/assets/chunks/chunk-PLP5XUTJ.js +1 -0
  80. data/website/assets/chunks/chunk-PNOERP6G.js +1 -0
  81. data/website/assets/chunks/chunk-QBPQ7DPE.js +1 -0
  82. data/website/assets/chunks/chunk-RWFFDL53.js +1 -0
  83. data/website/assets/chunks/chunk-SEGIW3MP.js +1 -0
  84. data/website/assets/chunks/chunk-SZDMZNUS.js +1 -0
  85. data/website/assets/chunks/chunk-T2QBAPVT.js +1 -0
  86. data/website/assets/chunks/chunk-UE533OZ7.js +1 -0
  87. data/website/assets/chunks/chunk-UTWOJE2A.js +1 -0
  88. data/website/assets/chunks/chunk-VOCDYA3H.js +1 -0
  89. data/website/assets/chunks/chunk-VYZZPKGK.js +161 -0
  90. data/website/assets/chunks/chunk-WWLZIRS6.js +1 -0
  91. data/website/assets/chunks/chunk-XNXYDBKX.js +32 -0
  92. data/website/assets/chunks/chunk-XQUIO562.js +1 -0
  93. data/website/assets/chunks/chunk-Y2XVFW7T.js +2 -0
  94. data/website/assets/chunks/chunk-YORIST7Z.js +1 -0
  95. data/website/assets/chunks/chunk-Z2WBQPVY.js +206 -0
  96. data/website/assets/chunks/classDiagram-JCYQIIEL-OFDVDDIM.js +1 -0
  97. data/website/assets/chunks/classDiagram-v2-OCEON4UE-E36B6BGQ.js +1 -0
  98. data/website/assets/chunks/cose-bilkent-JH36ORCC-CBAW64AB.js +1 -0
  99. data/website/assets/chunks/cynefin-VYW2F7L2-CBO6DLNV.js +1 -0
  100. data/website/assets/chunks/cynefinDiagram-MW4NZA55-DFINLOUY.js +62 -0
  101. data/website/assets/chunks/dagre-VZM6K2ZE-BZFBSZAH.js +4 -0
  102. data/website/assets/chunks/diagram-7IWD3JNH-XQHTVOU2.js +30 -0
  103. data/website/assets/chunks/diagram-B4RE2ZJO-FTSXBZJR.js +3 -0
  104. data/website/assets/chunks/diagram-LBJQPF4R-ZSZW4OLH.js +24 -0
  105. data/website/assets/chunks/diagram-Q27KOJAE-2IV5TVCJ.js +24 -0
  106. data/website/assets/chunks/diagram-UB23O5K3-D6PDUTFL.js +41 -0
  107. data/website/assets/chunks/ebnfDiagram-BXEA7PRR-C6MAER3M.js +1 -0
  108. data/website/assets/chunks/erDiagram-JOGREHBK-EDAD2H5M.js +85 -0
  109. data/website/assets/chunks/eventmodeling-45OFAUF4-4K577DMF.js +1 -0
  110. data/website/assets/chunks/flowDiagram-UKHOOZJN-545F4YX3.js +1 -0
  111. data/website/assets/chunks/ganttDiagram-PKOTCBZU-7A7EKMY3.js +292 -0
  112. data/website/assets/chunks/gitGraph-TEB2WS4Q-3WUB7WYQ.js +1 -0
  113. data/website/assets/chunks/gitGraphDiagram-DS77QQ5N-A5UWV5FF.js +106 -0
  114. data/website/assets/chunks/info-DKCQHKI2-EOOHI7XS.js +1 -0
  115. data/website/assets/chunks/infoDiagram-6WML65LV-CWIOOWJP.js +2 -0
  116. data/website/assets/chunks/ishikawaDiagram-WSZJBQD7-CEASQVDL.js +70 -0
  117. data/website/assets/chunks/journeyDiagram-NVQOT4AX-LYG3S2AW.js +139 -0
  118. data/website/assets/chunks/kanban-definition-27J2QSJJ-HCC6KAYI.js +89 -0
  119. data/website/assets/chunks/katex-F4MQ6FB3.js +257 -0
  120. data/website/assets/chunks/mindmap-definition-FAOFIHXS-JMGSM3RE.js +96 -0
  121. data/website/assets/chunks/packet-7NZHBO7P-W3SJA3MY.js +1 -0
  122. data/website/assets/chunks/pegDiagram-VL7TDLO6-4JO3Y3Z3.js +1 -0
  123. data/website/assets/chunks/pie-RZYD4A2V-YOIQBTY7.js +1 -0
  124. data/website/assets/chunks/pieDiagram-7S7Q4E2Y-VFQ7SE46.js +39 -0
  125. data/website/assets/chunks/quadrantDiagram-CIZ2JOQS-JISDI2S6.js +7 -0
  126. data/website/assets/chunks/radar-I7S5WNFK-NMKP5AZO.js +1 -0
  127. data/website/assets/chunks/railroad-3IZDKUUU-7MOIV7CI.js +1 -0
  128. data/website/assets/chunks/railroad-abnf-AHOZXSZD-HXNH5SLO.js +1 -0
  129. data/website/assets/chunks/railroad-ebnf-EBAXGLYW-OWDJEWMK.js +1 -0
  130. data/website/assets/chunks/railroad-peg-LSFZ7HO6-ASSINGP5.js +1 -0
  131. data/website/assets/chunks/railroadDiagram-AXF67PYL-AFV5VP5F.js +1 -0
  132. data/website/assets/chunks/requirementDiagram-LRYGKXZP-RNRPEHHN.js +84 -0
  133. data/website/assets/chunks/sankeyDiagram-W5VNT64P-O54R2L3E.js +40 -0
  134. data/website/assets/chunks/sequenceDiagram-SI44F4Z6-42WISKJ7.js +162 -0
  135. data/website/assets/chunks/sizeCapture-X5ZJPWSS-LRN2Q37T.js +1 -0
  136. data/website/assets/chunks/stateDiagram-OKZ733FA-BWIBJXIR.js +1 -0
  137. data/website/assets/chunks/stateDiagram-v2-UEYNNEHI-L73LHL3L.js +1 -0
  138. data/website/assets/chunks/swimlanes-SLNWSIFB-ECOAE7LQ.js +1 -0
  139. data/website/assets/chunks/swimlanesDiagram-ULZ7WXOC-3CKKKFZZ.js +8 -0
  140. data/website/assets/chunks/timeline-definition-Z64GVDOM-HR2OL5G7.js +120 -0
  141. data/website/assets/chunks/treeView-QDETBFTQ-C6D5WFJA.js +1 -0
  142. data/website/assets/chunks/treemap-6X3UGDF4-JFCDMSCA.js +1 -0
  143. data/website/assets/chunks/vennDiagram-T6HMQDX7-62WAAPTF.js +34 -0
  144. data/website/assets/chunks/wardley-OPB4EBWU-RQIOII7I.js +1 -0
  145. data/website/assets/chunks/wardleyDiagram-T6FBY63Y-KUXO3NHV.js +78 -0
  146. data/website/assets/chunks/xychartDiagram-ELKLHX3M-I54W4HJ7.js +7 -0
  147. data/website/assets/color-scheme-bootstrap-4H4ZR4BW.js +1 -0
  148. data/website/assets/docs-3WBBQT6K.css +1 -0
  149. data/website/assets/docs-W3D5DWQF.js +1 -0
  150. data/website/assets/docs-navigation-75ALFYBA.js +1 -0
  151. data/website/assets/graph-QWL54LWD.js +1 -0
  152. data/website/assets/manifest.json +314 -0
  153. data/website/assets/math-7JTH4OWZ.js +21 -0
  154. data/website/assets/mermaid-RXC6URG4.js +10 -0
  155. data/website/assets/minimal-3NPTDWQX.js +1 -0
  156. data/website/assets/minimal-E2VATXXS.css +1 -0
  157. data/website/assets/previews-FMHOX7UX.js +1 -0
  158. data/website/assets/search-BH73JZDU.js +1 -0
  159. data/website/assets/search-worker-SM4IM4J7.js +1 -0
  160. data/website/exe/jekyll-obsidian +5 -0
  161. data/website/lib/jekyll_obsidian/adapter.rb +733 -0
  162. data/website/lib/jekyll_obsidian/built_in_themes.rb +855 -0
  163. data/website/lib/jekyll_obsidian/cli.rb +84 -0
  164. data/website/lib/jekyll_obsidian/content_policy.rb +194 -0
  165. data/website/lib/jekyll_obsidian/destination_registry.rb +39 -0
  166. data/website/lib/jekyll_obsidian/directory_indexes.rb +30 -0
  167. data/website/lib/jekyll_obsidian/external_media.rb +371 -0
  168. data/website/lib/jekyll_obsidian/front_matter.rb +453 -0
  169. data/website/lib/jekyll_obsidian/github_markdown.rb +664 -0
  170. data/website/lib/jekyll_obsidian/html_publication.rb +206 -0
  171. data/website/lib/jekyll_obsidian/initializer.rb +68 -0
  172. data/website/lib/jekyll_obsidian/localized_compiler.rb +1044 -0
  173. data/website/lib/jekyll_obsidian/media_policy.rb +41 -0
  174. data/website/lib/jekyll_obsidian/ofm_scanner.rb +493 -0
  175. data/website/lib/jekyll_obsidian/output_text.rb +13 -0
  176. data/website/lib/jekyll_obsidian/preview.rb +93 -0
  177. data/website/lib/jekyll_obsidian/published_markdown.rb +32 -0
  178. data/website/lib/jekyll_obsidian/raw_html_manifest.rb +66 -0
  179. data/website/lib/jekyll_obsidian/runtime.rb +197 -0
  180. data/website/lib/jekyll_obsidian/site_compilation.rb +147 -0
  181. data/website/lib/jekyll_obsidian/site_navigation.rb +517 -0
  182. data/website/lib/jekyll_obsidian/url_builder.rb +163 -0
  183. data/website/lib/jekyll_obsidian/value_objects.rb +198 -0
  184. data/website/lib/jekyll_obsidian/vault_compiler.rb +2771 -0
  185. data/website/lib/jekyll_obsidian/version.rb +6 -0
  186. data/website/lib/jekyll_obsidian/workspace_layout.rb +104 -0
  187. data/website/lib/jekyll_obsidian.rb +22 -0
  188. data/website/scripts/audit-site.rb +120 -0
  189. data/website/scripts/templates/pages.yml +55 -0
  190. data/website/scripts/verify-site-urls.rb +585 -0
  191. 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