Skip to content

Commit

Permalink
Add API test to ensure all Fabricate public vars are documented
Browse files Browse the repository at this point in the history
  • Loading branch information
respatialized committed Dec 11, 2024
1 parent 7611938 commit d7a3c4d
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 95 deletions.
95 changes: 38 additions & 57 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,57 +1,38 @@
{:aliases
{:dev {:extra-deps {babashka/babashka.curl {:mvn/version "0.1.2"}
com.clojure-goes-fast/clj-async-profiler
{:mvn/version "1.1.1"}
com.clojure-goes-fast/clj-memory-meter {:mvn/version
"0.3.0"}
com.cnuernber/charred {:mvn/version "1.033"}
com.kiranshila/cybermonday {:mvn/version "0.6.215"}
com.taoensso/tufte {:mvn/version "2.6.3"}
com.vladsch.flexmark/flexmark-ext-wikilink
{:mvn/version "0.64.8"}
criterium/criterium {:mvn/version "0.4.6"}
dev.onionpancakes/chassis {:mvn/version "1.0.365"}
garden/garden {:mvn/version "1.3.10"}
juxt/dirwatch {:mvn/version "0.2.5"}
zprint/zprint {:mvn/version "1.2.9"}}
:extra-paths ["test" "dev"]
:jvm-opts ["-Djdk.attach.allowAttachSelf"
"-XX:+UnlockDiagnosticVMOptions"
"-XX:+DebugNonSafepoints"
"-Dsite.fabricate.mode=dev"]}
:runner {:exec-args {:excludes [:performance]
:patterns ["^(?!.*performance).*$"]}
:exec-fn cognitect.test-runner.api/test
:extra-deps {io.github.cognitect-labs/test-runner
{:git/url
"https://github.com/cognitect-labs/test-runner"
:sha "7284cda41fb9edc0f3bc6b6185cfb7138fc8a023"}}
:main-opts ["-m" "cognitect.test-runner"]}
:serve {:main-opts ["-m" "http.server" "--dir" "docs"]}
:test {:extra-deps {babashka/babashka.curl {:mvn/version "0.1.2"}
com.brunobonacci/mulog {:mvn/version "0.9.0"}
com.gfredericks/test.chuck {:mvn/version "0.2.14"}
nu.validator/validator {:mvn/version "20.7.2"}
org.clojure/test.check {:mvn/version "1.1.1"}
site.fabricate/manual
{:git/url
"https://github.com/fabricate-site/manual.git"
:sha "ae660ad598f0c3d01beb6250789db8cc5a54a69a"}}
:extra-paths ["test"]}
:validate {:exec-args {:dirs ["docs/"]}
:exec-fn site.fabricate.prototype.check/html}}
:deps
{babashka/fs {:mvn/version "0.2.12"}
hiccup/hiccup {:mvn/version "2.0.0-RC2"}
instaparse/instaparse {:mvn/version "1.4.12"}
metosin/malli {:mvn/version "0.13.0"}
nasus/nasus {:git/url "https://github.com/kachayev/nasus/"
:sha "8923283db09f88f3503d0246bad31eea15341138"}
org.clojure/clojure {:mvn/version "1.11.3"}
org.clojure/data.finger-tree {:mvn/version "0.0.3"}
org.scicloj/kindly {:mvn/version "4-alpha16"}
rewrite-clj/rewrite-clj {:mvn/version "1.1.47"}
site.fabricate/adorn {:git/tag "v0.1.131-alpha"
:git/url "https://github.com/fabricate-site/adorn"
:sha "6a9a87959537a71760cd08f8fc2a292ba4957755"}}
:paths ["src"]}
{:aliases {:runner {:exec-args {:excludes [:performance]
:patterns ["^(?!.*performance).*$"]}
:exec-fn cognitect.test-runner.api/test
:extra-deps
{io.github.cognitect-labs/test-runner
{:git/url "https://github.com/cognitect-labs/test-runner"
:sha "7284cda41fb9edc0f3bc6b6185cfb7138fc8a023"}}
:main-opts ["-m" "cognitect.test-runner"]}
:test {:extra-deps
{babashka/babashka.curl {:mvn/version "0.1.2"}
com.brunobonacci/mulog {:mvn/version "0.9.0"}
com.gfredericks/test.chuck {:mvn/version "0.2.14"}
nu.validator/validator {:mvn/version "20.7.2"}
org.clojure/test.check {:mvn/version "1.1.1"}
site.fabricate/manual
{:git/url "https://github.com/fabricate-site/manual.git"
:sha "ae660ad598f0c3d01beb6250789db8cc5a54a69a"}}
:extra-paths ["test"]}
:validate {:exec-args {:dirs ["docs/"]}
:exec-fn site.fabricate.prototype.check/html}}
:deps {babashka/fs {:mvn/version "0.2.12"}
hiccup/hiccup {:mvn/version "2.0.0-RC2"}
instaparse/instaparse {:mvn/version "1.4.12"}
metosin/malli {:mvn/version "0.13.0"}
nasus/nasus {:git/url
"https://github.com/kachayev/nasus/"
:sha
"8923283db09f88f3503d0246bad31eea15341138"}
org.clojure/clojure {:mvn/version "1.11.3"}
org.clojure/data.finger-tree {:mvn/version "0.0.3"}
org.scicloj/kindly {:mvn/version "4-alpha16"}
rewrite-clj/rewrite-clj {:mvn/version "1.1.47"}
site.fabricate/adorn {:git/tag "v0.1.131-alpha"
:git/url
"https://github.com/fabricate-site/adorn"
:sha
"6a9a87959537a71760cd08f8fc2a292ba4957755"}}
:paths ["src"]}
3 changes: 3 additions & 0 deletions src/site/fabricate/api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@
collect-dispatch)

(def site-schema
"Malli schema describing the contents of a Fabricate site.
A site is the primary map passed between the 3 core API functions: plan!, assemble, and construct!"
(m/schema [:map [:site.fabricate.api/entries [:* entry-schema]]
[:site.fabricate.api/options :map]]))

Expand Down
3 changes: 3 additions & 0 deletions src/site/fabricate/prototype/hiccup.clj
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,15 @@
([form] (parse-paragraphs form {})))

(defn ->meta
"Convert the given key/value pair to a Hiccup/HTML metadata element."
{:malli/schema [:=> [:cat [:schema [:cat :any :any]]] [:cat [:= :meta] :map]]}
[[k v]]
(let [attrs (if (map? v) v {:content v})]
[:meta (merge {:name (if (keyword? k) (str (name k)) k)} attrs)]))


(def default-metadata-map
"Default metadata for Fabricate HTML pages."
{:title "Fabricate"
:description "Fabricate: static website generation for Clojure"
"viewport" "width=device-width, initial-scale=1.0"
Expand All @@ -148,6 +150,7 @@
:site-title "Fabricate"})

(def default-metadata
"Additional default metadata for Fabricate HTML pages."
(list [:meta {:charset "utf-8"}]
[:meta {:http-equiv "X-UA-Compatible" :content "IE=edge"}]))

Expand Down
38 changes: 34 additions & 4 deletions src/site/fabricate/prototype/html.clj
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,15 @@
#{:ins :del :object})

(def external-link-pattern
"Regex pattern for external URLs."
(re-pattern "https?://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"))
(def internal-link-pattern
"Regex pattern for internal URLs."
(re-pattern "/[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"))

(def url [:or [:re external-link-pattern] [:re internal-link-pattern]])
(def url
"Malli schema for URLs."
[:or [:re external-link-pattern] [:re internal-link-pattern]])

(def global-attributes
"MDN list of global HTML attributes as malli schema"
Expand All @@ -91,9 +95,12 @@
[:itemscope :boolean] [:itemtype [:re external-link-pattern]]
[:lang [:enum "en"]] [:tabindex :int] [:title :string] [:part :string]]))

(def atomic-element [:or :boolean :double :int :string :nil])
(def atomic-element
"Malli schema representing atomic HTML/Hiccup elements"
(m/schema [:or :boolean :double :int :string :nil]))

(def ^{:malli/schema [:=> [:cat :any] :boolean]} atomic-element?
"Returns true if the given value is an atomic HTML/Hiccup element"
(m/validator atomic-element))

(defn- ->hiccup-schema
Expand All @@ -110,6 +117,7 @@
(->hiccup-schema tag attr-schema content-schema {})))

(defn ns-kw
"Convert the given keyword to a namespaced version, using the current ns if not provided."
{:malli/schema [:=>
[:cat [:? [:fn #(.isInstance clojure.lang.Namespace %)]]
:keyword] :keyword]}
Expand Down Expand Up @@ -612,18 +620,27 @@
[:schema [:ref ::heading-content]]
[:schema [:ref ::phrasing-content]]]}} ::html]))

(def element (m/schema (schema/subschema html ::element)))
;; TODO: figure out how to register all of these schemas/subschemas
;; in the global registry specified by the site.fabricate.prototype.schema ns
(def element
"Malli schema for HTML elements."
(m/schema (schema/subschema html ::element)))

(def ^{:malli/schema [:=> [:cat :any] :boolean]} element? (m/validator element))
(def ^{:malli/schema [:=> [:cat :any] :boolean]} element?
"Returns true if the given value is a HTML element."
(m/validator element))

(def ^{:malli/schema [:=> [:cat :any] [:or :map [:= :malli.core/invalid]]]}
parse-element
"Parse the given value as a HTML element."
(m/parser element))

(def ^{:malli/schema [:=> [:cat :any] [:or :map :nil]]} explain-element
"Explain why the given value is not a HTML element."
(m/explainer element))

(def element-flat
"Simplified schema for HTML elements that does not distinguish between flow, phrasing, heading, or metadata content."
(let [elems (->> (dissoc (get (m/properties html) :registry)
::flow-content
::heading-content
Expand All @@ -642,48 +659,60 @@
(seq elems)))))

(def ^{:malli/schema [:=> [:cat :any] [:or :map :nil]]} element-flat-explainer
"Explain why the given value does not match the schema for flat HTML content."
(m/explainer element-flat))
(def ^{:malli/schema [:=> [:cat :any] [:or :map [:= :malli.core/invalid]]]}
parse-element-flat
"Parse the given value according to the schema for flat HTML content."
(m/parser element-flat))

(def element-validators
"Map with tags (keys) and predicates (values) that check if a value is a HTML element of the given type."
(let [kws (filter keyword? (keys (get (m/properties html) :registry)))]
(into {:atomic-element (m/validator atomic-element)}
(map (fn [t] [t (m/validator (schema/subschema html (ns-kw t)))])
kws))))

(def element-explainers
"Map with tags (keys) and functions (values) that explain why a value does not match the schema for the HTML element of the given type."
(let [kws (filter keyword? (keys (get (m/properties html) :registry)))]
(into {:atomic-element (m/explainer atomic-element)}
(map (fn [t] [t (m/explainer (schema/subschema html (ns-kw t)))])
kws))))

(def element-parsers
"Map with tags (keys) and functions (values) that parse a HTML element of the specific type."
(let [kws (filter keyword? (keys (get (m/properties html) :registry)))]
(into {:atomic-element (m/parser atomic-element)}
(map (fn [t] [t (m/parser (schema/subschema html (ns-kw t)))]) kws))))

;; TODO: make this function actually work

;; "content is palpable when it's neither empty or hidden;
;; it is content that is rendered and is substantive.
;; Elements whose model is flow content or phrasing content
;; should have at least one node which is palpable."
(defn palpable?
"Returns true if the node is visible."
{:malli/schema [:=> [:cat :any] :boolean]}
[c]
(some? (some #(m/validate atomic-element %)
(tree-seq #(and (vector? %) (keyword? (first %))) rest c))))

(def ^{:malli/schema [:=> [:cat :any] :boolean]} phrasing?
"Returns true if the element is HTML phrasing content."
(m/validator (schema/subschema html ::phrasing-content)))

(def ^{:malli/schema [:=> [:cat :any] :boolean]} heading?
"Returns true if the element is HTML heading content."
(m/validator (schema/subschema html ::heading-content)))

(def ^{:malli/schema [:=> [:cat :any] :boolean]} flow?
"Returns true if the element is HTML flow content."
(m/validator (schema/subschema html ::flow-content)))

(defn validate-element
"Attempt to validate the element based on its tag, or the keyword in the first position of the vector."
{:malli/schema [:=> [:cat :any] :boolean]}
[elem]
(if (and (vector? elem) (keyword? (first elem)))
Expand All @@ -709,6 +738,7 @@
(when schema (last (flatten (last (m/form schema))))))))

(def tag-contents
"Map with permitted contents across all tags."
(->>
(concat phrasing-tags flow-tags heading-tags sectioning-tags metadata-tags)
(map (fn [t] [t (permitted-contents t)]))
Expand Down
13 changes: 8 additions & 5 deletions src/site/fabricate/prototype/read.clj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
[:file {:optional true :doc "The source file of the expression"} :string]]))

(def ^{:malli/schema [:=> [:cat :any] :boolean]} fabricate-expr?
"Returns true if the given value matches the schema for parsed Fabricate expressions."
(m/validator parsed-expr-schema))

(def evaluated-expr-schema
Expand Down Expand Up @@ -84,13 +85,13 @@
'()
(map (fn [e] {:expr-src (str e) :expr e})
(read-str (str open front-matter close))))]
(with-meta
(cond (= delims "[]") (apply conj [] (concat parsed-front-matter forms))
(= delims "()") (concat () parsed-front-matter forms))
(meta ext-form))))
(with-meta (cond (= delims "[]") (into []
(concat parsed-front-matter forms))
(= delims "()") (concat () parsed-front-matter forms))
(meta ext-form))))

(def parsed-schema
"Malli schema describing the elements of a fabricate template after it has been parsed by the Instaparse grammar"
"Malli schema describing the elements of a fabricate template after it has been parsed by the Instaparse grammar."
(m/schema
[:schema
{:registry {::txt [:tuple {:encode/get {:leave second}} [:= :txt] :string]
Expand Down Expand Up @@ -156,12 +157,14 @@

;; TODO: this probably needs to be made more robust
(defn read-error?
"Returns true if the given expression failed to read into a valid Clojure form."
{:malli/schema [:=> [:cat parsed-expr-schema] :boolean]}
[error-form]
(= "Unexpected EOF." (get-in error-form [:error :data :msg])))

;; TODO: should this be a multimethod?
(defn error->hiccup
"Return a Hiccup form with context for the error."
{:malli/schema [:=> [:cat parsed-expr-schema] error-form-schema]}
[{:keys [expr-src exec expr error result display] :as parsed-expr}]
[:div {:class "fabricate-error"} [:h6 "Error"]
Expand Down
2 changes: 1 addition & 1 deletion src/site/fabricate/prototype/read/grammar.clj
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
terminal-lookahead "(?=\\Z|(?:[\\]})]//🔚|✳|🔚))"]
(str fast-possessive "|(" reluctant-txt terminal-lookahead ")")))

(def delimiters ["" "🔚"])
(def delimiters "Default delimiters for Fabricate." ["" "🔚"])

(def grammar
"The formal grammar for Fabricate templates."
Expand Down
15 changes: 9 additions & 6 deletions src/site/fabricate/prototype/schema.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(ns site.fabricate.prototype.schema
"Utility namespace for working with malli schemas."
"Utility namespace for working with malli schemas, including a default registry and predicates used across Fabricate's implementation."
{:reference "https://github.com/metosin/malli"}
(:require [malli.core :as m]
[malli.util :as mu]
Expand All @@ -9,7 +9,7 @@
[babashka.fs :as fs]))


(def registry (atom {}))
(def registry "Global registry for Fabricate's Malli schemas." (atom {}))

(mr/set-default-registry! (mr/composite-registry (m/default-schemas)
(mt/schemas)
Expand All @@ -19,7 +19,7 @@


(defn malli?
"Returns true if the given form is a valid malli schema"
"Returns true if the given form is a valid malli schema."
{:malli/schema (m/schema [:=> [:cat :any] :boolean])}
[form]
(or (m/schema? form)
Expand All @@ -34,13 +34,15 @@
(swap! registry assoc type schema))

(defn file?
"Returns true if the given value is a type that represents a file."
{:malli/schema [:=> [:cat :any] :boolean]}
[v]
(or (instance? java.io.File v) (instance? java.nio.file.Path v)))

(register! :file [:fn file?])

(defn ns?
"Returns true if the given value is a Namespace or is a symbol that names a namespace."
{:malli/schema [:=> [:cat :any] :boolean]}
[v]
(or (instance? clojure.lang.Namespace v) (some? find-ns v)))
Expand Down Expand Up @@ -73,11 +75,11 @@
(m/schema [:schema props new-ref])))

(def regex
"Malli schema for regular expressions"
"Malli schema for regular expressions."
(m/schema [:fn #(instance? java.util.regex.Pattern %)]))

(defn ns-form?
"Returns true if the given form is a valid Clojure (ns ...) special form"
"Returns true if the given form is a valid Clojure (ns ...) special form."
{:malli/schema (m/schema [:=> [:cat :any] :boolean])}
[form]
(let [form
Expand All @@ -92,7 +94,7 @@

(defn unify
"A lighter-weight version of malli's own unify/merge that's
more compatible with number-based indexing/item access"
more compatible with number-based indexing/item access."
{:malli/schema (m/schema [:=> [:cat [:* [:fn malli?]]] [:fn malli?]])}
[schemas]
(m/into-schema :orn {} (map-indexed vector schemas)))
Expand All @@ -106,4 +108,5 @@
[:data {:optional true} :any]]]] [:trace [:vector :any]]]))

(def ^{:malli/schema [:=> [:cat :any] :boolean]} throwable-map?
"Returns true if the given map matches the type returned by Throwable->map."
(m/validator throwable-map-schema))
Loading

0 comments on commit d7a3c4d

Please sign in to comment.