|
2 | 2 | "Clerk's Static App Builder." |
3 | 3 | (:require [babashka.fs :as fs] |
4 | 4 | [babashka.process :refer [sh]] |
| 5 | + [clojure.edn :as edn] |
5 | 6 | [clojure.java.browse :as browse] |
6 | 7 | [clojure.java.io :as io] |
7 | 8 | [clojure.string :as str] |
|
12 | 13 | [nextjournal.clerk.view :as view] |
13 | 14 | [nextjournal.clerk.viewer :as viewer] |
14 | 15 | [nextjournal.clerk.webserver :as webserver] |
15 | | - [nextjournal.clerk.config :as config])) |
| 16 | + [nextjournal.clerk.config :as config]) |
| 17 | + (:import (java.net URL))) |
16 | 18 |
|
17 | 19 | (def clerk-docs |
18 | 20 | (into ["CHANGELOG.md" |
|
124 | 126 | (throw (ex-info "nothing to build" (merge {:expanded-paths expanded-paths} (select-keys build-opts [:paths :paths-fn :index])))) |
125 | 127 | expanded-paths)) |
126 | 128 |
|
127 | | -(defn ^:private maybe-add-index [{:as build-opts :keys [paths paths-fn index]} resolved-paths] |
128 | | - (when (and index (or (not (string? index)) (not (fs/exists? index)))) |
129 | | - (throw (ex-info "`:index` must be string and point to existing file" {:index index}))) |
| 129 | +(defn ^:private maybe-add-index [{:as opts :keys [index]} resolved-paths] |
| 130 | + (when (contains? opts :index) |
| 131 | + (or (instance? URL index) |
| 132 | + (and (string? index) (fs/exists? index)) |
| 133 | + (throw (ex-info "`:index` must be either an instance of java.net.URL or a string and point to an existing file" {:index index})))) |
130 | 134 | (cond-> resolved-paths |
131 | 135 | (and index (not (contains? (set resolved-paths) index))) |
132 | 136 | (conj index))) |
|
185 | 189 | (-> opts' |
186 | 190 | (update :resource->url #(merge {} %2 %1) @config/!resource->url) |
187 | 191 | (cond-> #_opts' |
188 | | - expand-paths? (dissoc :expand-paths?) |
189 | | - (and (not index) (= 1 (count expanded-paths))) (assoc :index (first expanded-paths))))))) |
| 192 | + expand-paths? |
| 193 | + (dissoc :expand-paths?) |
| 194 | + (and (not index) (= 1 (count expanded-paths))) |
| 195 | + (assoc :index (first expanded-paths)) |
| 196 | + (and (not index) (< 1 (count expanded-paths)) (every? (complement #{"index.clj"}) expanded-paths)) |
| 197 | + (as-> opts |
| 198 | + (let [index (io/resource "nextjournal/clerk/index.clj")] |
| 199 | + (-> opts (assoc :index index) (update :expanded-paths conj index))))))))) |
190 | 200 |
|
191 | 201 | #_(process-build-opts {:index 'book.clj :expand-paths? true}) |
192 | 202 | #_(process-build-opts {:paths ["notebooks/rule_30.clj"] :expand-paths? true}) |
| 203 | +#_(process-build-opts {:paths ["notebooks/rule_30.clj" |
| 204 | + "notebooks/markdown.md"] :expand-paths? true}) |
193 | 205 |
|
194 | 206 | (defn build-path->url [{:as opts :keys [bundle?]} docs] |
195 | 207 | (into {} |
196 | 208 | (map (comp (juxt identity #(cond-> (->> % (viewer/map-index opts) strip-index) (not bundle?) ->html-extension)) |
197 | | - :file)) |
| 209 | + str :file)) |
198 | 210 | docs)) |
199 | 211 | #_(build-path->url {:bundle? false} [{:file "notebooks/foo.clj"} {:file "index.clj"}]) |
200 | 212 | #_(build-path->url {:bundle? true} [{:file "notebooks/foo.clj"} {:file "index.clj"}]) |
201 | 213 |
|
202 | 214 | (defn build-static-app-opts [{:as opts :keys [bundle? out-path browse? index]} docs] |
203 | | - (let [path->doc (into {} (map (juxt :file :viewer)) docs)] |
| 215 | + (let [path->doc (into {} (map (juxt (comp str :file) :viewer)) docs)] |
204 | 216 | (assoc opts |
205 | 217 | :bundle? bundle? |
206 | 218 | :path->doc path->doc |
|
230 | 242 | [opts docs] |
231 | 243 | (let [{:as opts :keys [bundle? out-path browse? ssr?]} (process-build-opts opts) |
232 | 244 | index-html (str out-path fs/file-separator "index.html") |
233 | | - {:as static-app-opts :keys [path->url path->doc]} (build-static-app-opts opts docs)] |
| 245 | + {:as static-app-opts :keys [path->url path->doc]} (build-static-app-opts (viewer/update-if opts :index str) docs)] |
| 246 | + (when-not (contains? (-> path->url vals set) "") |
| 247 | + (throw (ex-info "Index must have been processed at this point" {:opts opts :docs docs}))) |
234 | 248 | (when-not (fs/exists? (fs/parent index-html)) |
235 | 249 | (fs/create-dirs (fs/parent index-html))) |
236 | 250 | (if bundle? |
237 | 251 | (spit index-html (view/->static-app static-app-opts)) |
238 | | - (do (when-not (contains? (-> path->url vals set) "") ;; no user-defined index page |
239 | | - (spit index-html (view/->static-app (dissoc static-app-opts :path->doc)))) |
240 | | - (doseq [[path doc] path->doc] |
241 | | - (let [out-html (str out-path fs/file-separator (->> path (viewer/map-index opts) ->html-extension))] |
242 | | - (fs/create-dirs (fs/parent out-html)) |
243 | | - (spit out-html (view/->static-app (cond-> (assoc static-app-opts :path->doc (hash-map path doc) :current-path path) |
244 | | - ssr? ssr!))))))) |
| 252 | + (doseq [[path doc] path->doc] |
| 253 | + (let [out-html (str out-path fs/file-separator (->> path (viewer/map-index opts) ->html-extension))] |
| 254 | + (fs/create-dirs (fs/parent out-html)) |
| 255 | + (spit out-html (view/->static-app (cond-> (assoc static-app-opts :path->doc (hash-map path doc) :current-path path) |
| 256 | + ssr? ssr!)))))) |
245 | 257 | (when browse? |
246 | 258 | (browse/browse-url (-> index-html fs/absolutize .toString path-to-url-canonicalize))) |
247 | 259 | {:docs docs |
248 | 260 | :index-html index-html |
249 | | - :build-href (if (and @webserver/!server (= out-path default-out-path)) "/build" index-html)})) |
| 261 | + :build-href (if (and @webserver/!server (= out-path default-out-path)) "/build/" index-html)})) |
250 | 262 |
|
251 | 263 |
|
252 | 264 | (defn compile-css! |
|
287 | 299 | (defn doc-url |
288 | 300 | ([opts doc file path] (doc-url opts doc file path nil)) |
289 | 301 | ([{:as opts :keys [bundle?]} docs file path fragment] |
290 | | - (let [url (get (build-path->url opts docs) path)] |
| 302 | + (let [url (get (build-path->url (viewer/update-if opts :index str) docs) path)] |
291 | 303 | (if bundle? |
292 | 304 | (str "#/" url) |
293 | 305 | (str (viewer/relative-root-prefix-from (viewer/map-index opts file)) |
294 | 306 | url (when fragment (str "#" fragment))))))) |
295 | 307 |
|
| 308 | +(defn read-opts-from-deps-edn! [] |
| 309 | + (if (fs/exists? "deps.edn") |
| 310 | + (let [deps-edn (edn/read-string (slurp "deps.edn"))] |
| 311 | + (if-some [clerk-alias (get-in deps-edn [:aliases :nextjournal/clerk])] |
| 312 | + (get clerk-alias :exec-args |
| 313 | + {:error "No `:exec-args` found in `:nextjournal/clerk` alias."}) |
| 314 | + {:error "No `:nextjournal/clerk` alias found in `deps.edn`."})) |
| 315 | + {:error "No `deps.edn` found in project."})) |
| 316 | + |
| 317 | +(def ^:dynamic ^:private *build-opts* nil) |
| 318 | +(defn index-paths [] |
| 319 | + (let [{:as opts :keys [index error]} (or *build-opts* (read-opts-from-deps-edn!))] |
| 320 | + (if error opts {:paths (remove #{index "index.clj"} (expand-paths opts))}))) |
| 321 | + |
| 322 | +#_(index-paths) |
| 323 | +#_(nextjournal.clerk/show! 'nextjournal.clerk.index) |
| 324 | + |
296 | 325 | (defn build-static-app! [{:as opts :keys [bundle?]}] |
297 | 326 | (let [{:as opts :keys [download-cache-fn upload-cache-fn report-fn compile-css? expanded-paths error]} |
298 | 327 | (try (process-build-opts (assoc opts :expand-paths? true)) |
|
324 | 353 | (report-fn {:stage :building :doc doc :idx idx}) |
325 | 354 | (let [{result :result duration :time-ms} (eval/time-ms |
326 | 355 | (try |
327 | | - (let [doc (binding [viewer/doc-url (partial doc-url opts state file)] |
328 | | - (eval/eval-analyzed-doc doc))] |
329 | | - (assoc doc :viewer (view/doc->viewer (assoc opts :inline-results? true) doc))) |
| 356 | + (binding [*build-opts* opts |
| 357 | + viewer/doc-url (partial doc-url opts state file)] |
| 358 | + (let [doc (eval/eval-analyzed-doc doc)] |
| 359 | + (assoc doc :viewer (view/doc->viewer (assoc opts :static-build? true |
| 360 | + :nav-path (str file)) doc)))) |
330 | 361 | (catch Exception e |
331 | 362 | {:error e})))] |
332 | 363 | (report-fn (merge {:stage :built :duration duration :idx idx} |
|
369 | 400 | :resource->url {"/js/viewer.js" "/viewer.js"} |
370 | 401 | :paths ["notebooks/cherry.clj"] |
371 | 402 | :out-path "build"}) |
| 403 | +#_(build-static-app! {:paths ["CHANGELOG.md" |
| 404 | + "notebooks/markdown.md" |
| 405 | + "notebooks/viewers/image.clj" |
| 406 | + "notebooks/viewers/html.cj"] |
| 407 | + :git/sha "d60f5417" |
| 408 | + :git/url "https://github.com/nextjournal/clerk"}) |
0 commit comments