Skip to content

Commit

Permalink
3 core stages/fns: plan!, assemble, construct!
Browse files Browse the repository at this point in the history
Naming things takes a long time.
  • Loading branch information
respatialized committed Jan 10, 2024
1 parent f8ef593 commit 2496e41
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 80 deletions.
84 changes: 49 additions & 35 deletions dev/site/fabricate/dev/build.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,61 @@
[clojure.string :as str]
[clojure.java.io :as io]))


(defn create-dir-recursive
[target-dir]
(->> (fs/path (fs/cwd) target-dir)
(fs/relativize (fs/cwd))
fs/components
(reduce (fn [paths path] (conj paths (fs/path (peek paths) path))) [])
(filter #(not (fs/exists? %)))
(run! fs/create-dir)))
(let [absolute-path? (fs/absolute? target-dir)
target-dir (if (fs/relative? target-dir)
(fs/relativize (fs/cwd) (fs/path (fs/cwd) target-dir))
target-dir)]
(->> target-dir
fs/components
(reduce (fn [paths path]
(conj paths
(let [next-path (fs/path (peek paths) path)]
(if absolute-path?
(fs/absolutize (str fs/file-separator next-path))
next-path))))
[])
(filter #(not (fs/exists? %)))
(run! fs/create-dir))))

(defn create-dir? [d] (when-not (fs/exists? d) (create-dir-recursive d)))

(defn create-publish-dirs!
[{:keys [site.fabricate.page/publish-dir], :as options}]
(let [css-dir (fs/path publish-dir "css")
[{:keys [site.fabricate.api/options], :as site}]
(let [{:keys [site.fabricate.page/publish-dir]} options
css-dir (fs/path publish-dir "css")
fonts-dir (fs/path publish-dir "fonts")]
(run! create-dir? [publish-dir css-dir fonts-dir])))
(run! create-dir? [publish-dir css-dir fonts-dir])
site))

(defn get-css!
[{:keys [site.fabricate.page/publish-dir], :as options}]
[{:keys [site.fabricate.api/options :as site]}]
(let
[remedy
[{:keys [site.fabricate.page/publish-dir]} options
remedy
{:file (fs/file (fs/path publish-dir "css" "remedy.css")),
:url
"https://raw.githubusercontent.com/jensimmons/cssremedy/6590d9630bdd324469620636d85b7ea3753e9a7b/css/remedy.css"}
normalize
{:file (fs/file (fs/path publish-dir "css" "normalize.css")),
:url "https://unpkg.com/@csstools/[email protected]/normalize.css"}]
(doseq [{:keys [file url]} [normalize remedy]]
(when-not (fs/exists? file) (io/copy url file)))))
(when-not (fs/exists? file) (io/copy url file))))
site)

(defn copy-fonts!
[{:keys [site.fabricate.page/publish-dir], :as options}]
(let [font-dir (System/getProperty "user.font-dir")
[{:keys [site.fabricate.api/options], :as site}]
(let [{:keys [site.fabricate.page/publish-dir]} options
font-dir (System/getProperty "user.font-dir")
fonts []]
(doseq [{:keys [src file]} fonts]
(when-not (fs/exists? file) (fs/copy src file)))))

(when-not (fs/exists? file) (fs/copy src file))))
site)

(def options {:site.fabricate.page/publish-dir "docs"})
(def options
"Options for building Fabricate's own documentation."
{:site.fabricate.page/publish-dir "docs"})

(def setup-tasks [create-publish-dirs! get-css! copy-fonts!])

Expand Down Expand Up @@ -97,27 +112,24 @@
[entry]
(let [parsed-page (read/parse (slurp (:site.fabricate.source/location entry)))
evaluated-page (read/eval-all parsed-page)
page-metadata (update (page/lift-metadata evaluated-page
(last (read/get-metadata
parsed-page)))
:page-style
eval)
page-metadata (page/lift-metadata evaluated-page
(:metadata (meta evaluated-page)))
hiccup-page [:html (page/doc-header page-metadata)
[:body
[:main
(apply conj
[:article {:lang "en-us"}]
(page/parse-paragraphs evaluated-page))]
[:article {:lang "en-us"}]
(page/parse-paragraphs evaluated-page))]
[:footer [:div [:a {:href "/"} "Home"]]]]]]
(assoc entry
:site.fabricate.document/data hiccup-page
:site.fabricate.page/title (:title page-metadata))))
:site.fabricate.document/data hiccup-page
:site.fabricate.page/title (:title page-metadata))))

(defmethod api/assemble [:site.fabricate.read/v0 :hiccup]
(defmethod api/build [:site.fabricate.read/v0 :hiccup]
[entry]
(fabricate-v0->hiccup entry))

(defmethod api/assemble [:site.fabricate.markdown/v0 :markdown]
(defmethod api/build [:site.fabricate.markdown/v0 :markdown]
[entry]
(assoc entry
:site.fabricate.document/data (slurp (:site.fabricate.source/location
Expand Down Expand Up @@ -178,10 +190,12 @@
:dir (str (fs/path (fs/cwd) "docs")),
:port 8002,
:no-cache true}))
(let [entries (api/plan! setup-tasks options)
assembled-entries (api/combine []
{:site.fabricate.api/options options,
:site.fabricate.api/entries entries})]
(api/construct! [] assembled-entries))
(garden/css styles/docs)
;; it's hard to beat this simplicity. also, a point in favor of the
;; "return a modified site with modified options" implementation:
;; potentially storing a reference to a server or other stateful
;; component
(->> {:site.fabricate.api/options options}
(api/plan! setup-tasks)
(api/assemble [])
(api/construct! []))
(run! fs/delete (fs/glob "docs" "**.html")))
91 changes: 65 additions & 26 deletions src/site/fabricate/prototype/api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -131,41 +131,79 @@
(fn [src _options] src))



(def site-fn-schema
"Function schema for functions that operate on a site"
(m/schema [:schema
{:registry {::task-list [:or [:* [:schema [:ref ::site-fn]]]
[:map-of [:schema [:ref ::site-fn]]
[:schema [:ref ::site-fn]]]],
::site-map
[:map [:site.fabricate.api/entries [:* entry-schema]]
[:site.fabricate.api/options :map]],
::site-fn [:=>
[:cat [:schema [:ref ::task-list]]
[:schema [:ref ::site-map]]]
[:schema [:ref ::site-map]]]}} ::site-fn]))


;; question: should this actually be exactly the same signature /
;; implementation as the other functions in the API? Making it different
;; - by simply executing each setup task for its side effects rather than
;; for returning an updated site - encourages keeping the initial setup
;; simpler, and makes the API somewhat more orthogonal. I think this
;; question is better answered through use while the API continues to
;; stabilize.

(defn plan!
"Execute all the given `setup-tasks`, then collect the list of entries."
[setup-tasks options]
(doseq [t setup-tasks] (t options))
(vec (for [[collect-pattern _] (.getMethodTable collect)
entry-data (collect collect-pattern options)
output (:site.fabricate.page/outputs entry-data)]
(-> entry-data
(dissoc :site.fabricate.page/outputs)
(merge output)))))
"Execute all the given `setup-tasks`, then `collect` the list of entries from each source.
This list of entries will be appended to any entries passed in as a component of the `site` argument."
[setup-tasks
{:keys [site.fabricate.api/entries :site.fabricate.api/options],
:or {entries []},
:as site}]
(let [post-setup-site (reduce (fn [site task] (task site)) site setup-tasks)
collected-entries
(vec (for [[collect-pattern _] (.getMethodTable collect)
entry-data (collect collect-pattern options)
output (:site.fabricate.page/outputs entry-data)]
(-> entry-data
(dissoc :site.fabricate.page/outputs)
(merge output))))]
(update post-setup-site
:site.fabricate.api/entries
(fn [es] (reduce conj es collected-entries)))))

;; not enforcing a spec on a multimethod seems like the best way of keeping
;; it open for extension, and keeps the API simpler.
(defmulti assemble
(defmulti build
"Generate structured (EDN) document content for an entry from a source format. Takes an entry and returns a document (entry)."
{:term/definition
{:source (URI. "https://www.merriam-webster.com/dictionary/assemble"),
:definition "to fit together the parts of"}}
(fn [entry] [(:site.fabricate.source/format entry)
(:site.fabricate.document/format entry)]))

(comment
{:term/definition
{:source (URI. "https://www.merriam-webster.com/dictionary/assemble"),
:definition "to fit together the parts of"}})


(defn combine
"Prepare the entries for `produce!` by calling `assemble` on each entry, then running `tasks` on the results."
(defn assemble
"Prepare the entries for `produce!` by calling `build` on each entry, then running `tasks` on the results."
{:term/definition
{:source (URI. "https://www.merriam-webster.com/dictionary/assemble"),
:definition "to fit together the parts of"}}
[tasks
{:keys [site.fabricate.api/entries site.fabricate.api/options], :as site}]
(let [sort-fn (get options :site.fabricate.api/entry-sort-fn identity)
assembled-entries (mapv assemble (sort-fn entries))]
doc-entries (mapv build (sort-fn entries))]
(reduce (fn [site task] (task site))
(assoc site :site.fabricate.api/entries assembled-entries)
(assoc site :site.fabricate.api/entries doc-entries)
tasks)))


(defmulti produce!
"Produce the content of a file from the results of the `assemble` operation and write it to disk. Takes an entry and returns an entry."
"Produce the content of a file from the results of the `build` operation and write it to disk. Takes an entry and returns an entry."
{:term/definition
{:source (URI. "https://www.merriam-webster.com/dictionary/produce"),
:definition
Expand All @@ -184,12 +222,13 @@
(run! produce! entries)
(reduce (fn [site task] (site entries)) init-site sorted-tasks)))

(defn build!
"`plan`, `assemble` and `produce!` all of the entries, writing their output to disk."
[options]
nil)

(defn rebuild!
"Idempotent version of `assemble` and `produce!`; called when a file change is detected."
[entry]
nil)
(comment
(defn build!
"`plan`, `assemble` and `produce!` all of the entries, writing their output to disk."
[options]
nil)
(defn rebuild!
"Idempotent version of `assemble` and `produce!`; called when a file change is detected."
[entry]
nil))
39 changes: 20 additions & 19 deletions test/site/fabricate/prototype/api_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,35 @@

(t/deftest entries
(let [tmp-dir (fs/create-temp-dir {:prefix "fabricate-test-"})
entry-data (api/plan! [(fn [_] (fs/create-dir (fs/path tmp-dir "css")))]
{:site.fabricate.page/publish-dir tmp-dir})]
{:keys [site.fabricate.api/entries], :as plan-data}
(api/plan! [(fn [s] (fs/create-dir (fs/path tmp-dir "css")) s)]
{:site.fabricate.api/options
{:site.fabricate.page/publish-dir tmp-dir}})]
(t/testing "planning"
(t/is (every? (m/validator api/entry-schema) entry-data)))
(t/is (every? (m/validator api/entry-schema) entries)))
(t/testing "building"
(doseq [e entries]
(let [built (api/build e)]
(t/is (some? (:site.fabricate.document/data built))))))
(t/testing "assembly"
(doseq [e entry-data]
(let [assembled (api/assemble e)]
(t/is (some? (:site.fabricate.document/data assembled))))))
(t/testing "combine"
(let [assembled (mapv #(api/assemble %) entry-data)
combined (api/combine [#(mapv doc->page
(:site.fabricate.api/entries %))]
{:site.fabricate.api/entries assembled})]
(t/is (contains? (first combined) :site.fabricate.page/data))))
(let [assembled (api/assemble [#(mapv doc->page
(:site.fabricate.api/entries %))]
{:site.fabricate.api/entries entries})]
(t/is (contains? (first assembled) :site.fabricate.page/data))))
(t/testing "production"
(doseq [e entry-data]
(let [assembled (api/assemble e)
(doseq [e entries]
(let [built (api/build e)
{:keys [site.fabricate.page/output], :as produced} (api/produce!
assembled)]
built)]
(t/is (some? (:site.fabricate.page/title produced))
(format "Page produced from %s should have a title"
(str (:site.fabricate.source/location assembled))))
(str (:site.fabricate.source/location built))))
(t/is (instance? java.io.File output))
(t/is (fs/exists? output)))))))

(comment
(api/build! {})
(fs/create-temp-dir {:prefix "something-"})
(def assembled-posts-test
(mapv api/assemble (api/plan {:input-dir "pages", :patterns api/patterns})))
(first assembled-posts-test))
(def built-posts-test
(mapv api/build (api/plan {:input-dir "pages", :patterns api/patterns})))
(first built-posts-test))

0 comments on commit 2496e41

Please sign in to comment.