Skip to content

Commit

Permalink
Support docstring and attribute map correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
aroemers committed Feb 16, 2024
1 parent ecc063e commit 6afb802
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 8 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 15 additions & 2 deletions src/redelay/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)))))
Expand Down
52 changes: 50 additions & 2 deletions test/redelay/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down Expand Up @@ -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})))))
Expand Down Expand Up @@ -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))))

0 comments on commit 6afb802

Please sign in to comment.