Templar is a dependency inversion library that uses templates (groups of function names and descriptions) to
dynamically resolve/load functions.
Setup of template function dispatcher
namespace.
(ns templar.template.foobar.dispatcher
(:require [templar.core :as templar]))
(def template-id :foobars)
(def template-fs [{:fn "foo"
:description "This is the foo function, meh."}
{:fn "bar"
:args [{:name "x"
:type :map
:description "The x of the bar call."}
{:name "y"
:type :string
:description "This y of the bar call."}
{:name "z"
:optional? true
:type :long
:description "This is the optional z for the bar call"}]
:description "This is the bar function, blah."}
{:fn "ans"
:args []
:description "Answer, without the question. Bring a towel."}])
(def default-template-ns "templar.template.foobar.shout")
(defn namespace!
"Register the namespace `ns` to :foobars template with optional metadata map `meta`."
([ns] (namespace! ns nil))
([ns meta]
(templar/register-namespace! ns template-id meta)))
(defn init
"Initialize example foobar dispatcher."
[]
(templar/register! template-id template-fs) ; register template with templar
(when-let [check (namespace! default-template-ns {:description "default foobars ns"})]
(println "WARNING: " default-template-ns ":: " check)))
(defn dispatch
"Dispatch template function call `fn` for :foobars template with optional
function arguments `args`."
[fn & args]
(apply templar/apply-template-function (concat [template-id fn] args)))
(def foo (partial dispatch :foo))
(def bar (partial dispatch :bar))
(def ans (partial dispatch :ans))
(init)
Setting up two implementations, shout
and whisper
.
shout
(ns templar.template.foobar.shout
(:require [clojure.string :as str]))
(defn foo [& args] (let [x (str/upper-case (str "foo " args))]
(println x)
x))
(defn bar [& args] (let [x (str/upper-case (str "bar " args))]
(println x)
x))
(defn ans [& args] (let [x "ANSWER IS 42!"]
(println x)
x))
whisper
(ns templar.template.foobar.whisper
(:require [clojure.string :as str]))
(defn foo [& args] (let [x (str/lower-case (str "pssst. foo " args))]
(println x)
x))
(defn bar [& args] (let [x (str/lower-case (str "pssssst. bar " args))]
(println x)
x))
(defn ans [& args] (let [x "psst. the answer is 42."]
(println x)
x))
And running the example
function run
:
(ns templar.example
(:require [templar.template.foobar.dispatcher :as foobar]))
(defn run
[]
(foobar/namespace! :templar.template.foobar.shout)
(println "SHOUTING!!!")
(foobar/foo 1 2 3 4)
(foobar/bar 3 4 2 3 :a :b)
(foobar/ans)
(foobar/namespace! :templar.template.foobar.whisper)
(println "whispering...")
(foobar/foo 1 2 3 4)
(foobar/bar 3 4 2 3 :a :b)
(foobar/ans))
Results in the output:
(run)
;=>
SHOUTING!!!
FOO (1 2 3 4)
BAR (3 4 2 3 :A :B)
ANSWER IS 42!
whispering...
pssst. foo (1 2 3 4)
pssssst. bar (3 4 2 3 :a :b)
psst. the answer is 42.
This example implementation above is just one of many ways to leverage templar
. This approach is minimal on the dispatcher
by leveragingpartial
to quickly stand up an implementation of the template, but at the cost of arity checking and more fleshed out
function signatures and documentation.
However, more granular implementation may have the foo
and bar
implemented as actual functions with arity-controlled
arguments. This would allow IDEs and easy introspection to determine what the allowed arities are for each template
function.
For example, instead of:
(def foo (partial dispatch :foo))
you can the following if foo
expects two (2) arguments:
(defn foo
"This is the docstring for foo that developers might use since the implemented `foo` might be in libary jar somewhere."
[x y]
(apply dispatch [:foo x y]))
You can determine if a namespace is template compliant by calling the (compliant ns template-id)
.
If the call returns nil
, then the namespace is compliant.
(compliant :templar.template.foobar.shout :foobars)
;=>
nil
If the namespace is NOT compliant, then the call will return a vector of failed compliants (missing functions).
(ns templar.template.foobar.dumb
(:require [clojure.string :as str]))
(defn foo [& args] (println (str/lower-case (str "duh. foo " args))))
(defn bar [& args] (println (str/lower-case (str "duh. bar " args))))
;;; note lack of template :foobars function `ans`
and running compliant
on the dumb
namespace:
(templar/compliant :templar.template.foobar.dumb :foobars)
;=>
[{:message "Missing function {:fn :ans}."
:fn :ans}]
Templates are registered using the templar.core/register!
function.
(ns templar.template.foobar.dispatcher
(:require [templar.core :as templar]))
(def template-id :foobars)
(def template-fs [{:fn :foo
:description "This is the foo function, meh."}
{:fn :bar
:args [{:name "x"
:type :map
:description "The x of the bar call."}
{:name "y"
:type :string
:description "This y of the bar call."}
{:name "z"
:optional? true
:type :long
:description "This is the optional z for the bar call"}]
:description "This is the bar function, blah."}
{:fn :ans
:args []
:description "Answer, without the question. Bring a towel."}])
(templar/register! template-id template-fs)
The code registers the template :foobars
with three (3) functions: foo
, bar
, and ans
.
The only required key in the template function maps is :fn
which may be a keyword or string.
Templar also needs to know the namespace to use to resolve the functions in the template.
(def default-template-ns :templar.template.foobar.shout)
(defn namespace!
[ns]
(templar/register-namespace! ns template-id))
(when-let [check (namespace! default-template-ns)]
(println "WARNING: " check))
The namespace MUST be compliant with the template to be associate/register to that template.
The templar/register-namespace!
function returns nil
if the associate/registration of the namespace was successful.
The templar/register-namespace!
function returns the same response as the compliant
function if the namespace is
NOT compliant to the template. If the namespace is not compliant, the namespace is NOT associated with the template and
the pre-existing namespace (if any) associated/registered to the template remains active.
The state of the templates may be pulled using the templar/state
function:
(templar/state)
;=>
{:foobars {:id :foobars,
:t [{:fn :foo, :description "This is the foo function, meh."}
{:fn :bar,
:args [{:name "x", :type :map, :description "The x of the bar call."}
{:name "y", :type :string, :description "This y of the bar call."}
{:name "z", :optional? true, :type :long, :description "This is the optional z for the bar call"}],
:description "This is the bar function, blah."}
{:fn :ans, :args [], :description "Answer, without the question. Bring a towel."}],
:ns {:id :foobars, :ns :templar.template.foobar.shout, :m {:description "default foobars ns"}}}}
Individual template state may be accessed via the templar/state-of
function:
(templar/state-of :foobars)
;=>
{:id :foobars,
:t [{:fn :foo, :description "This is the foo function, meh."}
{:fn :bar,
:args [{:name "x", :type :map, :description "The x of the bar call."}
{:name "y", :type :string, :description "This y of the bar call."}
{:name "z", :optional? true, :type :long, :description "This is the optional z for the bar call"}],
:description "This is the bar function, blah."}
{:fn :ans, :args [], :description "Answer, without the question. Bring a towel."}],
:ns {:id :foobars, :ns :templar.template.foobar.shout, :m {:description "default foobars ns"}}}
The registered namespaces associated with templates may be access via the templar/registered-namespace
function:
(templar/registered-namespace :foobars)
;=>
{:id :foobars, :ns :templar.template.foobar.shout, :m {:description "default foobars ns"}}
To pull just the namespace of a template:
(templar/namespace-of :foobars)
;=>
:templar.template.foobar.shout
Copyright © 2019 SierraLogic LLC
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.