info.voidstar/tbnl.core

0.1.1-SNAPSHOT


the infrastructure shared by mastermind and figurehead

dependencies

org.clojure/clojure
1.6.0
org.clojure/tools.nrepl
0.2.3
org.clojure/core.async
0.1.303.0-886421-alpha
org.clojure/tools.cli
0.3.1
cider/cider-nrepl
0.7.0
compliment
0.1.3



(this space intentionally left almost blank)
 

the common message bus

(ns core.bus
  (:require (core [state :as state]))
  (:require [clojure.core.async
             :as async
             :refer [chan
                     buffer sliding-buffer
                     pub sub unsub
                     mult tap untap
                     >!! <!!]]))
(declare sub-topic unsub-topic get-subscribers
         register-listener unregister-listener get-listeners
         get-message-topic remove-message-topic build-message
         get-topics
         say say!! well-saying?
         what-is-said what-is-said!!)

the defaults

(def defaults
  (atom
   {
    :bus-main-buffer-size 10000
    :bus-pub-buffer-size 10000
    :pub-buf-fn (fn [topic]
                  (buffer (:bus-pub-buffer-size
                           @defaults)))
    :get-message-topic (fn [message]
                         (cond
                          (map? message) (:topic message)
                          (sequential? message) (first message)
                          :else message))
    :remove-message-topic (fn [message]
                            (cond
                             (map? message) (:content message)
                             (sequential? message) (rest message)
                             :else nil))
    :build-message (fn [topic content]
                     {:topic topic
                      :content content})
    :well-saying? (fn [said]
                    (and said
                         (map? said)
                         (:topic said)
                         (:content said)))}))

subscribe the ch(an) to the topic on bus-pub, with close? as in clojure.core.async/subunsubscribe the ch(an) from the topic on bus-pubget subscribersregister as a listener of all messages over bus-chanundo register-listenerget listenersget all said topicsget the topic of the messageremove the topic from the messagebuild message from topic and contentsay content with the topicsay content with the topic (>!! as chan-op)Is said a well saying?get what is from the sub-chget what is said on sub-ch (

(let [bus-chan (chan (:bus-main-buffer-size @defaults))
      bus-chan-mult (mult bus-chan)
      bus-chan-1 (chan)
      _1 (tap bus-chan-mult bus-chan-1)
      bus-pub (pub bus-chan-1
                   (:get-message-topic @defaults)
                   (:pub-buf-fn @defaults))
      bus-chan-2 (chan)
      _2 (tap bus-chan-mult bus-chan-2)
      listener-pub (pub bus-chan-2
                        (constantly true)
                        (:pub-buf-fn @defaults))
      topics (atom #{})
      subscribers (atom {})
      listeners (atom #{})]
  (defn sub-topic
    ([ch topic] (sub-topic ch topic true))
    ([ch topic close?]
       (swap! subscribers update-in [topic]
              (comp set conj) ch)
       (sub bus-pub topic ch close?)))
  (defn unsub-topic
    [ch topic]
    (swap! subscribers update-in [topic]
           disj ch)
    (unsub bus-pub topic ch))
  (defn get-subscribers
    []
    @subscribers)
  (defn register-listener
    ([ch] (register-listener ch true))
    ([ch close?]
       (swap! listeners
              (comp set conj) ch)
       (sub listener-pub true ch close?)))
  (defn unregister-listener
    [ch]
    (swap! listeners
           disj ch)
    (unsub listener-pub true ch))
  (defn get-listeners
    []
    @listeners)
  (defn get-topics
    []
    @topics)
  (defn get-message-topic
    [message]
    ((:get-message-topic @defaults) message))
  (defn remove-message-topic
    [message]
    ((:remove-message-topic @defaults) message))
  (defn build-message
    [topic content]
    ((:build-message @defaults) topic content))
  (defn say
    ([topic content chan-op] (say topic content false chan-op))
    ([topic content verbose? chan-op]
       (swap! topics conj topic)
       (let [message (build-message topic content)]
         (chan-op bus-chan message)
         (when verbose?
           (prn {:message message
                 :topics (get-topics)
                 :subscribers (get-subscribers)
                 :listeners (get-listeners)})))))
  ;; say! is omitted because >! may not work with Clojure on Android for SDK 18
  ;; !!! use say! within go block may deadlock
  ;; (defn say!
  ;;   "say content with the topic (>! as chan-op)"
  ;;   ([topic content] (say! topic content false))    
  ;;   ([topic content verbose?]
  ;;      (let [chan-op (fn [ch val]
  ;;                      (go >! ch val))]
  ;;        (say topic content verbose? chan-op))))
  (defn say!!
    ([topic content] (say!! topic content false))
    ([topic content verbose?]
       (let [chan-op (fn [ch val]
                       (>!! ch val))]
         (say topic content verbose? chan-op))))
  (defn well-saying?
    [said]
    ((:well-saying? defaults) said))
  (defn what-is-said
    ([sub-ch chan-op] (what-is-said sub-ch false chan-op))
    ([sub-ch verbose? chan-op]
       (let [said (chan-op sub-ch)]
         (when verbose?
           (prn [:said said]))
         (remove-message-topic said))))
  (defn what-is-said!!
    ([sub-ch] (what-is-said!! sub-ch false))
    ([sub-ch verbose?]
       (let [chan-op (fn [ch]
                       (<!! ch))]
         (what-is-said sub-ch verbose? chan-op)))))
 

initialization procedures

(ns core.init
  (:require
   (core [state :as state]
         [plugin :as plugin])))
(declare add-to-parse-opts-vector get-parse-opts-vector
         parse-opts-vector-helper
         set-default-plugins)

the vector to feed into clojure.tools.cli/parse-opts

(def ^:private parse-opts-vector
  (atom [
         ["-h" "--help" "show help"]         
         ["-v" "--verbose" "be verbose and show debug info"]
         ["-B" "--batch" "batch mode: no block after loading plugins"]
         ]))

lines are added into the parse-opts vector

(defn add-to-parse-opts-vector
  [lines]
  (swap! parse-opts-vector into lines))

return the parse-opts vector

(defn get-parse-opts-vector
  []
  @parse-opts-vector)

help parse the parse-opts vector

(def parse-opts-vector-helper
  (atom {
         :parse-fn
         {
          :inet-port (comp #(when (and (> % 0)
                                       (< % 65536))
                              %)
                           #(Integer/parseInt %))
          :file #(clojure.java.io/file %)
          }
         }))
(defn set-default-plugins
  [& plugins]
  (swap! parse-opts-vector
         conj
         (let [option :plugin]
           ["-P"
            (str "--"
                 (name option)
                 " [PLUGIN]")
            "[m] plugin to load"
            :default plugins
            :parse-fn symbol
            :assoc-fn (fn [m k v]
                        (update-in m [k]
                                   (comp vec conj) v))])))

set default plugin namespaces to be required/loaded

(defmacro require-and-set-default-plugins
  [& plugins]
  ;; compile-time require will put the plugin on classpath
  (doseq [plugin plugins]
    (require (plugin/get-plugin-main-entry plugin)))
  `(set-default-plugins ~@(map (fn [plugin] `'~plugin) plugins)))
 

main procedures

(ns core.main
  (:require (core [init :as init]
                  [plugin :as plugin]
                  [state :as state]))
  (:require [clojure.tools.cli :refer [parse-opts]]
            [clojure.stacktrace :refer [print-stack-trace]]
            [clojure.core.async :as async :refer [chan <!!]])
  (import (java.util UUID)))

the main entry

(defn main
  [& args]
  (try
    (state/add-state :instance-id
                     (-> (UUID/randomUUID) str keyword))
    ;; load the plugins and populate the parse-opts vector
    (let [{:keys [options arguments errors summary]}
          (parse-opts args (init/get-parse-opts-vector))
          verbose (:verbose options)]
      (let [plugins (:plugin options)]
        (when verbose
          (prn (list :load-plugins
                     plugins)))
        (doseq [plugin plugins]
          (plugin/load-plugin plugin))
        (doseq [plugin plugins]
          (when verbose
            (prn (list :populate-parse-opts-vector
                       plugin
                       (init/get-parse-opts-vector))))
          (plugin/populate-parse-opts-vector plugin
                                             (init/get-parse-opts-vector)))))
    ;; parse-opts again; initilize and run the plugins
    (let [{:keys [options arguments errors summary]}
          (parse-opts args (init/get-parse-opts-vector))
          verbose (:verbose options)]
      (reset! plugin/current-options options)
      (cond
       ;; ask for help
       (:help options)
       (do
         (println summary))
       :main
       (let [plugins (plugin/list-all-plugins-by-priority)]
         (doseq [plugin plugins]
           (plugin/init-and-run-plugin plugin options))
         (when-not (:batch options)
           ;; block the main Thread 
           (when verbose
             (prn {:batch (:batch options)}))
           (<!! (chan))))))
    (catch Throwable e
      (print-stack-trace e))
    (finally
      (plugin/execute-all-exit-hooks)
      ;; http://clojuredocs.org/clojure_core/clojure.java.shell/sh#example_896
      (shutdown-agents))))
 

the plugin infrastructure

(ns core.plugin
  (:require (core [bus :as bus]))
  (:require [clojure.stacktrace :refer [print-stack-trace]]
            [clojure.core.async
             :as async
             :refer [thread <!! chan timeout]]))
(declare list-all-plugins list-all-plugins-by-priority
         get-plugin get-plugin-main-entry
         get-config-map get-config-map-entry update-config-map-entry set-config-map-entry
         get-param get-param-entry update-param-entry set-param-entry
         get-state get-state-entry update-state-entry set-state-entry
         load-plugin unload-plugin
         populate-parse-opts-vector
         init-plugin run-plugin stop-plugin init-and-run-plugin load-init-and-run-plugin restart-plugin
         block-thread unblock-thread
         register-exit-hook unregister-exit-hook execute-all-exit-hooks)
(def ^:dynamic *current-plugin*
  "bound to current plugin by the context")

the defaults

(def defaults
  (atom {
         :auto-restart-retry-interval 1000
         }))

all loaded plugins and their config map

(def plugins
  (atom {}))

current options

(def current-options
  (atom nil))

exit hooks that are executed on main-thread exit

(def exit-hooks
  (atom {}))

list all plugins

(defn list-all-plugins
  []
  (keys @plugins))

list all plugins from highest (larger number) to lowest priority

the convention is that 'I do not care'-priority is 1, and 'absolute first'-priority is 99; 0 and 100 are reserved for core

(defn list-all-plugins-by-priority
  []
  (->> @plugins
       (sort-by #(or (get-in (second %)
                             [:param :priority])
                     ;; assign low priority if missing :priority spec
                     0))
       (map first)
       reverse))

list all non-stop plugins from highest to lowest priority

(defn list-all-nonstop-plugins-by-priority
  []
  (filter #(not (get-state-entry % :stop))
          (list-all-plugins-by-priority)))

get the named plugin

(defn get-plugin
  ([] (get-plugin *current-plugin*))
  ([plugin]
     (keyword plugin)))

get the main entry to the plugin

(defn get-plugin-main-entry
  ([] (get-plugin-main-entry *current-plugin*))
  ([plugin]
     (symbol (str plugin ".main"))))

config-map

get the config map

(defn get-config-map 
  ([] (get-config-map *current-plugin*))
  ([plugin]
     (get @plugins (get-plugin plugin))))

get a config entry

(defn get-config-map-entry 
  ([key] (get-config-map-entry *current-plugin* key))
  ([plugin key]
     (get (get-config-map plugin) key)))

update a config entry to (f & args)

(defn update-config-map-entry
  ([plugin key f & args]
     (apply swap! plugins update-in 
            [(get-plugin plugin) key]
            f args)))

set a config entry to val

(defn set-config-map-entry
  ([key val] (set-config-map-entry *current-plugin* key val))
  ([plugin key val]
     (update-config-map-entry plugin key (constantly val))))

get the params

config-map/param

(defn get-param
  ([] (get-param *current-plugin*))
  ([plugin]
     (get-config-map-entry plugin :param)))

get a param entry

(defn get-param-entry
  ([] (get-param-entry *current-plugin*))
  ([plugin key]
     (get (get-param plugin) key)))

update a param entry to (f & args)

(defn update-param-entry
  ([plugin key f & args]
     (apply swap! plugins update-in
            [(get-plugin plugin) :param key]
            f args)))

set a parameter entry to val

(defn set-param-entry
  ([key val] (set-param-entry *current-plugin* key val))
  ([plugin key val]
     (update-param-entry plugin key (constantly val))))

get the states

config-map/state

(defn get-state
  ([] (get-state *current-plugin*))
  ([plugin]
     (get-config-map-entry plugin :state)))

get a state entry

(defn get-state-entry
  ([key] (get-state-entry *current-plugin* key))
  ([plugin key]
     (get (get-state plugin) key)))

update a state entry to (f & args)

(defn update-state-entry
  ([plugin key f & args]
     (apply swap! plugins update-in
            [(get-plugin plugin) :state key]
            f args)))

set a state entry to val

(defn set-state-entry
  ([key val] (set-state-entry *current-plugin* key val))
  ([plugin key val]
     (update-state-entry plugin key (constantly val))))

load/unload plugin

load a plugin

(defn load-plugin
  [plugin]
  (binding [*current-plugin* plugin]
    (let [plugin-main-entry (get-plugin-main-entry plugin)]
      (require plugin-main-entry)
      (swap! plugins
             assoc
             (get-plugin plugin)
             @(ns-resolve plugin-main-entry
                          'config-map)))))

unload the plugins

(defn unload-plugin
  [plugin]
  (binding [*current-plugin* plugin]
    (when-let [unload (get-config-map-entry plugin :unload)]
      (unload))
    (swap! plugins
           dissoc
           (get-plugin plugin))))

populate parse-opts vector for the plugin

populate parse-opts vector

(defn populate-parse-opts-vector 
  [plugin current-parse-opts-vector]
  (binding [*current-plugin* plugin]
    (when-let [populate-parse-opts-vector (get-config-map-entry plugin :populate-parse-opts-vector)]
      (populate-parse-opts-vector current-parse-opts-vector))))

initialize the plugin with the options; return false to abort running the plugin

init/run plugin

(defn init-plugin 
  ([plugin] (init-plugin plugin @current-options))
  ([plugin options]
     (binding [*current-plugin* plugin]
       (let [verbose (:verbose options)]
         (when verbose
           (prn [:init-plugin plugin options]))
         (let [result (if-let [init (get-config-map-entry plugin :init)]
                        (init options)
                        true)]
           (when verbose
             (prn [:init-plugin plugin :result result]))
           result)))))

run the plugin with the options in a separte thread

(defn run-plugin 
  ([plugin] (run-plugin plugin @current-options))
  ([plugin options]
     (binding [*current-plugin* plugin]
       (let [verbose (:verbose options)]
         (when-let [run (get-config-map-entry plugin :run)]
           (when verbose
             (prn [:run-plugin plugin options]))
           (set-state-entry plugin :stop false)
           (if (get-param-entry plugin :sync)
             (when-not (get-state-entry plugin :stop)
               (try
                 (run options)
                 (catch Exception e
                   (when verbose
                     (print-stack-trace e)))))
             (thread
               ;; only :async plugin can auto-restart
               (loop []
                 (try
                   (run options)
                   (catch Exception e
                     (when verbose
                       (print-stack-trace e))))
                 (when (and
                        ;; auto-restart has been requested and...
                        (get-param-entry plugin :auto-restart)
                        ;; plugin has NOT been explicitly stopped
                        (not (get-state-entry plugin :stop)))
                   (let [auto-restart-retry-interval (:auto-restart-retry-interval @defaults)]
                     (when verbose
                       (prn [:plugin plugin
                             :auto-restart
                             :retry-interval auto-restart-retry-interval]))
                     (Thread/sleep auto-restart-retry-interval)
                     (recur)))))))))))

stop the plugin

(defn stop-plugin
  ([plugin] (stop-plugin plugin @current-options))
  ([plugin options]
     (let [verbose (:verbose options)
           stop (get-config-map-entry plugin :stop)]
       (when stop
         (when verbose
           (prn [:stop-plugin plugin options]))
         (stop options)))))

init the plugin and, if successful, run it

(defn init-and-run-plugin
  ([plugin] (init-and-run-plugin plugin @current-options))
  ([plugin options]
     (let [verbose (:verbose options)]
       (when (init-plugin plugin options)
         (run-plugin plugin options)
         (when-let [wait (get-param-entry plugin :wait)]
           (when verbose
             (prn [:wait wait]))
           (<!! (timeout wait)))))))

load-plugin + init-and-run-plugin; mainly for dynamic loading

(defn load-init-and-run-plugin
  [plugin options]
  (let [verbose (:verbose options)]
    (load-plugin plugin)
    (init-and-run-plugin plugin options)))

stop-plugin + init-and-run-plugin

(defn restart-plugin
  ([plugin] (restart-plugin plugin @current-options))
  ([plugin options]
     (stop-plugin plugin options)
     (init-and-run-plugin plugin options)))

block the plugin thread so will not keep restarting the plugin; optional with timeout and topic (subscribed to :unblock-thread

(defn block-thread
  ([] (block-thread nil nil))
  ([timeout-or-tag]
     (cond
      (number? timeout-or-tag)
      (block-thread timeout-or-tag nil)
      :else
      (block-thread nil timeout-or-tag)))
  ([timeout tag]
     (let [ch (if (number? timeout)
                (async/timeout timeout)
                (chan))]
       (try
         (when tag
           (bus/sub-topic ch :unblock-thread))
         (loop [said (bus/what-is-said!! ch)]
           (when (and said tag (not= tag said))
             ;; only recur when <!! return from :unblock-thread (and val tag) and not having a matching tag
             (recur (bus/what-is-said!! ch))))
         (finally
           (when tag
             (bus/unsub-topic ch :nnblock-thread)))))))

unblock thread with the given unblock-tag

(defn unblock-thread
  [unblock-tag]
  (bus/say!! :unblock-thread unblock-tag))

jail body by blocking

(defmacro blocking-jail
  [[timeout unblock-tag finalization verbose] & body]
  `(let [timeout# ~timeout
         unblock-tag# ~unblock-tag
         verbose# ~verbose]
     (try
       ~@body
       (block-thread timeout# unblock-tag#)
       (catch Exception e#
         (when verbose#
           (clojure.stacktrace/print-stack-trace e#))
         (throw e#))
       (finally
         (when verbose#
           (prn [:final :blocking-jail
                 :timeout timeout#
                 :unblock-tag unblock-tag#
                 :finalization '~finalization]))
         ~finalization))))

jail body by looping

(defmacro looping-jail
  [[stop-condition finalization verbose] & body]
  `(let [verbose# ~verbose]
     (try
       (loop []
         ~@body
         (when-not ~stop-condition
           (recur)))
       (catch Exception e#
         (when verbose#
           (clojure.stacktrace/print-stack-trace e#))
         (throw e#))
       (finally
         (when verbose#
           (prn [:final *current-plugin* :looping-jail
                 :stop-condition '~stop-condition
                 :finalization '~finalization]))
         ~finalization))))

register exit hook on the main thread

(defn register-exit-hook
  [key hook]
  (swap! exit-hooks assoc key hook))

undo register-exit-hook

(defn unregister-exit-hook
  [key]
  (swap! exit-hooks dissoc key))

execute all registered exit hooks

(defn execute-all-exit-hooks
  []
  (doseq [[_ hook] @exit-hooks]
    (hook)))
 

listen for commands on bus and execute them

(ns core.plugin.command-executor.main
  (:require (core [init :as init]
                  [state :as state]
                  [bus :as bus]
                  [plugin :as plugin]))
  (:require [clojure.core.async :as async
             :refer [chan <!!]]))
(def defaults
  (atom
   {
    :stop-unblock-tag :stop-core.plugin.command-executor
    }))
(defn populate-parse-opts-vector
  [current-parse-opts-vector]
  (init/add-to-parse-opts-vector [
                                  [nil
                                   "--no-command-executor"
                                   "disable command executor"]
                                  ]))
(defn init
  [options]
  (let [no-command-executor (:no-command-executor options)]
    (when-not no-command-executor
      true)))
(defn run
  [options]
  (let [verbose (:verbose options)
        ch (chan)]
    (plugin/blocking-jail [
                           ;; timeout
                           nil
                           ;; unblock-tag
                           (:stop-unblock-tag @defaults)
                           ;; finalization
                           (do
                             (bus/unsub-topic ch :command))
                           ;; verbose
                           verbose
                           ]
                          (bus/sub-topic ch :command)
                          ;; listen for model update
                          (loop [said (<!! ch)]
                            (let [topic (bus/get-message-topic said)
                                  content (bus/remove-message-topic said)]
                              (case topic
                                :command
                                (do
                                  (let [command (:command content)
                                        param (:param content)
                                        command-impl (state/get-command command)]
                                    (when command-impl
                                      (bus/say!! :response
                                                 {:command command
                                                  :result (command-impl param)}
                                                 verbose))))
                                :else))
                            (recur (<!! ch))))))
(defn stop
  []
  (plugin/set-state-entry :core.plugin.command-executor
                          :stop true)
  (plugin/unblock-thread (:stop-unblock-tag @defaults)))

the config map

(def config-map
  {
   :populate-parse-opts-vector populate-parse-opts-vector
   :init init
   :run run
   :stop stop
   :param {
           :priority 1
           :auto-restart true
           }})
 

echo bus messages

(ns core.plugin.echo.main
  (:require (core [init :as init]
                  [state :as state]
                  [bus :as bus]
                  [plugin :as plugin]))
  (:require [clojure.stacktrace :refer [print-stack-trace]]
            [clojure.core.async
             :as async
             :refer [chan
                     <!!
                     alt!!
                     timeout]]))
(def defaults
  (atom
   {
    :echo-buffer 100
    }))
(defn populate-parse-opts-vector
  [current-parse-opts-vector]
  (init/add-to-parse-opts-vector [
                                  ["-e"
                                   "--echo"
                                   "enable echo"]
                                  ]))
(defn init
  [options]
  (when (:echo options)
    true))

archetype of looping-jail

(defn run
  [options]
  (let [verbose (:verbose options)
        ch (chan (:echo-buffer @defaults))]
    (bus/register-listener ch)
    (plugin/looping-jail [
                          ;; stop condition
                          (plugin/get-state-entry :stop)
                          ;; finalization
                          (do
                            (bus/unregister-listener ch))
                          ;; verbose
                          verbose]
                         (prn (<!! ch)))))

archetype of stopping looping-jail

(defn stop
  [options]
  (plugin/set-state-entry :core.plugin.echo
                          :stop true))

the config map

(def config-map
  {:populate-parse-opts-vector populate-parse-opts-vector
   :init init
   :run run
   :stop stop
   :param {:priority 100                ; echo needs to be run first in order to capture all transcripts
           :auto-restart false
           }})
 

start nREPL server

(ns core.plugin.nrepl.main
  (:require (core [init :as init]
                  [state :as state]
                  [bus :as bus]
                  [plugin :as plugin]))
  (:require [clojure.tools.nrepl.server :as nrepl-server]
            [clojure.stacktrace :refer [print-stack-trace]]
            [cider.nrepl :refer [cider-nrepl-handler]]
            [clojure.core.async
             :as async
             :refer [<!! chan]]))
(def defaults
  (atom
   {
    :stop-unblock-tag :stop-core.plugin.nrepl
    }))
(defn populate-parse-opts-vector
  [current-parse-opts-vector]
  (init/add-to-parse-opts-vector [
                                  (let [option :nrepl-port]
                                    ["-R"
                                     (str "--"
                                          (name option)
                                          " [PORT]")
                                     (str "nREPL port")
                                     :parse-fn (get-in @init/parse-opts-vector-helper
                                                       [:parse-fn :inet-port])])
                                  ]))
(defn init
  [options]
  (when (:nrepl-port options)
    true))

archetype of blocking-jail

(defn run
  [options]
  (let [verbose (:verbose options)
        nrepl-port (:nrepl-port options)]
    (plugin/blocking-jail [
                           ;; timeout
                           nil
                           ;; unblock-tag
                           (:stop-unblock-tag @defaults)
                           ;; finalization
                           (nrepl-server/stop-server (plugin/get-state-entry :nrepl-server))
                           ;; verbose
                           verbose
                           ]
                          (binding [*ns* (create-ns 'user)]
                            (refer-clojure)
                            (use 'clojure.repl)
                            (use 'clojure.pprint)
                            (use 'clojure.java.io)
                            (require '(core [bus :as bus]
                                            [plugin :as plugin]
                                            [state :as state]))
                            (require '[clojure.tools.nrepl.server :as nrepl-server])
                            (plugin/set-state-entry :nrepl-server
                                                    ((resolve 'nrepl-server/start-server)
                                                     :port nrepl-port
                                                     :handler cider-nrepl-handler))))))

archetype of stopping blocking-jail

(defn stop
  [options]
  (plugin/set-state-entry :core.plugin.nrepl
                          :stop true)
  (plugin/unblock-thread (:stop-unblock-tag @defaults)))

the config map

(def config-map
  {
   :populate-parse-opts-vector populate-parse-opts-vector
   :init init
   :run run
   :stop stop
   :param {:priority 0
           :auto-restart true}})
 

global (rather than plugin) state

(ns core.state)
(declare
 ;; state management
 add-state get-state reset-state update-state remove-state list-states
 ;; command management
 register-command unregister-command
 get-command list-commands
 defcommand)

global state

(def ^:dynamic *state*
  (atom {}))

add the state with the init

(defn add-state 
  [state init]
  (swap! *state* assoc state init))

get the state

(defn get-state 
  [state]
  (get @*state* state))

reset the state to the value

(defn reset-state 
  [state value]
  (swap! *state* assoc state value))

update state to (f state & args)

(defn update-state 
  [state f & args]
  (apply swap! *state* update-in [state] f args))

remote the state

(defn remove-state 
  [state]
  (swap! *state* dissoc state))

list all states

(defn list-states
  []
  (keys @*state*))

special states

register command to command dispatcherundo register-commandget commandlist all commandsdefine and register the command

command dispatcher

(let [state-name :command-dispatch]
  (defn register-command
    [command command-impl]
    (update-state state-name
                  assoc command command-impl))
  (defn unregister-command
    [command]
    (update-state state-name
                  dissoc command))
  (defn get-command
    [command]
    (get (get-state state-name) command))
  (defn list-commands
    []
    (keys (get-state state-name)))
  (defmacro defcommand
    [command & body]
    `(do
       (defn ~command
         ~@body)
       (register-command ~(keyword command) ~command))))