diff --git a/deps.edn b/deps.edn index ef50bbf..39063f2 100644 --- a/deps.edn +++ b/deps.edn @@ -1,8 +1,7 @@ -{:deps {org.clojure/clojure {:mvn/version "1.10.0"} - org.clojure/clojurescript {:mvn/version "1.10.439"} - org.clojure/tools.reader {:mvn/version "1.3.2"} - reagent {:mvn/version "0.8.1"} - re-frame {:mvn/version "0.10.6"}} +{:deps {org.clojure/clojure {:mvn/version "1.10.0"} + org.clojure/clojurescript {:mvn/version "1.10.516"} + reagent {:mvn/version "0.8.1"} + re-frame {:mvn/version "0.10.6"}} :paths ["src"] :aliases {:examples {:extra-paths ["pluto-web/src" "target" "test" "examples/src" "examples/resources"] :extra-deps {com.bhauman/figwheel-main {:mvn/version "0.2.0"} @@ -10,8 +9,8 @@ binaryage/devtools {:mvn/version "0.9.10"}} :main-opts ["-m" "figwheel.main" "-b" "examples/dev" "-r"]} :test-clj {:extra-paths ["test"] - :extra-deps {eftest {:mvn/version "0.5.2"}} + :extra-deps {eftest {:mvn/version "0.5.4"}} :main-opts ["-e" "(require,'[eftest.runner,:refer,[find-tests,run-tests]]),(run-tests,(find-tests,\"test\"))"]} :test-cljs {:extra-paths ["test"] - :extra-deps {olical/cljs-test-runner {:mvn/version "2.1.0"}} + :extra-deps {olical/cljs-test-runner {:mvn/version "3.4.0"}} :main-opts ["-m" "cljs-test-runner.main" "-o" "target/cljs-test-runner-out"]}}} diff --git a/examples/src/pluto/examples.cljs b/examples/src/pluto/examples.cljs index 98261b9..97ebcf3 100644 --- a/examples/src/pluto/examples.cljs +++ b/examples/src/pluto/examples.cljs @@ -1,16 +1,19 @@ (ns pluto.examples - (:require [pluto.web.components :as components] + (:require [pluto.web.components :as components] pluto.web.events pluto.web.queries - [pluto.core :as pluto] - [pluto.storages :as storages] + [pluto.core :as pluto] + [pluto.trace :as trace] + [pluto.storages :as storages] [pluto.web.editor.markdown :as mk] pluto.reader.views - [reagent.core :as reagent] - [reagent.dom :as dom] - [re-frame.core :as re-frame] - [re-frame.registrar :as registrar] - [re-frame.loggers :as re-frame.loggers])) + [reagent.core :as reagent] + [reagent.dom :as dom] + [re-frame.core :as re-frame] + [re-frame.registrar :as registrar] + [re-frame.loggers :as re-frame.loggers] + [pluto.trace :as trace] + [pluto.trace :as trace])) (def warn (js/console.warn.bind js/console)) (re-frame.loggers/set-loggers! @@ -33,20 +36,22 @@ [:li [:span [:b (str type)] (pr-str (dissoc m :type))]]))])) -(defn dispatch-events [events] - (doseq [event events] - (when (vector? event) - (re-frame/dispatch event)))) +;; TODO somehow hook re-frame loggers into :logger +;; https://github.com/Day8/re-frame/blob/master/docs/FAQs/Logging.md -(defn resolve-query [[id :as data]] - (when (registrar/get-handler :sub id) - (re-frame/subscribe data))) +(defn dispatch-events [ctx events] + (doseq [event events] + (if (vector? event) + (re-frame/dispatch event) + (trace/trace ctx (trace/create-trace :error :event/dispatch event))))) + +(defn resolve-query [ctx [id :as data]] + (if (registrar/get-handler :sub id) + (re-frame/subscribe data) + (trace/trace ctx (trace/create-trace :error :query/resolve data)))) (defn parse [m] - (pluto/parse {:env {:id "Extension ID" - :logger nil - :event-fn dispatch-events - :query-fn resolve-query} + (pluto/parse {:env {:id "Extension ID"} :capacities {:components components/all :queries {'random-boolean {:data :random-boolean} @@ -62,7 +67,10 @@ 'alert {:permissions [:read] :data :alert - :arguments {:value :string}}}}} + :arguments {:value :string}}}} + :event-fn dispatch-events + :query-fn resolve-query + :tracer #(.log js/console %)} m)) (defn render-extension [m el el-errors] diff --git a/project.clj b/project.clj index 537ce67..89d7d37 100644 --- a/project.clj +++ b/project.clj @@ -1,9 +1,6 @@ (defproject status-im/pluto "iteration-4-8-SNAPSHOT" - :dependencies [[org.clojure/clojure "1.9.0"] - [org.clojure/clojurescript "1.10.439"] - [org.clojure/tools.reader "1.2.1"] - [reagent "0.8.0"] - [re-frame "0.10.5"] - [binaryage/devtools "0.9.10"] - [day8.re-frame/re-frame-10x "0.3.2"]] + :dependencies [[org.clojure/clojure "1.10.0"] + [org.clojure/clojurescript "1.10.516"] + [reagent "0.8.1"] + [re-frame "0.10.6"]] :source-paths ["src"]) diff --git a/src/pluto/core.cljc b/src/pluto/core.cljc index 05f0a6f..ab968ef 100644 --- a/src/pluto/core.cljc +++ b/src/pluto/core.cljc @@ -1,13 +1,13 @@ (ns pluto.core "Main pluto namespace entry point." (:refer-clojure :exclude [read]) - (:require [clojure.string :as string] - [clojure.tools.reader.edn :as edn] - [pluto.reader.errors :as errors] - [pluto.reader.events :as events] - [pluto.reader.types :as types] - [pluto.reader.views :as views] - [pluto.utils :as utils])) + (:require [clojure.string :as string] + [clojure.edn :as edn] + [pluto.reader.errors :as errors] + [pluto.reader.events :as events] + [pluto.reader.types :as types] + [pluto.reader.views :as views] + [pluto.utils :as utils])) (defn- reader-error [ex] (errors/error ::errors/reader-error (:ex-kind (ex-data ex)) @@ -33,10 +33,6 @@ "Parse an extension value from its type" (fn [ctx ext k v] (namespace k))) -(defmulti parse-value - "Parse an extension value from its type" - (fn [ctx ext k v] (namespace k))) - (defn- capacity? [m s] (let [keys (set (map name (keys m)))] (keys (name s)))) @@ -83,24 +79,22 @@ (let [indexes (zipmap order (range))] (compare [(get indexes (namespace k1)) k1] [(get indexes (namespace k2)) k2]))) -;; TODO somehow hook re-frame loggers into :logger -;; https://github.com/Day8/re-frame/blob/master/docs/FAQs/Logging.md - (defn parse "Parse an extension definition map as encapsulated in :data key of the map returned by `read`. `ctx` is a map defining: * `capacities` a map of valid supported capacities (hooks, queries, events) * `env` a map of extension environment, will be provided as second parameter into event and query handlers + * `event-fn` a function used to fire events + * `query-fn` a function receiving a query and returning an `atom` + * `tracer` [optional] a function that will be passed details about runtime extension execution (event fired, query values updated, ..): {:id 0 :category :error :type :event/dispatch :data {}} - `env` is a map defining: - * `logger` [optional] a function that will be passed details about runtime extension execution (event fired, query values updated, ..): {:type :event :name 'my-event :properties {}} Returns the input map modified so that values have been parsed into: * `:data` the result of parsing * `:permissions` a vector of required permissions * `:errors` a vector of errors maps triggered during the parsing - If `errors` is not empty `data` will not be available. + If `errors` is not empty, `data` will not be available. e.g. diff --git a/src/pluto/reader/blocks.cljc b/src/pluto/reader/blocks.cljc index b5912ea..f47ad72 100644 --- a/src/pluto/reader/blocks.cljc +++ b/src/pluto/reader/blocks.cljc @@ -1,30 +1,40 @@ (ns pluto.reader.blocks - (:require [clojure.walk :as walk] - [re-frame.core :as re-frame] - [reagent.core :as reagent] + (:require [clojure.walk :as walk] + [re-frame.core :as re-frame] + #?(:cljs [reagent.core :as reagent]) [pluto.reader.destructuring :as destructuring] - [pluto.reader.errors :as errors] - [pluto.reader.types :as types] - [pluto.utils :as utils] - [pluto.reader.reference :as reference])) + [pluto.reader.errors :as errors] + [pluto.reader.types :as types] + [pluto.trace :as trace] + [pluto.utils :as utils])) (defmulti parse "Parse a block element. Return hiccup data." (fn [ctx ext parent [type]] type)) -(defn substitute-query-values [m v] - (walk/prewalk #(or (get m %) (when (string? %) (:data (utils/interpolate m %))) %) v)) +(defn- interpolate [ctx m v] + (let [{:keys [data errors]} (utils/interpolate m v)] + (if errors + (trace/trace ctx (trace/create-trace :error :query/interpolation errors)) + data))) + +(defn substitute-query-values [ctx m v] + (walk/prewalk #(or (get m %) (when (string? %) (interpolate ctx m %)) %) v)) (defn- query? [binding-value] (and (vector? binding-value) (let [s (first binding-value)] (or (symbol? s) (keyword? s))))) -(defn resolve-rhs [env v] +(defn resolve-rhs [ctx env v] (cond (= v 'properties) (get env :pluto.reader/properties) (symbol? v) (get env v) - (query? v) (some-> (re-frame/subscribe (substitute-query-values env v)) deref) + (query? v) + (when-let [signal (re-frame/subscribe (substitute-query-values ctx env v))] + (let [o @signal] + (trace/trace ctx (trace/create-trace :trace :query/resolve o)) + o)) :else v)) (defn destructure-into [env k v] @@ -32,18 +42,18 @@ (into env (:data (destructuring/destructure k v))) (assoc env k v))) -(defn resolve-binding [env k v] - (let [v' (resolve-rhs env v)] +(defn resolve-binding [ctx env k v] + (let [v' (resolve-rhs ctx env v)] (destructure-into env k v'))) -(defn resolve-bindings-into [env bindings] - (reduce #(apply resolve-binding %1 %2) (or env {}) (partition 2 bindings))) +(defn resolve-bindings-into [ctx env bindings] + (reduce #(apply resolve-binding ctx %1 %2) (or env {}) (partition 2 bindings))) -(defn replace-atom [values o] +(defn replace-atom [ctx values o] (cond (contains? values o) (get values o) (symbol? o) nil - (string? o) (:data (utils/interpolate values o)) - (and (fn? o) (:event (meta o))) #(o % values) ;; Intercept events and inject the env. TODO remove this hack + (string? o) (interpolate ctx values o) + (and (fn? o) (:event (meta o))) #(o %1 (merge %2 {:env values})) ;; Intercept events and inject the env. TODO remove this hack :else (walk/postwalk-replace values o))) (defn walkup-upto-leaf [f lp? lf tree] @@ -59,24 +69,24 @@ (declare let-block for-block) -(defn let-block [{:keys [prev-env bindings]} children] - (let [new-env (resolve-bindings-into prev-env bindings)] - (walkup-upto-leaf #(replace-atom new-env %) +(defn let-block [{:keys [ctx prev-env bindings]} children] + (let [new-env (resolve-bindings-into ctx prev-env bindings)] + (walkup-upto-leaf #(replace-atom ctx new-env %) #(and (vector? %) (#{for-block let-block} (first %))) (fn [[x props children]] [x (assoc props :prev-env new-env) children]) children))) -(defn for-block [{:keys [prev-env bindings]} children] +(defn for-block [{:keys [ctx prev-env bindings]} children] (let [[k v] bindings - for-values (resolve-rhs prev-env v)] + for-values (resolve-rhs ctx prev-env v)] (when (sequential? for-values) #?(:cljs (apply array (map reagent/as-element (for [val for-values] ^{:key val} - [let-block {:prev-env prev-env :bindings [k val]} + [let-block {:ctx ctx :prev-env prev-env :bindings [k val]} children]))))))) (defn static-value? [v] @@ -127,7 +137,7 @@ (let [{:keys [errors data]} (resolve-and-validate-queries ctx ext bindings)] (if (not-empty errors) {:errors errors} - {:data [let-block {:bindings data} (last body)]})))))) + {:data [let-block {:ctx ctx :bindings data} (last body)]})))))) (defmethod parse 'for [ctx ext parent [_ binding & body]] (cond @@ -140,7 +150,7 @@ (let [{:keys [errors data]} (resolve-and-validate-queries ctx ext binding)] (if (not-empty errors) {:errors errors} - {:data [for-block {:bindings data} + {:data [for-block {:ctx ctx :bindings data} (last body)]})))) (defn when-block [{:keys [test]} body] diff --git a/src/pluto/reader/events.cljc b/src/pluto/reader/events.cljc index b05f0aa..bb3f2b0 100644 --- a/src/pluto/reader/events.cljc +++ b/src/pluto/reader/events.cljc @@ -4,22 +4,29 @@ [pluto.reader.errors :as errors] [pluto.reader.reference :as reference] [pluto.reader.types :as types] + [pluto.trace :as trace] [pluto.utils :as utils])) ;; TODO part of this is duplicated from blocks/let -(defn replace-atom [env o] +(defn- interpolate [ctx m v] + (let [{:keys [data errors]} (utils/interpolate m v)] + (if errors + (trace/trace ctx (trace/create-trace :error :query/interpolation errors)) + data))) + +(defn replace-atom [ctx env o] (cond (contains? env o) (get env o) (symbol? o) nil - (string? o) (:data (utils/interpolate env o)) - (fn? o) #(o % env) + (string? o) (interpolate ctx env o) + (fn? o) #(o %1 (merge {:a %2} {:env env})) :else (walk/postwalk-replace env o))) (defn- resolve-env "Resolve pairs from `env` in `m`. Uses #replace-atom to perform the resolution." - [env m] - (reduce-kv #(assoc %1 %2 (replace-atom env %3)) {} m)) + [ctx env m] + (reduce-kv #(assoc %1 %2 (replace-atom ctx env %3)) {} m)) (defn- resolve-arguments "Resolve an event arguments based on event definition" @@ -30,18 +37,23 @@ (defn- dispatch-events "Dispatches an event using ctx" - [ctx events] - (when-let [f (get-in ctx [:env :event-fn])] - (if (seq events) - (f events) - (println "Empty event dispatched")))) + [{:keys [event-fn] :as ctx} events raw?] + (if (seq events) + (do + (trace/trace ctx (trace/create-trace :log :event/dispatch events)) + (cond + raw? + events + event-fn + (event-fn ctx events))) + (trace/trace ctx (trace/create-trace :error :event/dispatch {})))) (defn- resolve-event "Returns the final event vector" [ctx ext env [event args :as reference]] (let [{data :data} (reference/resolve ctx ext :event reference) {inline :data} (resolve-arguments ctx ext event (or args {}))] - [data (:env ctx) (resolve-env env inline)])) + [data (:env ctx) (resolve-env ctx env inline)])) (defn- create-event [ctx ext env ref] (cond @@ -55,13 +67,15 @@ (defn- resolve-query "Resolve a query using ctx" - [ctx ext query] + [{:keys [query-fn] :as ctx} ext query] (let [{data :data} (types/resolve ctx ext :query query)] - (when-let [f (get-in ctx [:env :query-fn])] - (when-let [signal (f data)] - @signal)))) + (when query-fn + (when-let [signal (query-fn ctx data)] + (let [o @signal] + (trace/trace ctx (trace/create-trace :log :query/resolve o)) + o))))) -(defn merge-resolved-query [ctx ext m {:keys [value bindings]}] +(defn- merge-resolved-query [ctx ext m {:keys [value bindings]}] (cond (map? bindings) (merge m (:data (destructuring/destructure bindings (merge m (resolve-query ctx ext value))))) @@ -74,16 +88,17 @@ (errors/merge-errors {:data (with-meta - (fn [dynamic env] + (fn [dynamic {:keys [env raw?] :as all}] ;; TODO env contains data that shouldn't be there ;; env is the dispatched argument. Used as default but is overridden by the local arguments ;; Perform destructuring based on dynamic and static arguments ;; Then resolve recursive properties in the aggregated env ;; Final map contains inline arguments resolved (let [{:keys [data errors]} (destructuring/destructure properties (merge dynamic arguments))] - ;; TODO handle errors - (let [env' (resolve-env env (merge env (reduce #(merge-resolved-query ctx ext %1 %2) data queries)))] - (dispatch-events ctx (map #(create-event ctx ext env' %) refs))))) + (when (seq errors) + (trace/trace ctx (trace/create-trace :error :event/destructuring errors))) + (let [env' (resolve-env ctx env (merge env (reduce #(merge-resolved-query ctx ext %1 %2) data queries)))] + (dispatch-events ctx (map #(create-event ctx ext env' %) refs) raw?)))) {:event true})} nil)) diff --git a/src/pluto/reader/views.cljc b/src/pluto/reader/views.cljc index cf84990..5956b86 100644 --- a/src/pluto/reader/views.cljc +++ b/src/pluto/reader/views.cljc @@ -1,11 +1,12 @@ (ns pluto.reader.views - (:require [clojure.spec.alpha :as spec] - #?(:cljs [reagent.core :as reagent]) - [pluto.reader.blocks :as blocks] - [pluto.reader.errors :as errors] + (:require [clojure.spec.alpha :as spec] + #?(:cljs [reagent.core :as reagent]) + [pluto.reader.blocks :as blocks] + [pluto.reader.errors :as errors] [pluto.reader.reference :as reference] - [pluto.reader.types :as types] - [pluto.utils :as utils])) + [pluto.reader.types :as types] + [pluto.utils :as utils] + [pluto.trace :as trace])) (spec/def ::form (spec/or @@ -130,21 +131,21 @@ :else acc)) (defn event->fn [ctx ext event f] - (fn [o] + (fn [& o] (when event (let [{:keys [data errors]} (types/resolve ctx ext :event event)] (when data - (data {:a (f o)})))))) + (data (apply f o))))))) #?(:cljs - (defn default-logger [err info] - (.log js/console err info))) + (defn default-logger [ctx error info] + (trace/trace ctx (trace/create-trace :error :view {:error error :info info})))) -(defn error-boundary [component] +(defn error-boundary [ctx component] #?(:cljs (reagent/create-class {:display-name "error-boundary-wrapper" - :component-did-catch default-logger + :component-did-catch #(default-logger ctx %1 %2) :reagent-render (fn error-boundary [_] component)}))) (defn- inject-properties @@ -168,16 +169,16 @@ component-did-update component-will-unmount]} data] (merge {:display-name (str (first data)) :reagent-render (fn [o] - [error-boundary + [error-boundary ctx (inject-properties data o)])} (when get-initial-state {:get-initial-state-mount (event->fn ctx ext get-initial-state #(js->clj %))}) (when component-will-receive-props {:component-will-receive-props (event->fn ctx ext component-will-receive-props #(assoc (js->clj %1) :new %2))}) (when should-component-update {:should-component-update (event->fn ctx ext should-component-update #(assoc (js->clj %1) :old %2 :new %3))}) (when component-will-mount {:component-will-mount (event->fn ctx ext component-will-mount #(js->clj %))}) - (when component-did-mount {:component-did-mount (event->fn ctx ext component-did-mount #(js->clj %))}) + (when component-did-mount {:component-did-mount (event->fn ctx ext component-did-mount #(do {}))}) (when component-will-update {:component-will-update (event->fn ctx ext component-will-update #(assoc (js->clj %1) :new %2))}) (when component-did-update {:component-did-update (event->fn ctx ext component-did-update #(assoc (js->clj %1) :old %2))}) - (when component-will-unmount {:component-will-unmount (event->fn ctx ext component-will-unmount #(js->clj %))})))) + (when component-will-unmount {:component-will-unmount (event->fn ctx ext component-will-unmount #(do {}))})))) ;; TODO normalize to always have a props map (defn parse @@ -189,7 +190,7 @@ #?(:cljs {:data (reagent/create-class (create-reagent-spec ctx ext o data))}) {:data (fn [o] - [error-boundary + [error-boundary ctx (inject-properties data o)])})))) ([ctx ext parent o] (if (list? o) diff --git a/src/pluto/trace.cljc b/src/pluto/trace.cljc new file mode 100644 index 0000000..b3823d8 --- /dev/null +++ b/src/pluto/trace.cljc @@ -0,0 +1,19 @@ +(ns pluto.trace) + +(def ^:private id (atom 0)) + +(defn- next-id [] (swap! id inc)) + +(defn create-trace + "Create a trace map. To be used with `trace`" + [c t v] + {:id (next-id) + :category c + :type t + :data v}) + +(defn trace + "Trace provided object using the ctx `tracer`" + [{:keys [tracer]} m] + (when (fn? tracer) + (tracer m)))