Claude-skill-registry clojure-aero
Aero is an EDN configuration library with reader tag extensions for profiles, environment variables, and references. Use when working with configuration files, environment-specific settings, or when you need explicit, intentful config management in Clojure.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/clojure-aero" ~/.claude/skills/majiayu000-claude-skill-registry-clojure-aero && rm -rf "$T"
skills/data/clojure-aero/SKILL.mdAero
A small library for explicit, intentful configuration using EDN with powerful reader tag extensions.
Setup
deps.edn:
aero/aero {:mvn/version "1.1.6"}
Leiningen:
[aero "1.1.6"]
See https://clojars.org/aero for the latest version.
Quick Start
(require '[aero.core :refer [read-config]]) ;; Create config.edn: ;; {:greeting "World!" ;; :port #profile {:default 8000 ;; :dev 8001 ;; :prod 80}} ;; Read from classpath (recommended) (read-config (clojure.java.io/resource "config.edn")) ;; => {:greeting "World!", :port 8000} ;; Read with profile (read-config (clojure.java.io/resource "config.edn") {:profile :dev}) ;; => {:greeting "World!", :port 8001}
Core Concepts
Aero reads EDN configuration files with special reader tags that allow:
- Profile-based configuration (dev/test/prod)
- Environment variable injection
- References to other config values
- File inclusion for modular configs
- Type coercion (string to long/double/keyword/boolean)
- Conditional logic based on hostname, user, etc.
Always use
io/resource to read from classpath - works in both REPL and JAR files. Direct file paths like (read-config "config.edn") fail in JARs.
Reader Tags
#profile - Environment-specific values
{:webserver {:port #profile {:default 8000 :dev 8001 :test 8002 :prod 80}}} ;; Usage: (read-config "config.edn" {:profile :dev}) ;; => {:webserver {:port 8001}}
#env - Environment variables
{:database-uri #env DATABASE_URI} ;; Reads from (System/getenv "DATABASE_URI")
#envf - Format with environment variables
{:database #envf ["protocol://%s:%s" DATABASE_HOST DATABASE_NAME]} ;; Builds string from multiple env vars
#or - Provide defaults
{:port #or [#env PORT 8080] :debug #boolean #or [#env DEBUG "true"]} ;; First available value wins, uses 8080 if PORT not set
#ref - Reference other config values
{:db-connection "datomic:dynamo://dynamodb" :webserver {:db #ref [:db-connection]} :analytics {:db #ref [:db-connection]}} ;; Both :webserver and :analytics get same db-connection value ;; References use get-in vector paths
#include - Modular configs
{:webserver #include "webserver.edn" :analytics #include "analytics.edn"}
By default resolves relative to parent config. Use custom resolver:
(require '[aero.core :refer [resource-resolver root-resolver]]) ;; Always resolve from classpath (read-config "config.edn" {:resolver resource-resolver}) ;; Or provide a map (read-config "config.edn" {:resolver {"webserver.edn" "resources/webserver/config.edn"}})
#join - String concatenation
{:url #join ["jdbc:postgresql://psq-prod/prod?user=" #env PROD_USER "&password=" #env PROD_PASSWD]}
#merge - Merge maps
{:config #merge [{:foo :bar} {:foo :baz :qux 123}]} ;; => {:config {:foo :baz :qux 123}}
Type coercion tags
{:port #long #or [#env PORT "8080"] ; Parse string to Long :factor #double #env FACTOR ; Parse to Double :mode #keyword #env MODE ; Parse to keyword :debug #boolean #or [#env DEBUG "true"]} ; Parse to boolean
#hostname - Host-specific config
{:webserver {:port #hostname {"stone" 8080 #{"emerald" "diamond"} 8081 :default 8082}}}
#user - User-specific config
Like #hostname but switches on the current user.
Common Patterns
Hide passwords in local files
Don't put secrets in version control or env vars. Use private files:
{:secrets #include #join [#env HOME "/.secrets.edn"] :aws-secret-access-key #profile {:test #ref [:secrets :aws-test-key] :prod #ref [:secrets :aws-prod-key]}}
Wrap config access in functions
(ns myproj.config (:require [aero.core :as aero] [clojure.java.io :as io])) (defn config [profile] (aero/read-config (io/resource "config.edn") {:profile profile})) (defn webserver-port [config] (get-in config [:webserver :port])) ;; Usage in app: (let [cfg (config :prod)] (start-server :port (webserver-port cfg)))
This insulates your code from config structure changes.
Feature toggles
{:features {:new-ui #profile {:default false :dev true :staging true :prod false}}}
Component integration
Pass config to components without boilerplate:
(defn configure [system profile] (let [config (aero/read-config (io/resource "config.edn") {:profile profile})] (merge-with merge system config))) (defn new-system [profile] (-> (new-system-map) (configure profile) (system-using (new-dependency-map))))
Custom Reader Tags
Extend the reader multimethod for custom tags:
(require '[aero.core :refer [reader]]) (defmethod reader 'mytag [{:keys [profile] :as opts} tag value] (if (= value :favorite) :chocolate :vanilla)) ;; In config.edn: ;; {:flavor #mytag :favorite}
Gotchas
-
File paths vs resources: Use
not(io/resource "config.edn")
to avoid JAR deployment failures"config.edn" -
Environment variables for secrets: Don't use #env for passwords - they leak via
and monitoring. Use #include with private files insteadps -
Single config file: Keep one config file when possible - easier to manage and less duplication
-
#or evaluation order: Tags evaluate left to right, first non-nil wins
-
References are recursive: #ref works across #include boundaries
-
Profile is just a key: Can be any keyword - :dev, :prod, :staging, :local, etc.
Advanced: Macro Tag Literals (Alpha)
For custom conditional constructs, use the alpha API:
(ns myns (:require [aero.alpha.core :as aero.alpha])) (defmethod aero.alpha/eval-tagged-literal 'myprofile [tagged-literal opts env ks] (aero.alpha/expand-case (:profile opts) tagged-literal opts env ks))
See README for #or implementation example.
References
- GitHub: https://github.com/juxt/aero
- Clojars: https://clojars.org/aero
- API Docs: https://cljdoc.org/d/aero/aero/
- Community: #juxt channel on Clojurians Slack
- Extensions: https://github.com/monkey-projects/aero-ext