diff --git a/CHANGELOG.md b/CHANGELOG.md index d2f36e1..6dc4c34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,13 @@ - The string representation of an unrealized State object now says `:unrealized` instead of `:not-delivered`. - The string representation of a realized State object now adheres to a `*print-length*` of 10. - ❗️BREAKING: The low-level `state*` function now takes a `:name` key, instead of `:ns-str` and `:name-str`. The new entry must be a symbol and is still optional. -- ❗️BREAKING: The `defstate` macro no longer supports a docstring or attribute map. It conflicted with start values which were strings or maps. Metadata on the name symbol is still supported. So instead of `(defstate foo "docstring" ...)` you should now write `(defstate ^{:doc "docstring"} foo ...)`. - ❗️BREAKING: The State object now implements `IReference` instead of `IObj`. This means that `with-meta` support has been replaced with `alter-meta!` and `reset-meta!`. This way updating meta data does not result in a new State object anymore. - ❗️BREAKING: The notifications to the `watchpoint` now receive one of `:starting`, `:started`, `:stopping` or `:stopped` as the third argument, and the State object as the fourth argument. +### Fixed + +- The `defstate` macro could sometimes mistake a string or map value for a docstring or attribute map. For example, this would fail `(defstate foo "bar" :stop (println this))`. This is now fixed. + ## 1.1.0 ### Added diff --git a/README.md b/README.md index 313df2c..45d391f 100644 --- a/README.md +++ b/README.md @@ -143,16 +143,17 @@ Trying to redefine a `defstate` which is active (i.e. realized) is skipped and y #### Some other details -The `defstate` macro supports metadata on the name symbol. +The `defstate` macro supports an optional docstring and attributes map. +It also supports metadata on the name symbol. Note that this metadata is set on the var. If you want **metadata** on the State object, you can use `:meta` expression inside `state`, or use Clojure's `alter-meta!` or `reset-meta!` on it. So a full `defstate` could look like this: ```clj -(defstate ^:private my-state +(defstate ^:private my-state "my docs" {:extra "attr"} :start (start-it ...) :stop (stop-it this) - :meta {:my-meta-score 42}) + :meta {:meta-score 42}) ``` Next to metadata support, Clojure's `namespace` and `name` functions also work on states. diff --git a/src/redelay/core.clj b/src/redelay/core.clj index 220142c..18006c2 100644 --- a/src/redelay/core.clj +++ b/src/redelay/core.clj @@ -106,6 +106,18 @@ (recur qualifiers (rest exprs) qualifier (update qualified qualifier (fnil conj []) expr)))) qualified))) +(defn- defstate-attrs [exprs] + (let [{[arg1 arg2 arg3] nil start :start} + (qualified-exprs [:start :stop :meta] exprs)] + (cond (and (string? arg1) (map? arg2) (or start arg3)) + , [(assoc arg2 :doc arg1) (drop 2 exprs)] + (and (string? arg1) (or start arg2)) + , [{:doc arg1} (rest exprs)] + (and (map? arg1) (or start arg2)) + , [arg1 (rest exprs)] + :otherwise + , [nil exprs]))) + (declare state?) (defn- skip-defstate? [ns name] @@ -169,8 +181,9 @@ (if (skip-defstate? *ns* name) (binding [*out* *err*] (println "WARNING: skipping redefinition of active defstate" name)) - (let [default-meta {:dynamic true, :defstate true} - name-with-meta (with-meta name (merge default-meta (meta name))) + (let [[attrs exprs] (defstate-attrs exprs) + default-meta {:dynamic true, :defstate true} + name-with-meta (with-meta name (merge default-meta attrs (meta name))) qualified-name (symbol (str *ns*) (str name))] `(def ~name-with-meta (state ~@exprs :name ~qualified-name))))) diff --git a/test/redelay/core_test.clj b/test/redelay/core_test.clj index 51b6ab5..853c422 100644 --- a/test/redelay/core_test.clj +++ b/test/redelay/core_test.clj @@ -19,7 +19,7 @@ bar (state :start (inc @foo) :name bar) stopped (promise)] - (defstate ^:private baz + (defstate ^:private baz "docstring" {:extra "attr"} :start (dec @bar) (inc @bar) :stop (deliver stopped this) :meta {:dev true}) @@ -48,7 +48,12 @@ (is (= "bar" (name bar))) (is (= "baz" (name baz))) - (is (submap? {:private true, :dynamic true, :defstate true} (meta #'baz))) + (is (submap? {:private true + :dynamic true + :defstate true + :doc "docstring" + :extra "attr"} + (meta #'baz))) (is (= {:dev true} (meta baz))) (is (= {:dev false} (alter-meta! baz update :dev not))) (is (= {:answer 42} (reset-meta! baz {:answer 42}))))) @@ -97,3 +102,46 @@ @notifications)) (finally (remove-watch watchpoint ::test))))) + +(deftest defstate-doc-attr-test + (defstate state-0) + (defstate state-1 "val") + (defstate state-2 "doc" "val") + (defstate state-3 12345 "val") + (defstate state-4 {:my :val}) + (defstate state-5 {:attr true} {:my :val}) + (defstate state-6 "doc" {:my :val}) + (defstate state-7 "doc" {:attr true} {:my :val}) + (defstate state-8 "val" :stop) + (defstate state-9 "doc" {:attr true} :start "val") + + (is (= nil @state-0)) + + (is (= "val" @state-1)) + (is (= nil (-> state-1 var meta :doc))) + + (is (= "val" @state-2)) + (is (= "doc" (-> state-2 var meta :doc))) + + (is (= "val" @state-3)) + (is (= nil (-> state-3 var meta :doc))) + + (is (= {:my :val} @state-4)) + (is (= nil (-> state-4 var meta :attr))) + + (is (= {:my :val} @state-5)) + (is (= true (-> state-5 var meta :attr))) + + (is (= {:my :val} @state-6)) + (is (= "doc" (-> state-6 var meta :doc))) + + (is (= {:my :val} @state-7)) + (is (= "doc" (-> state-7 var meta :doc))) + (is (= true (-> state-7 var meta :attr))) + + (is (= "val" @state-8)) + (is (= nil (-> state-8 var meta :doc))) + + (is (= "val" @state-9)) + (is (= "doc" (-> state-9 var meta :doc))) + (is (= true (-> state-9 var meta :attr))))