{"id":17005852,"url":"https://github.com/otann/wrench","last_synced_at":"2025-03-17T09:31:01.897Z","repository":{"id":62434676,"uuid":"115234354","full_name":"Otann/wrench","owner":"Otann","description":"🔧 Elegant configuration for a more civilized age","archived":false,"fork":false,"pushed_at":"2023-06-24T18:23:49.000Z","size":95,"stargazers_count":46,"open_issues_count":1,"forks_count":4,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-02-27T22:10:45.739Z","etag":null,"topics":["12-factor","12factor","clojure","config","configuration","environment-variables"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Otann.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-12-24T02:20:13.000Z","updated_at":"2024-05-31T07:54:23.000Z","dependencies_parsed_at":"2023-09-28T04:08:32.526Z","dependency_job_id":null,"html_url":"https://github.com/Otann/wrench","commit_stats":{"total_commits":61,"total_committers":4,"mean_commits":15.25,"dds":0.06557377049180324,"last_synced_commit":"79d2c99f07c9da3bb8188e0a05e132e2b895b1af"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Otann%2Fwrench","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Otann%2Fwrench/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Otann%2Fwrench/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Otann%2Fwrench/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Otann","download_url":"https://codeload.github.com/Otann/wrench/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243858929,"owners_count":20359260,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["12-factor","12factor","clojure","config","configuration","environment-variables"],"created_at":"2024-10-14T05:04:17.832Z","updated_at":"2025-03-17T09:31:01.565Z","avatar_url":"https://github.com/Otann.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Wrench\n\n[![Circle CI](https://circleci.com/gh/Otann/wrench.svg?style=shield\u0026no-cache=0)](https://circleci.com/gh/Otann/wrench)\n[![Clojars](https://img.shields.io/clojars/v/wrench.svg?no-cache=1)](https://clojars.org/wrench)\n[![codecov](https://codecov.io/gh/Otann/wrench/branch/master/graph/badge.svg)](https://codecov.io/gh/Otann/wrench)\n\n\u003cimg width=\"25%\"\n     max-height=\"100px\"\n     align=\"right\" padding=\"5px\"\n     alt=\":)\"\n     src=\"/wrench.png\"/\u003e\n\nWrench is a library to manage your clojure app's configuration.\nIt is designed with specific goals in mind:\n\n- **All values are available during initialization of your code**\n- That means you can use it in your `def`s (and def-like macros, like `defroutes`)  \n- All values come from environment variables, as [12 factors manifesto](https://12factor.net/config) recommends\n- Each configuration could be accompanied with a custom spec\n- One can ensure that whole config matches provided specs during runtime\n- Configuration values are coerced to their spec from string and edn (enables values like `[8080 8888]`)\n- Definition and usage of each key are easily traceable, since they are simple vars\n- For local development extra values could be provided in REPL and from local EDN file\n\n## Installation\n\nAdd `[wrench \"0.3.3\"]` to the dependency section in your project.clj file.\n\nWrench requires 1.9 version of Clojure.\n\n## Usage\n\nSimplest way to use it is to define a config with a simple `def`. \nFor instance, if you want to read environment variable `USER` you would do following:  \n\n```clojure\n(require '[wrench.core :as cfg])\n(cfg/def user)\n```\n\nYou can also customize name of the variable and provide specification:\n\n```clojure\n(cfg/def port {:name \"HTTP_PORT\", :spec int?})\n```\n\nIn this case loaded value would be coerced to an int.\n\nFollowing specs are coerced to corresponding values: `string?`, `int?`, `double?`, `boolean?`, `keyword?`.\nEVerything else is coerced to edn.\n\nThere are plenty of other options:\n\n- `:doc` will be symbol's documentation\n- `:spec` spec-compatible (including any predicate) to validate the value, defaults to `string?`\n- `:name` name of the environment variable, defaults to uppercased name of the var (ignoring namespace) with dashes replaced with underscores\n- `:require` fails validation, if value is missing, default is `false`\n- `:default` to provide a fallback value if it is missing, default is nil\n- `:secret` to hide value from `*out*` during validation, default is `false`\n\n```clojure\n(cfg/def oauth-secret {:doc    \"OAuth secret to validate token\"\n                       :require true\n                       :secret  true})\n\n(cfg/def host {:doc \"Remote host for a dependency service\"\n               :name \"SERVICE_NAME_HOST\"\n               :require true})\n```\n\nThen use those vars as you would use any other constant, i.e.: \n\n```clojure\n(cfg/def port {:name \"NREPL_PORT\"\n               :spec int?\n               :default 7888})\n\n(mount/defstate nrepl-server\n  :start (nrepl-server/start-server :port port)\n  :stop (nrepl-server/stop-server nrepl-server))\n```\n\nIf a value does not pass validation, `::cfg/invalid` will be used.\n\nTo ensure you have everything configured properly, validate your config before app starts:\n\n```clojure\n(defn -main [\u0026 args]\n  (println \"Starting service!\")\n  (if-not (cfg/validate-and-print)\n    (System/exit 1)))\n```\n\nIf everything is alright, then configuration will be printed to `*out*`,\nreplacing values marked as `:secret` with `\u003cSECRET\u003e`:\n \n```\nLoaded config:\n-  #'some.service/port 8080\n-  #'some.service/token \u003cSECRET\u003e\n``` \n \nIf there were errors during validation\nor required keys are missing, then aggregated summary will be printed and `false` returned:\n\n```\nFailed to load config:\n- configuration #'some.service/token is required and is missing\n- configuration #'some.service/port present, but does not conform spec: something-wrong\n```\n\n## Testing\n\nIdiomatic `with-redefs` could be used to alter var's value:\n\n```clojure\n;; service.clj\n(ns some.service\n  (:require [wrench.core :as cfg]))\n  \n(cfg/def user {:default \"Rich\"}) \n\n;; service-test.clj\n(ns some.service-test\n  (:require [clojure.test :refer :all]\n            [some.service :as svc]))\n \n(deftest a-test\n  (testing \"Let's talk about javascript\"\n    (with-redefs [svc/user \"David\"]\n      (is (= svc/user \"David\")))))\n```\n\nYou can also permanently alter config, by providing alternative values to vars:\n\n```clojure\n(cfg/reset! :var {#'svc/user \"David\"})\n```\n\nor environment variables\n\n```clojure\n(cfg/reset! :env {\"PORTS\" \"[8080 8081 8082]\"})\n; or load from file\n(cfg/reset! :env (cfg/from-file \"dev-config.edn\"))\n;; or in combination\n(cfg/reset! :env (from-file \"dev-config.edn\")\n            :var {#'ports [8080 8081]})\n```\n\nThose changes will be applied to global scope, your experience may vary depending on your test runner.\n\n## REPL and reloaded workflow\n\nIf during REPL development you ever need whole configuration map, it is available using:\n\n```clojure\n(cfg/config)\n```\n\nNote, that wrench relies on default reloading mechanics, maning that changes in environment variables\nor in external config file would not trigger reloading variables with \n`(clojure.tools.namespace.repl/refresh)`.\n\nTo mitigate this you could either use `refresh-all` or reload config manually before your system starts:\n\n```clojure\n(defn reset \"reloads modified source files, and restarts the states\" []\n  (stop)\n  (cfg/reset! :env (cfg/from-file \"dev-config.edn\"))\n  (ns-tools/refresh :after 'user/go))\n```\n\n## License\n\nCopyright © 2018 Anton Chebotaev\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fotann%2Fwrench","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fotann%2Fwrench","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fotann%2Fwrench/lists"}