From be6e9be24636b65711e32a021e3426706e7c5938 Mon Sep 17 00:00:00 2001 From: Vadim Platonov Date: Fri, 30 Jun 2017 10:25:03 +0200 Subject: [PATCH 1/5] Works under self-hosted Clojurescript --- src/mount/core.cljc | 116 +++++++++++++++++++------------------ src/mount/tools/macro.cljc | 73 +++++++++++++++-------- 2 files changed, 108 insertions(+), 81 deletions(-) diff --git a/src/mount/core.cljc b/src/mount/core.cljc index 913e636..f5b7c32 100644 --- a/src/mount/core.cljc +++ b/src/mount/core.cljc @@ -1,5 +1,5 @@ (ns mount.core - #?(:clj (:require [mount.tools.macro :refer [on-error throw-runtime] :as macro] + #?(:clj (:require [mount.tools.macro :refer [deftime on-error throw-runtime] :as macro] [mount.tools.logger :refer [log]] [clojure.set :refer [intersection]] [clojure.string :as s]) @@ -7,26 +7,10 @@ [clojure.set :refer [intersection]] [mount.tools.logger :refer [log]])) #?(:cljs (:require-macros [mount.core] - [mount.tools.macro :refer [if-clj on-error throw-runtime]]))) + [mount.tools.macro :refer [deftime on-error throw-runtime]]))) -(defonce ^:private -args (atom {})) ;; mostly for command line args and external files -(defonce ^:private state-seq (atom 0)) -(defonce ^:private mode (atom :clj)) -(defonce ^:private meta-state (atom {})) -(defonce ^:private running (atom {})) ;; to clean dirty states on redefs - -;; supporting tools.namespace: (disable-reload!) -#?(:clj - (alter-meta! *ns* assoc ::load false)) ;; to exclude the dependency - -(defn- make-state-seq [state] - (or (:order (@meta-state state)) - (swap! state-seq inc))) - -(deftype NotStartedState [state] - Object - (toString [this] - (str "'" state "' is not started (to start all the states call mount/start)"))) +(defn- with-ns [ns name] + (str "#'" ns "/" name)) ;;TODO validate the whole lifecycle (defn- validate [{:keys [start stop suspend resume] :as lifecycle}] @@ -34,8 +18,24 @@ (not start) (throw-runtime "can't start a stateful thing without a start function. (i.e. missing :start fn)") (or suspend resume) (throw-runtime "suspend / resume lifecycle support was removed in \"0.1.10\" in favor of (mount/stop-except)"))) -(defn- with-ns [ns name] - (str "#'" ns "/" name)) +(defonce ^:private -args (atom {})) ;; mostly for command line args and external files +(defonce ^:private mode (atom :clj)) +(defonce ^:private running (atom {})) ;; to clean dirty states on redefs +(defonce ^:private state-seq (atom 0)) +(defonce ^:private meta-state (atom {})) + +(defn- make-state-seq [state] + (or (:order (@meta-state state)) + (swap! state-seq inc))) + +;; supporting tools.namespace: (disable-reload!) +#?(:clj + (alter-meta! *ns* assoc ::load false)) ;; to exclude the dependency + +(deftype NotStartedState [state] + Object + (toString [this] + (str "'" state "' is not started (to start all the states call mount/start)"))) (defn- pounded? [f] (let [pound "(fn* [] "] ;;TODO: think of a better (i.e. typed) way to distinguish #(f params) from (fn [params] (...))) @@ -110,8 +110,8 @@ :fail? false) :f-failed)] (log cause :error) ;; this would mostly be useful in REPL / browser console - (alter-state! current (NotStartedState. state))) - (alter-state! current (NotStartedState. state))) ;; (!) if a state does not have :stop when _should_ this might leak + (alter-state! current (->NotStartedState state))) + (alter-state! current (->NotStartedState state))) ;; (!) if a state does not have :stop when _should_ this might leak (swap! running dissoc state) (update-meta! [state :status] #{:stopped}))) @@ -149,7 +149,7 @@ ;;TODO: make private after figuring out the inconsistency betwen cljs compile stages ;; (i.e. _sometimes_ this, if private, is not seen by expanded "defmacro" on cljs side) (defn mount-it [s-var s-name s-meta] - (let [with-inst (assoc s-meta :inst (atom (NotStartedState. s-name)) + (let [with-inst (assoc s-meta :inst (atom (->NotStartedState s-name)) :var s-var) on-reload (on-reload-meta s-var) existing? (when-not (= :noop on-reload) @@ -159,39 +159,41 @@ (log (str ">> starting.. " s-name " (namespace was recompiled)")) (up s-name with-inst (atom #{}))))) -#?(:clj - (defmacro defstate - "Defines a state. Restarts on recompilation. - Pass ^{:on-reload :noop} to prevent auto-restart - on ns recompilation, or :stop to stop on recompilation." - [state & body] - (let [[state params] (macro/name-with-attributes state body) - {:keys [start stop] :as lifecycle} (apply hash-map params) - state-name (with-ns *ns* state) - order (make-state-seq state-name)] - (validate lifecycle) - (let [s-meta (cond-> {:order order - :start `(fn [] ~start) - :status #{:stopped}} - stop (assoc :stop `(fn [] ~stop)))] - `(do - ;; (log (str "|| mounting... " ~state-name)) - ;; only create/redefine a new state iff this is not a running ^{:on-reload :noop} - (if-not (running-noop? ~state-name) - (do - (~'defonce ~state (DerefableState. ~state-name)) - (mount-it (~'var ~state) ~state-name ~s-meta)) - (~'defonce ~state (current-state ~state-name))) - (~'var ~state)))))) +(deftime -#?(:clj - (defmacro defstate! [state & {:keys [start! stop!]}] - (let [state-name (with-ns *ns* state)] - `(defstate ~state - :start (~'let [~state (mount/current-state ~state-name)] - ~start!) - :stop (~'let [~state (mount/current-state ~state-name)] - ~stop!))))) +(defmacro defstate + "Defines a state. Restarts on recompilation. + Pass ^{:on-reload :noop} to prevent auto-restart + on ns recompilation, or :stop to stop on recompilation." + [state & body] + (let [[state params] (macro/name-with-attributes state body) + {:keys [start stop] :as lifecycle} (apply hash-map params) + state-name (with-ns *ns* state) + order (make-state-seq state-name)] + (validate lifecycle) + (let [s-meta (cond-> {:order order + :start `(fn [] ~start) + :status #{:stopped}} + stop (assoc :stop `(fn [] ~stop)))] + `(do + ;; (log (str "|| mounting... " ~state-name)) + ;; only create/redefine a new state iff this is not a running ^{:on-reload :noop} + (if-not (running-noop? ~state-name) + (do + (~'defonce ~state (->DerefableState ~state-name)) + (mount-it (~'var ~state) ~state-name ~s-meta)) + (~'defonce ~state (current-state ~state-name))) + (~'var ~state))))) + +(defmacro defstate! [state & {:keys [start! stop!]}] + (let [state-name (with-ns *ns* state)] + `(defstate ~state + :start (~'let [~state (mount.core/current-state ~state-name)] + ~start!) + :stop (~'let [~state (mount.core/current-state ~state-name)] + ~stop!)))) + +) (defn in-cljc-mode [] (reset! mode :cljc)) diff --git a/src/mount/tools/macro.cljc b/src/mount/tools/macro.cljc index 0e827b0..d9b4041 100644 --- a/src/mount/tools/macro.cljc +++ b/src/mount/tools/macro.cljc @@ -1,31 +1,56 @@ (ns mount.tools.macro - #?(:cljs (:require-macros [mount.tools.macro]))) + (:refer-clojure :exclude [case]) + #?(:cljs (:require-macros [mount.tools.macro :refer [deftime case]]))) -#?(:clj - (defmacro if-clj [then else] - (if (-> &env :ns not) - then - else))) +;; From https://github.com/cgrand/macrovich 0.2.0 +;; Licensed under EPL v1. Copyright Cristophe Grand. +(defmacro deftime + "This block will only be evaluated at the correct time for macro definition, at other times its content + are removed. + For Clojure it always behaves like a `do` block. + For Clojurescript/JVM the block is only visible to Clojure. + For self-hosted Clojurescript the block is only visible when defining macros in the pseudo-namespace." + [& body] + (when #?(:clj (not (:ns &env)) :cljs (re-matches #".*\$macros" (name (ns-name *ns*)))) + `(do ~@body))) -#?(:clj - (defmacro on-error [msg f & {:keys [fail?] - :or {fail? true}}] - `(if-clj - (try ~f - (catch Throwable t# - (if ~fail? - (throw (RuntimeException. ~msg t#)) - {:f-failed (ex-info ~msg {} t#)}))) - (try ~f - (catch :default t# - (if ~fail? - (throw (~'str ~msg " " t#)) - {:f-failed (ex-info ~msg {} t#)})))))) +(defmacro usetime + "This block content is not included at macro definition time. + For Clojure it always behaves like a `do` block. + For Clojurescript/JVM the block is only visible to Clojurescript. + For self-hosted Clojurescript the block is invisible when defining macros in the pseudo-namespace." + [& body] + (when #?(:clj true :cljs (not (re-matches #".*\$macros" (name (ns-name *ns*))))) + `(do ~@body))) -#?(:clj - (defmacro throw-runtime [msg] - `(throw (if-clj (RuntimeException. ~msg) - (~'str ~msg))))) +(defmacro case [& {:keys [cljs clj]}] + (if (contains? &env '&env) + `(if (:ns ~'&env) ~cljs ~clj) + (if #?(:clj (:ns &env) :cljs true) + cljs + clj))) + +(deftime + +(defmacro on-error [msg f & {:keys [fail?] + :or {fail? true}}] + `(case + :clj (try ~f + (catch Throwable t# + (if ~fail? + (throw (RuntimeException. ~msg t#)) + {:f-failed (ex-info ~msg {} t#)}))) + :cljs (try ~f + (catch :default t# + (if ~fail? + (throw (~'str ~msg " " t#)) + {:f-failed (ex-info ~msg {} t#)}))))) + +(defmacro throw-runtime [msg] + `(throw (case :clj (RuntimeException. ~msg) + :cljs (~'str ~msg)))) + +) ;; this is a one to one copy from https://github.com/clojure/tools.macro ;; to avoid a lib dependency for a single function From 1ccff026de1cfc43cafcb7027ca0a88218b37a87 Mon Sep 17 00:00:00 2001 From: Andrea Richiardi Date: Fri, 6 Apr 2018 21:23:40 -0700 Subject: [PATCH 2/5] Move macrovich macro in separate namespace and tweak This patch makes sure we use a separate namespace for macrovich and that macros are correctly loaded. It avoids having warnings under :advanced. It also puts vars back where they were before as they look in the right order. --- src/mount/core.cljc | 50 ++++++++++++++------------- src/mount/tools/macro.cljc | 62 ++++++++++------------------------ src/mount/tools/macrovich.cljc | 21 ++++++++++++ 3 files changed, 64 insertions(+), 69 deletions(-) create mode 100644 src/mount/tools/macrovich.cljc diff --git a/src/mount/core.cljc b/src/mount/core.cljc index f5b7c32..929eda2 100644 --- a/src/mount/core.cljc +++ b/src/mount/core.cljc @@ -1,16 +1,34 @@ (ns mount.core - #?(:clj (:require [mount.tools.macro :refer [deftime on-error throw-runtime] :as macro] + #?(:clj (:require [mount.tools.macro :refer [on-error throw-runtime] :as macro] + [mount.tools.macrovich :refer [deftime]] [mount.tools.logger :refer [log]] [clojure.set :refer [intersection]] [clojure.string :as s]) - :cljs (:require [mount.tools.macro :as macro] + :cljs (:require [mount.tools.macro] [clojure.set :refer [intersection]] [mount.tools.logger :refer [log]])) #?(:cljs (:require-macros [mount.core] - [mount.tools.macro :refer [deftime on-error throw-runtime]]))) + [mount.tools.macro :refer [on-error throw-runtime]] + [mount.tools.macrovich :refer [deftime]]))) -(defn- with-ns [ns name] - (str "#'" ns "/" name)) +(defonce ^:private -args (atom {})) ;; mostly for command line args and external files +(defonce ^:private state-seq (atom 0)) +(defonce ^:private mode (atom :clj)) +(defonce ^:private meta-state (atom {})) +(defonce ^:private running (atom {})) ;; to clean dirty states on redefs + +;; supporting tools.namespace: (disable-reload!) +#?(:clj + (alter-meta! *ns* assoc ::load false)) ;; to exclude the dependency + +(defn- make-state-seq [state] + (or (:order (@meta-state state)) + (swap! state-seq inc))) + +(deftype NotStartedState [state] + Object + (toString [this] + (str "'" state "' is not started (to start all the states call mount/start)"))) ;;TODO validate the whole lifecycle (defn- validate [{:keys [start stop suspend resume] :as lifecycle}] @@ -18,24 +36,8 @@ (not start) (throw-runtime "can't start a stateful thing without a start function. (i.e. missing :start fn)") (or suspend resume) (throw-runtime "suspend / resume lifecycle support was removed in \"0.1.10\" in favor of (mount/stop-except)"))) -(defonce ^:private -args (atom {})) ;; mostly for command line args and external files -(defonce ^:private mode (atom :clj)) -(defonce ^:private running (atom {})) ;; to clean dirty states on redefs -(defonce ^:private state-seq (atom 0)) -(defonce ^:private meta-state (atom {})) - -(defn- make-state-seq [state] - (or (:order (@meta-state state)) - (swap! state-seq inc))) - -;; supporting tools.namespace: (disable-reload!) -#?(:clj - (alter-meta! *ns* assoc ::load false)) ;; to exclude the dependency - -(deftype NotStartedState [state] - Object - (toString [this] - (str "'" state "' is not started (to start all the states call mount/start)"))) +(defn- with-ns [ns name] + (str "#'" ns "/" name)) (defn- pounded? [f] (let [pound "(fn* [] "] ;;TODO: think of a better (i.e. typed) way to distinguish #(f params) from (fn [params] (...))) @@ -166,7 +168,7 @@ Pass ^{:on-reload :noop} to prevent auto-restart on ns recompilation, or :stop to stop on recompilation." [state & body] - (let [[state params] (macro/name-with-attributes state body) + (let [[state params] (mount.tools.macro/name-with-attributes state body) {:keys [start stop] :as lifecycle} (apply hash-map params) state-name (with-ns *ns* state) order (make-state-seq state-name)] diff --git a/src/mount/tools/macro.cljc b/src/mount/tools/macro.cljc index d9b4041..fd602fc 100644 --- a/src/mount/tools/macro.cljc +++ b/src/mount/tools/macro.cljc @@ -1,54 +1,26 @@ (ns mount.tools.macro (:refer-clojure :exclude [case]) - #?(:cljs (:require-macros [mount.tools.macro :refer [deftime case]]))) - -;; From https://github.com/cgrand/macrovich 0.2.0 -;; Licensed under EPL v1. Copyright Cristophe Grand. -(defmacro deftime - "This block will only be evaluated at the correct time for macro definition, at other times its content - are removed. - For Clojure it always behaves like a `do` block. - For Clojurescript/JVM the block is only visible to Clojure. - For self-hosted Clojurescript the block is only visible when defining macros in the pseudo-namespace." - [& body] - (when #?(:clj (not (:ns &env)) :cljs (re-matches #".*\$macros" (name (ns-name *ns*)))) - `(do ~@body))) - -(defmacro usetime - "This block content is not included at macro definition time. - For Clojure it always behaves like a `do` block. - For Clojurescript/JVM the block is only visible to Clojurescript. - For self-hosted Clojurescript the block is invisible when defining macros in the pseudo-namespace." - [& body] - (when #?(:clj true :cljs (not (re-matches #".*\$macros" (name (ns-name *ns*))))) - `(do ~@body))) - -(defmacro case [& {:keys [cljs clj]}] - (if (contains? &env '&env) - `(if (:ns ~'&env) ~cljs ~clj) - (if #?(:clj (:ns &env) :cljs true) - cljs - clj))) + #?(:cljs (:require-macros [mount.tools.macrovich :refer [deftime]]) + :clj (:require [mount.tools.macrovich :refer [deftime]]))) (deftime -(defmacro on-error [msg f & {:keys [fail?] - :or {fail? true}}] - `(case - :clj (try ~f - (catch Throwable t# - (if ~fail? - (throw (RuntimeException. ~msg t#)) - {:f-failed (ex-info ~msg {} t#)}))) - :cljs (try ~f - (catch :default t# - (if ~fail? - (throw (~'str ~msg " " t#)) - {:f-failed (ex-info ~msg {} t#)}))))) + (defmacro on-error [msg f & {:keys [fail?] + :or {fail? true}}] + `(mount.tools.macrovich/case + :clj (try ~f + (catch Throwable t# + (if ~fail? + (throw (RuntimeException. ~msg t#)) + {:f-failed (ex-info ~msg {} t#)}))) + :cljs (try ~f + (catch :default t# + (if ~fail? + (throw (~'str ~msg " " t#)) + {:f-failed (ex-info ~msg {} t#)}))))) -(defmacro throw-runtime [msg] - `(throw (case :clj (RuntimeException. ~msg) - :cljs (~'str ~msg)))) + (defmacro throw-runtime [msg] + `(throw (mount.tools.macrovich/case :clj (RuntimeException. ~msg) :cljs (~'str ~msg)))) ) diff --git a/src/mount/tools/macrovich.cljc b/src/mount/tools/macrovich.cljc new file mode 100644 index 0000000..839d3de --- /dev/null +++ b/src/mount/tools/macrovich.cljc @@ -0,0 +1,21 @@ +(ns ^{:doc "From https://github.com/cgrand/macrovich. Licensed under EPL v1. + Copyright Cristophe Grand." } + mount.tools.macrovich + (:refer-clojure :exclude [case])) + +(defmacro deftime + "This block will only be evaluated at the correct time for macro definition, at other times its content + are removed. + For Clojure it always behaves like a `do` block. + For Clojurescript/JVM the block is only visible to Clojure. + For self-hosted Clojurescript the block is only visible when defining macros in the pseudo-namespace." + [& body] + (when #?(:clj (not (:ns &env)) :cljs (re-matches #".*\$macros" (name (ns-name *ns*)))) + `(do ~@body))) + +(defmacro case [& {:keys [cljs clj]}] + (if (contains? &env '&env) + `(if (:ns ~'&env) ~cljs ~clj) + (if #?(:clj (:ns &env) :cljs true) + cljs + clj))) From dc59f3ec98efd1f8666bf30b29f0d7dbff8ef63b Mon Sep 17 00:00:00 2001 From: Andrea Richiardi Date: Fri, 6 Apr 2018 21:24:09 -0700 Subject: [PATCH 3/5] Add minimal self-host testing --- project.clj | 6 +++++- test/core/mount/test_self_host.cljs | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 test/core/mount/test_self_host.cljs diff --git a/project.clj b/project.clj index 02f9081..95abeee 100644 --- a/project.clj +++ b/project.clj @@ -8,6 +8,9 @@ :dependencies [] ;; for visual clarity + :tach {:test-runner-ns 'mount.test-self-host + :source-paths ["test/core"]} + :profiles {:dev {:source-paths ["dev" "dev/clj" "test/clj"] :dependencies [[org.clojure/clojure "1.7.0"] [org.clojure/clojurescript "1.7.170"]; :classifier "aot"] @@ -27,7 +30,8 @@ :plugins [[lein-cljsbuild "1.1.1"] [lein-doo "0.1.6"] [lein-figwheel "0.5.0-2"] - [test2junit "1.1.3"]] + [test2junit "1.1.3"] + [lein-tach "1.0.0"]] :test2junit-output-dir ~(or (System/getenv "CIRCLE_TEST_REPORTS") "target/test2junit") diff --git a/test/core/mount/test_self_host.cljs b/test/core/mount/test_self_host.cljs new file mode 100644 index 0000000..006c5fc --- /dev/null +++ b/test/core/mount/test_self_host.cljs @@ -0,0 +1,17 @@ +(ns mount.test-self-host + (:require + [cljs.test :as t] + + mount.test.fun-with-values + mount.test.private-fun + mount.test.printing + )) + +(t/run-tests + 'mount.test.fun-with-values + 'mount.test.private-fun + 'mount.test.printing + ) + +(defn run-tests [] + (t/run-all-tests #"mount.test.*")) From 75d05e3158bc0794436da025cf07e66da8ca84ac Mon Sep 17 00:00:00 2001 From: Andrea Richiardi Date: Fri, 6 Apr 2018 21:30:20 -0700 Subject: [PATCH 4/5] Add package.json for npmjs.com publishing --- package.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..da08627 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "mount", + "version": "0.1.12", + "license": "EPL-1.0", + "homepage": "https://github.com/tolitius/mount", + "repository": { + "type": "git", + "url": "https://github.com/tolitius/mount" + }, + "author": { + "name" : "tolitius", + "url" : "http://www.dotkam.com" + } + , + "files": [ + "src/*" + ], + "directories": { + "lib": "src" + } +} From 697775ef45bfbef15208eda935caf4d821b4b4d4 Mon Sep 17 00:00:00 2001 From: Andrea Richiardi Date: Fri, 6 Apr 2018 22:12:53 -0700 Subject: [PATCH 5/5] Use more modern clojure and clojurescript in the tests --- project.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project.clj b/project.clj index 95abeee..dbe1985 100644 --- a/project.clj +++ b/project.clj @@ -12,8 +12,8 @@ :source-paths ["test/core"]} :profiles {:dev {:source-paths ["dev" "dev/clj" "test/clj"] - :dependencies [[org.clojure/clojure "1.7.0"] - [org.clojure/clojurescript "1.7.170"]; :classifier "aot"] + :dependencies [[org.clojure/clojure "1.8.0"] + [org.clojure/clojurescript "1.9.946"]; :classifier "aot"] [datascript "0.13.3"] [compojure "1.4.0"] [ring/ring-jetty-adapter "1.1.0"]