commit 022ce6da0376a6bedd159f6006331907c385b16c
parent 64cd3a986c608b1ae639c76a2477f9f82590e2f2
Author: Daniel Barlow <dan@telent.net>
Date: Sun, 20 Feb 2022 12:02:36 +0000
savepoint
Diffstat:
2 files changed, 86 insertions(+), 0 deletions(-)
diff --git a/just/observable-test.fnl b/just/observable-test.fnl
@@ -0,0 +1,51 @@
+(local fennel (require :fennel))
+(local observable (require :observable))
+
+(macro expect [form]
+ `(do
+ (assert ,form ,(view [:failed form]))
+ (print ,(view [:passed form]))))
+
+(macro expect-error [form]
+ `(let [(ok?# msg#) (pcall (fn [] ,form))]
+ (assert (not ok?#) msg#)
+ (print ,(view [:passed (list :error form)]))))
+
+(local app-state
+ (observable.new
+ {
+ :foo 27
+ :bar 54
+ :nest {
+ :name "beatles"
+ :artists [:john :paul: :ringo :george ]
+ }
+ }))
+
+
+(expect (= (app-state:get [:foo]) 27))
+(expect (= (app-state:get [:nest :name]) "beatles"))
+
+(let [s (observable.new {:foo 43})]
+ ;; s:get on a non-leaf returns the subtree. use table.concat
+ ;; for comparison of values
+ (expect (= (table.concat (s:get [])) (table.concat {:foo 43}))))
+
+(let [s (observable.new {:foo 43})]
+ ;; update existing entry
+ (s:update [:foo] 84)
+ (expect (= (s:get [:foo]) 84))
+
+ ;; create new entry
+ (s:update [:baz] 48)
+ (expect (= (s:get [:baz]) 48))
+
+ ;; doesn't create new nested keys
+ (expect-error (s:update [:nonexistent :path] 22)))
+
+(let [s (observable.new {:foo {:bar 43}})]
+ (var win false)
+ ;; observers live on subtrees, not individual nodes
+ (s:observe [:foo] #(set win true))
+ (s:update [:foo :bar] 42)
+ (expect (and win)))
diff --git a/just/observable.fnl b/just/observable.fnl
@@ -0,0 +1,35 @@
+(local {: view} (require :fennel))
+
+(fn concat [dest src]
+ (table.move dest 1 (# dest) (# src) src))
+
+(fn update [data self path value]
+ (let [[first & rest] path]
+ (if (next rest)
+ (update (. data first) self rest value)
+ (do
+ (if data._subscribers
+ (each [_ f (pairs data._subscribers)] (f)))
+ (tset data first value)))))
+
+(fn get [data self path]
+ (let [[first & rest] path]
+ (if (not first) data
+ (next rest) (get (. data first) self rest)
+ (. data first))))
+
+(fn observe [data self path fun]
+ (let [el (get data self path)]
+ (when el
+ (if el._subscribers
+ (el._subscribers:insert fun)
+ (tset el :_subscribers [fun])))))
+
+(fn new [data]
+ {
+ :observe (partial observe data)
+ :update (partial update data)
+ :get (partial get data)
+ })
+
+{: new }