Skip to content

Instantly share code, notes, and snippets.

@kurtharriger
Created December 29, 2021 01:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kurtharriger/3097bdde3f50cc83645fa16b8e848f49 to your computer and use it in GitHub Desktop.
Save kurtharriger/3097bdde3f50cc83645fa16b8e848f49 to your computer and use it in GitHub Desktop.
dime app helper
(ns user
(:require
[app.server :as server]
[dime.core :as di]
[dime.var :as dv :refer [defconst]]))
(def app-namespaces
['app.ynab
'app.budget
'app.parser
'app.server])
;; todo: maybe services can specify a stop method on
;; metadata or implement a lifecycle protocol
(defn stop [app]
(when-let [server (:app/server app)]
((:stop server))))
(def ^:dynamic *quiet* false)
(defn notify-starting []
(when-not *quiet* (prn "Starting...")))
(defn notify-started []
(when-not *quiet* (prn "Started.")))
(defn notify-stopping []
(when-not *quiet* (prn "Stopping.")))
(defn notify-stopped []
(when-not *quiet* (prn "Stopped.")))
(defn start* []
(notify-starting)
(let [graph (dv/ns-vars->graph app-namespaces)
app (with-meta (di/inject-all graph {}) {:graph graph})]
(notify-started)
app))
(defn stop* [app]
(when-let [server (:app.server/server app)]
(notify-stopping)
((:stop server))
(notify-stopped)
nil))
(defonce app-state (atom nil))
(defn start! []
(swap! app-state #(or % (start*))))
(defn stop! []
(swap! app-state stop*))
(defn restart! []
(stop!)
(start!)
nil)
(defprotocol AppAction
(run [this app args]))
(extend-protocol AppAction
clojure.lang.IFn
(run [this app args] (apply this (cons app args))))
(defn uget [map key]
(let [value (get map key ::not-found)]
(if (and (= value ::not-found) (not (qualified-keyword? key)))
(if-let [matches (get (group-by name (keys map)) (name key) ::not-found)]
(if (= (count matches) 1)
(get map (first matches) ::not-found)
::ambiguous))
value)))
(extend-protocol AppAction
clojure.lang.Keyword
(run [this app args]
(when-let [value (uget app this)]
(if (fn? value)
(apply value args)
value))))
(defn app
"Starts application if it is not currenlty running.
If no action is specified starts app and returns nil
(to prevent printing lots of data to repl)
If an action is specified then it will be run on the
started application.
action can be a function such as:
(app keys) to list exposed objects
(app identity) to return application map (since no action returns nil)
(app meta) to return metadata and access the graph
If the action is a keyword then it will lookup the value from the app
and if that value is a function it will invoke it with the specified args
(app :app.ynab/get-budgets)
(app :app.ynab/get-budgets-by-id budget-id)
or if you are already in the a app.ynab namespace you can just do (user/app ::get-budget-by-id ...)
which is almost as easy as calling the function directly (and possibly much easier when you need to have the dependencies injected)
If the keyword is unquafied it will attempt to find a qualified keyword with same name and use that as long as there is only one match
If you want to retrieve the partial function without invoking it then apply a function such as get or uget for search using unqualified keyword
(app get key) or (app uget key)"
[& [action & args]]
;; todo: maybe also create an AppAtomAction
;; that could be applied to the atom
;; so app becomes the facade for everthing
;; (app start) instead of (user/start!)
;; or maybe something like (app *ns*)
;; to reload and restart services in that
;; namespace...
(when-let [app (swap! app-state #(or % (start*)))]
(when action
(run action app args))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment