(ns net.deertopia.doerg.config (:require [clojure.spec.alpha :as s] [babashka.fs :as fs] [aero.core :as aero] [clojure.java.io :as io])) (s/def ::config (s/keys :req [::ibm-plex-web ::latex ::dvisvgm ::doerg-temml-worker ::doerg-parser ::state-directory ::org-roam-db-path])) (s/def ::directory (s/conformer #(fs/file %))) (s/def ::file (s/conformer #(-> % fs/expand-home fs/file))) (s/def ::executable (s/conformer ;; I'd love to use `fs/which` here, but it's fairly problematic to ;; check `fs/executable?` at… build time (which `fs/which` does)? ;; Wait… what? Do I know how Clojure compilation works? #(or #_(some-> % fs/expand-home fs/which fs/file) (some-> % fs/expand-home fs/file) ::s/invalid))) (s/def ::ibm-plex-web ::directory) (s/def ::latex ::executable) (s/def ::dvisvgm ::executable) (s/def ::doerg-temml-worker ::executable) (s/def ::doerg-parser ::executable) (s/def ::state-directory ::file) (s/def ::org-roam-db-path ::file) (defmethod aero/reader 'xdg-data-dir [_opts tag value] "Aero tag to search for a directory on $XDG_DATA_DIRS." (some #(let [x (fs/path % value)] (and (fs/exists? x) x)) (fs/split-paths (System/getenv "XDG_DATA_DIRS")))) (defmethod aero/reader 'file [{:keys [source]} tag value] "Aero tag to reference a `java.io.File` relative to the config file." (-> (aero/relative-resolver source value) fs/file)) (defn read-config [files & {:as opts}] (let [r (->> files (filter identity) (map #(aero/read-config % opts)) (apply merge)) conformed (s/conform ::config r)] (if-not (s/invalid? conformed) conformed (throw (ex-info "Failed to conform config" (s/explain-data ::config r)))))) (defn load-config! [var spec files & {:as opts}] (alter-var-root var (constantly (read-config files opts)))) (def sources [;; Default config. (io/resource "net/deertopia/doerg/default-config.edn") ;; Defaults set at build time, if any. (io/resource "net/deertopia/doerg/extra-config.edn") ;; Config set at runtime. (System/getenv "DOERG_CONFIG")]) (def default (read-config sources)) (def ^:dynamic *cfg* default)