diff --git a/src/taoensso/encore/ctx_filter.cljc b/src/taoensso/encore/ctx_filter.cljc deleted file mode 100644 index 604085e7..00000000 --- a/src/taoensso/encore/ctx_filter.cljc +++ /dev/null @@ -1,197 +0,0 @@ -(ns ^:no-doc taoensso.encore.ctx-filter - "Alpha, subject to change without notice! - Low-level toolkit for building context filters. - Used by Telemere, Timbre, Tufte, etc." - {:added "v3.67.0 (2023-09-08)"} - (:require - [clojure.string :as str] - [taoensso.encore :as enc :refer [have have?]]) - - #?(:cljs - (:require-macros - [taoensso.encore.ctx-filter :as cf-macros :refer - [valid-level-int valid-level level>=]]))) - -;;;; Levels - -(def level-aliases - "Map of { } aliases. - Configurable via the `taoensso/level-aliases` edn system config." - (enc/nested-merge - {:min 0 :trace 10 :debug 20 :info 50 :warn 60 :error 70 :fatal 80 :report 90 :max 100} - (enc/read-sys-val* :taoensso/level-aliases))) - -(let [expected (conj (set (keys level-aliases)) 'integer)] - (defn ^:no-doc bad-level! - "Throws an `ex-info` for given invalid level." - [x] - (throw - (ex-info "[encore/ctx-filter] Invalid level" - {:level {:value x, :type (type x)} - :expected expected})))) - -(defn get-level-int - "Returns valid integer level, or nil." - [x] - (enc/cond - (keyword? x) (get level-aliases x) - (integer? x) (long x))) - -(comment (get-level-int :bad)) - -#?(:clj - (do - (defmacro valid-level-int - "Returns valid integer level, or throws." - [x] - (if (enc/const-form? x) - (do (or (get-level-int x) (bad-level! x))) - `(let [x# ~x] (or (get-level-int x#) (bad-level! x#))))) - - (defmacro valid-level - "Returns valid level, or throws." - [x] - (if (enc/const-form? x) - (do (if (get-level-int x) x (bad-level! x))) - `(let [x# ~x] (if (get-level-int x#) x# (bad-level! x#))))) - - (defmacro ^:no-doc const-level>= - "Returns true, false, or nil (inconclusive)." - [x y] - (when (and (enc/const-form? x) (enc/const-form? y)) - (>= (long (valid-level-int x)) (long (valid-level-int y))))) - - (defmacro level>= - "Returns true if valid level `x` has value >= valid level `y`. - Throws if either level is invalid." - [x y] - (if (and (enc/const-form? x) (enc/const-form? y)) - (>= (long (valid-level-int x)) (long (valid-level-int y))) - `(let [~(with-meta 'x-level {:tag 'long}) (valid-level-int ~x) - ~(with-meta 'y-level {:tag 'long}) (valid-level-int ~y)] - (>= ~'x-level ~'y-level)))))) - -(comment (level>= :info :bad)) - -;;;; Filter - -(let [nf-compile (fn [nf-spec] (enc/name-filter (or nf-spec :any))) - nf-conform? (fn [nf-spec n] ((nf-compile nf-spec) n)) - nf->level - (fn [level-spec s] - (if (vector? level-spec) - ;; Spec: [[ ] ... [\"*\" ]] - (enc/rsome - (fn [[nf-spec level]] - (when (nf-conform? nf-spec s) - (valid-level-int level))) - level-spec) - (valid-level-int level-spec)))] - - (defn valid-nf-spec - "Returns valid string filter spec, or throws." - [x] - (if-let [t (enc/catching (do (nf-compile x) nil) t t)] - (throw - (ex-info - (if (fn? x) - "[encore/ctx-filter] Invalid name filter spec (fn filters no longer supported)" - "[encore/ctx-filter] Invalid name filter spec") - {:nf-spec {:value x, :type (type x)}} - t)) - x)) - - (defn ^:no-doc -filter-name? - "Low-level uncached util." - #?(:cljs {:tag boolean}) - [nf-spec n] - (if (nil? nf-spec) false (if ^boolean (nf-conform? nf-spec n) false true))) - - (defn ^:no-doc -filter-level? - "Low-level uncached util." - #?(:cljs {:tag boolean}) - - ([min-level level] - (if (nil? min-level) - false - (if ^boolean (level>= level min-level) false true))) - - ([level-spec nf-name level] - (if (nil? level-spec) - false - (let [min-level (nf->level level-spec nf-name)] - (if ^boolean (level>= level min-level) false true))))) - - (defn ^:no-doc -filter? - "Low-level uncached util." - #?(:cljs {:tag boolean}) - [ns-spec level-spec ns level] - (or - ^boolean (-filter-name? ns-spec ns) - ^boolean (-filter-level? level-spec ns level)))) - -(enc/def* filter? - "Returns true iff `ns` and `level` are filtered (disallowed) by given criteria." - {:arglists #_(:arglists (meta #'-filter?)) '([ns-spec level-spec ns level]) - :tag #?(:cljs boolean :clj nil)} - (enc/fmemoize -filter?)) - -(comment - (do (filter? nil [["my-ns" 5]] "my-ns" 10)) - (enc/qb 1e6 (filter? nil [["my-ns" 5]] "my-ns" :info)) 98.14) - -;;;; Utils - -(defn valid-level-spec - "Returns valid level-spec, or throws." - [x] - (if (vector? x) - (do - (enc/run! - (fn [[nf-spec level]] - (valid-nf-spec nf-spec) - (valid-level level)) - x) - x) - (valid-level x))) - -(defn update-level-spec - "TODO Docstring - ns, old, new may be nil" - ([old-spec new-spec] (update-level-spec old-spec nil new-spec)) - ([old-spec nf-spec new-spec] - (if (nil? nf-spec) - (when new-spec - (valid-level-spec new-spec)) - - ;; Update nf-specific level-spec - (let [new-spec (when new-spec (valid-level new-spec)) - nf-spec (valid-nf-spec nf-spec) - - old-vec (if (vector? old-spec) old-spec (if old-spec [["*" (valid-level old-spec)]] [])) - new-vec - (reduce ; Remove any pre-existing [ _] or [#{} _] entries - (fn [acc [nf-spec* _level :as entry]] - (if-let [exact-match? - (or - (= nf-spec* nf-spec) - (= nf-spec* #{nf-spec}))] - (do acc) ; Remove entry - (conj acc entry) ; Retain entry - )) - - (if new-spec - [[nf-spec new-spec]] ; Insert new-spec entry at head - []) - - old-vec)] - - (if-let [simplified ; [["*" :info]] -> :info - (when (= (count new-vec) 1) - (let [[[nf-spec level]] new-vec] - (when (contains? #{"*" :any} nf-spec) - level)))] - simplified - (not-empty new-vec)))))) - -(comment :see-tests) diff --git a/src/taoensso/encore/signals.cljc b/src/taoensso/encore/signals.cljc new file mode 100644 index 00000000..80d6b63e --- /dev/null +++ b/src/taoensso/encore/signals.cljc @@ -0,0 +1,467 @@ +(ns ^:no-doc taoensso.encore.signals + "Experimental, subject to change without notice!! + Private low-level toolkit for contextual signal filters. + Used by Telemere, Tufte, Timbre, etc. + + \"Signal\" here refers to an object or datum that: + - Originates in an ns (generated or received there, etc.) + - Has a level (priority, significance, etc.) + - May have an identifier + - May have a kind (type, taxonomy, etc.)" + + {:added "vX.Y.Z (YYYY-MM-DD)"} + (:require + [clojure.string :as str] + [taoensso.encore :as enc :refer [have have?]]) + + #?(:cljs + (:require-macros + [taoensso.encore.signals :refer + [valid-level-int valid-level level>=]]))) + +(comment + (remove-ns 'taoensso.encore.signals) + (:api (enc/interns-overview))) + +;;;; Levels + +(def level-aliases + "Map of { } aliases. + Configurable via the `taoensso/level-aliases` edn system config." + (enc/nested-merge + {:min 0 :trace 10 :debug 20 :info 50 :warn 60 :error 70 :fatal 80 :report 90 :max 100} + (enc/read-sys-val* :taoensso/level-aliases))) + +(let [expected (conj (set (keys level-aliases)) 'integer)] + (defn ^:no-doc bad-level! + "Throws an `ex-info` for given invalid level." + [x] + (throw + (ex-info "[encore/signals] Invalid level" + {:level {:value x, :type (type x)} + :expected expected})))) + +(defn ^:no-doc get-level-int + "Returns valid integer level, or nil." + [x] + (enc/cond + (keyword? x) (get level-aliases x) + (integer? x) (long x))) + +(comment (get-level-int :bad)) + +#?(:clj + (do + (defmacro ^:no-doc valid-level-int + "Returns valid integer level, or throws." + [x] + (if (enc/const-form? x) + (do (or (get-level-int x) (bad-level! x))) + `(let [x# ~x] (or (get-level-int x#) (bad-level! x#))))) + + (defmacro ^:no-doc valid-level + "Returns valid level, or throws." + [x] + (if (enc/const-form? x) + (do (if (get-level-int x) x (bad-level! x))) + `(let [x# ~x] (if (get-level-int x#) x# (bad-level! x#))))) + + (defmacro ^:no-doc const-level>= + "Returns true, false, or nil (inconclusive)." + [x y] + (when (and (enc/const-form? x) (enc/const-form? y)) + (>= (long (valid-level-int x)) (long (valid-level-int y))))) + + (defmacro ^:no-doc level>= + "Returns true if valid level `x` has value >= valid level `y`. + Throws if either level is invalid." + [x y] + (if (and (enc/const-form? x) (enc/const-form? y)) + (>= (long (valid-level-int x)) (long (valid-level-int y))) + `(let [~(with-meta 'x-level {:tag 'long}) (valid-level-int ~x) + ~(with-meta 'y-level {:tag 'long}) (valid-level-int ~y)] + (>= ~'x-level ~'y-level)))))) + +(comment (level>= :info :bad)) + +;;;; Basic filtering + +(let [nf-compile (fn [nf-spec ] (enc/name-filter (or nf-spec :any))) + nf-conform? (fn [nf-spec n] ((nf-compile nf-spec) n)) + nf->min-level + (fn [min-level nf-arg] + (if (vector? min-level) + ;; [[ ] ... [\"*\" ]] + (enc/rsome + (fn [[nf-spec min-level]] + (when (nf-conform? nf-spec nf-arg) + (valid-level-int min-level))) + min-level) + (valid-level-int min-level)))] + + (defn ^:no-doc valid-nf-spec + "Returns valid `enc/name-filter` spec, or throws." + [x] + (if-let [t (enc/catching (do (nf-compile x) nil) t t)] + (throw + (ex-info + (if (fn? x) + "[encore/signals] Invalid name filter (fn filters no longer supported)" + "[encore/signals] Invalid name filter") + {:name-filter {:value x, :type (type x)}} + t)) + x)) + + (defn ^:no-doc filter-name? + "Low-level name filter." + #?(:cljs {:tag boolean}) + [nf-spec nf-arg] + (if ^boolean (nf-conform? nf-spec nf-arg) false true)) + + (defn ^:no-doc filter-level? + "Low-level level filter." + #?(:cljs {:tag boolean}) + ([min-level level] (if ^boolean (level>= level min-level) false true)) + ([min-level nf-arg level] + (let [min-level (nf->min-level min-level nf-arg)] + (if ^boolean (level>= level min-level) false true))))) + +(defn ^:no-doc valid-min-level + "Returns valid min level, or throws." + [x] + (if (vector? x) + (do + (enc/run! + (fn [[nf-spec min-level]] + (valid-nf-spec nf-spec) + (valid-level min-level)) + x) + x) + (valid-level x))) + +(defn update-min-level + "Low-level util to update given min level." + ([old new] (update-min-level old nil new)) + ([old nf-spec new] + (if (nil? nf-spec) + (when new + (valid-min-level new)) + + ;; Update name-specific min-level + (let [new (when new (valid-level new)) + nf-spec (valid-nf-spec nf-spec) + + old-vec (if (vector? old) old (if old [["*" (valid-level old)]] [])) + new-vec + (reduce ; Remove any pre-existing [ _] or [#{} _] entries + (fn [acc [nf-spec* _min-level :as entry]] + (if-let [exact-match? + (or + (= nf-spec* nf-spec) + (= nf-spec* #{nf-spec}))] + (do acc) ; Remove entry + (conj acc entry) ; Retain entry + )) + + (if new + [[nf-spec new]] ; Insert new entry at head + []) + + old-vec)] + + (if-let [simplified ; [["*" :info]] -> :info + (when (= (count new-vec) 1) + (let [[[nf-spec min-level]] new-vec] + (when (contains? #{"*" :any} nf-spec) + min-level)))] + simplified + (not-empty new-vec)))))) + +;;;; SigFilter + +(deftype SigFilter [ns-filter kind-filter id-filter min-level filter-fn] + #?(:clj clojure.lang.IDeref :cljs IDeref) + (#?(:clj deref :cljs -deref) [_] + {:ns-filter ns-filter :kind-filter kind-filter + :id-filter id-filter :min-level min-level}) + + #?(:clj clojure.lang.IFn :cljs IFn) + (#?(:clj invoke :cljs -invoke) [_ ns kind id level] (filter-fn ns kind id level)) + (#?(:clj invoke :cljs -invoke) [_ ns id level] (filter-fn ns id level)) + (#?(:clj invoke :cljs -invoke) [_ ns level] (filter-fn ns level))) + +(enc/def* sig-filter + "Returns nil, or a stateful (caching) `SigFilter` with the given specs." + {:arglists + '([{:keys [ns-filter kind-filter id-filter min-level]}] + [ns-filter kind-filter id-filter min-level])} + + (let [get-cached + (enc/fmemoize ; Same specs -> share cache (ref. transparent) + (fn sig-filter + [ ns-filter kind-filter id-filter min-level] + (when (or ns-filter kind-filter id-filter min-level) + (do ; Validation + (when ns-filter (valid-nf-spec ns-filter)) + (when kind-filter (valid-nf-spec kind-filter)) + (when id-filter (valid-nf-spec id-filter)) + (when min-level + (if (map? min-level) + (enc/run-kv! + (fn [kind min-level] (valid-min-level min-level)) + min-level) + + (valid-min-level min-level)))) + + (SigFilter. ns-filter kind-filter id-filter min-level + (enc/fmemoize + (fn filter-signal? + ([ns kind id level] + (boolean + (or + (when ns-filter (filter-name? ns-filter ns)) + (when kind-filter (filter-name? kind-filter kind)) + (when id-filter (filter-name? id-filter id)) + (when min-level + (if (map? min-level) + (when-let [min-level (enc/get1 min-level kind :default nil)] + (filter-level? min-level ns level)) + (filter-level? min-level ns level))) + false))) + + ([ns id level] + (boolean + (or + (when ns-filter (filter-name? ns-filter ns)) + (when id-filter (filter-name? id-filter id)) + (when min-level (filter-level? min-level ns level)) + false))) + + ([ns level] + (boolean + (or + (when ns-filter (filter-name? ns-filter ns)) + (when min-level (filter-level? min-level ns level)) + false)))))))))] + + (fn sig-filter + ([ ns-filter kind-filter id-filter min-level] (get-cached ns-filter kind-filter id-filter min-level)) + ([{:keys [ns-filter kind-filter id-filter min-level :as specs]}] (get-cached ns-filter kind-filter id-filter min-level))))) + +(comment ; [69.31 83.4 93.7] + [(let [sf (sig-filter "*" nil nil nil)] (enc/qb 1e6 (sf :ns :info))) + (let [sf (sig-filter "*" nil nil nil)] (enc/qb 1e6 (sf :ns :id :info))) + (let [sf (sig-filter "*" nil nil nil)] (enc/qb 1e6 (sf :ns :kind :id :info)))]) + +(defprotocol IFilterableSignal + (filter-signal? [_ sig-filter] + "Returns boolean indicating if given signal is filtered by given `SigFilter`.")) + +(let [nil-sf (SigFilter. nil nil nil nil nil)] + (defn update-sig-filter + "Returns nil, or updated stateful (caching) `SigFilter`." + {:arglists '([old-sig-filter {:keys [ns-filter kind-filter id-filter min-level]}])} + [old specs] + (let [^SigFilter base (or old nil-sf)] + (if (empty? specs) + old + (sig-filter + (get specs :ns-filter (.-ns-filter base)) + (get specs :kind-filter (.-kind-filter base)) + (get specs :id-filter (.-id-filter base)) + (get specs :min-level (.-min-level base))))))) + +(comment (update-sig-filter nil {})) + +(enc/def* reference-spec-docs + "TODO" + nil) + +;;;; Callsite filtering + +(enc/defonce callsite-counter (enc/counter)) +#?(:clj (defn- const-forms? [& forms] (enc/revery? enc/const-form? forms))) + +(let [limiters_ (enc/latom {})] + (defn ^:no-doc callsite-limit!? + "Calls the identified stateful limiter and returns true iff limited." + #?(:cljs {:tag boolean}) + [limiter-id spec req-id] + (let [limiter + (or + (get (limiters_) limiter-id) ; Common case + (limiters_ limiter-id #(or % (enc/limiter {} spec))))] + (some? (limiter req-id))))) + +(comment (enc/qb 1e6 (callsite-limit!? :limiter-id1 [[1 4000]] :req-id))) ; 152.26 + +#?(:clj + (defn filterable-callsite + "Low-level util for writing macros with compile-time and runtime filtering. + Returns {:keys [callsite elide? rt-sig-filter-form]}. + + `macro-opts` / `opts-arg` :keys include: + [kind id level sample rate-limit when]." + + [{:keys [loc sf-arity ct-sig-filter rt-sig-filter, opts-arg] + :as macro-opts}] + + (let [callsite (callsite-counter) + {:keys [ns line column file]} loc + + opts-arg-map (when (map? opts-arg) opts-arg) + get-opt-form + (fn [check-runtime-opts? k] + (if-let [entry (or (find macro-opts k) (find opts-arg-map k))] + (val entry) + (when check-runtime-opts? `(get ~'__opts-arg ~k)))) + + elide? + (when-let [sf ct-sig-filter] + (let [kind (get-opt-form false :kind) + id (get-opt-form false :id) + level (get-opt-form false :level)] + (case (int (or sf-arity -1)) + 2 (when (const-forms? level) (sf ns level)) + 3 (when (const-forms? id level) (sf ns id level)) + 4 (when (const-forms? kind id level) (sf ns kind id level)) + (enc/-unexpected-arg! sf-arity + {:context `callsite-filter + :param 'sf-arity + :expected #{2 3 4}})))) + + base-rv {:callsite callsite, :get-opts-form-fn get-opt-form}] + + (if elide? + (assoc base-rv :elide? true) + (let [rt-filter-form + (let [sample-form + (if-let [rate (enc/const-form (get-opt-form false :sample))] + ;; Compile-time rate + `(< (Math/random) ~(enc/as-pnum! rate)) + + ;; Runtime rate + `(if-let [~'rate ~(get-opt-form true :sample)] + (< (Math/random) (double ~'rate)) + false)) + + rl-form + (when-let [spec (enc/const-form (get-opt-form false :rate-limit))] + `(callsite-limit!? ~callsite ~spec nil)) + + when-form + (when-let [whenf (get-opt-form false :when)] + `(let [~'this-callsite ~callsite] (not ~whenf))) + + sf-form + (case (int (or sf-arity -1)) + 2 `(when-let [~'sf ~rt-sig-filter] (~'sf ~(get-opt-form true :ns) ~(get-opt-form true :level))) + 3 `(when-let [~'sf ~rt-sig-filter] (~'sf ~(get-opt-form true :ns) ~(get-opt-form true :id) ~(get-opt-form true :level))) + 4 `(when-let [~'sf ~rt-sig-filter] (~'sf ~(get-opt-form true :ns) ~(get-opt-form true :kind) ~(get-opt-form true :id) ~(get-opt-form true :level))) + (enc/-unexpected-arg! sf-arity + {:context `callsite-filter + :param 'sf-arity + :expected #{2 3 4}}))] + + ;; Check by ascending cost, rl last since it increments + `(or ~@(filter some? [sample-form sf-form when-form rl-form])))] + + (assoc base-rv :rt-filter-form rt-filter-form)))))) + +(comment (filterable-callsite {:sf-arity 2})) + +;;;; Dispatch + +(defn wrap-handler + "Wraps given handler-fn to add common handler-level functionality." + [handler-fn + {:keys + [#?(:clj async) sample rate-limit, + ns-filter kind-filter id-filter min-level + ;; error-fn backp-fn ; TODO + ]}] + + (let [;; rl-error (enc/limiter {} [[1 (enc/ms :mins 15)]]) + ;; rl-backp (enc/limiter {} [[1 (enc/ms :mins 15)]]) + + sample-rate (when sample (enc/as-pnum! sample)) + rate-limiter (when-let [spec rate-limit] (enc/limiter {} spec)) + sig-filter* (sig-filter ns-filter kind-filter id-filter min-level) + + wrapped-handler-fn + (fn wrapped-handler-fn [handler-arg] + (enc/try* + (enc/cond + (if sample-rate (< (Math/random) ^double sample-rate) false) :noop + (if rate-limiter (if (rate-limiter nil) true false) false) :noop + (if sig-filter* + ^boolean (filter-signal? handler-arg sig-filter*) + ;; ^boolean + ;; (sig-filter* + ;; (.-ns handler-val) (.-kind handler-val) + ;; (.-id handler-val) (.-level handler-val)) + false) + + :else (do (handler-fn handler-arg) nil)) + + (catch :any t + ;; TODO + ;; (when error-fn (error-fn t)) + ;; (when-not (some? (rl-error :TODO)) + ;; (comment "TODO Encore error signal with `t`")) + nil)))] + + #?(:cljs wrapped-handler-fn + :clj + (if-not async + wrapped-handler-fn + (let [runner (enc/runner (have map? async))] + (fn wrapped-handler-fn* [handler-arg] + (when-let [back-pressure? (false? (runner (fn [] (wrapped-handler-fn handler-arg))))] + ;; TODO + ;; (when backp-fn (backp-fn)) + ;; (when-not (some? (rl-backp :TODO)) + ;; (comment "TODO Encore warn signal")) + ))))))) + +(defn -registered-handlers [handlers] (enc/map-vals meta handlers)) + +#?(:clj + (defmacro registered-handlers + "Returns { } for all registered handlers." + [handlers-var] `(-registered-handlers ~handlers-var))) + +#?(:clj + (defmacro handler-register! + "Registers given (handler-fn [handler-arg]), and returns + { } for all handlers now registered." + [handlers-var handler-id handler-fn dispatch-opts] + `(let [wrapped-handler-fn# (with-meta (wrap-handler ~handler-fn ~dispatch-opts) (or ~dispatch-opts {}))] + (-registered-handlers + #?(:cljs (set! ~handlers-var (assoc ~handlers-var ~handler-id wrapped-handler-fn#)) + :clj (alter-var-root #'~handlers-var (fn [m#] (assoc m# ~handler-id wrapped-handler-fn#)))))))) + +#?(:clj + (defmacro handler-unregister! + "Unregisters handler with given id, and returns { } + for all handlers still registered." + [handlers-var handler-id] + `(-registered-handlers + #?(:cljs (set! ~handlers-var (dissoc ~handlers-var ~handler-id)) + :clj (alter-var-root #'~handlers-var (fn [m#] (dissoc m# ~handler-id))))))) + +#?(:clj + (defmacro with-handler + "Low-level util. Executes form with given pre-wrapped handler-fn registered." + [handlers-var handler-id wrapped-handler-fn form] + `(binding [~handlers-var (assoc ~handlers-var ~handler-id ~wrapped-handler-fn)] + ~form))) + +(comment + (def *handlers* nil) + (handler-register! *handlers* :hid1 (fn [x]) {}) + (registered-handlers *handlers*)) + +(enc/def* reference-dispatch-opts-docs + "TODO" + nil) diff --git a/test/taoensso/encore_tests.cljc b/test/taoensso/encore_tests.cljc index 32b0fb11..f5476473 100644 --- a/test/taoensso/encore_tests.cljc +++ b/test/taoensso/encore_tests.cljc @@ -6,7 +6,7 @@ ;; [clojure.test.check.properties :as tc-props] [clojure.string :as str] [taoensso.encore :as enc] - [taoensso.encore.ctx-filter :as cf]) + [taoensso.encore.signals :as sigs]) #?(:cljs (:require-macros @@ -708,98 +708,115 @@ [[true true true false false false] [0 1 2 3 4 5]]))])) -;;;; Context filter +;;;; Signal filtering -(deftest _context-filter +(defn- sf-filter? + ([[ns-spec ns] [kind-spec kind] [id-spec id] [min-level-spec level]] (when-let [sf (sigs/sig-filter ns-spec kind-spec id-spec min-level-spec)] (sf ns kind id level))) + ([[ns-spec ns] [id-spec id] [min-level-spec level]] (when-let [sf (sigs/sig-filter ns-spec nil id-spec min-level-spec)] (sf ns id level))) + ([[ns-spec ns] [min-level-spec level]] (when-let [sf (sigs/sig-filter ns-spec nil nil min-level-spec)] (sf ns level)))) + +(deftest _sig-filter [(testing "Levels" - [(is (= (cf/get-level-int -10) -10)) - (is (= (cf/get-level-int :info) 50)) - (is (= (cf/get-level-int nil) nil)) - (is (= (cf/get-level-int :__nx) nil)) - - (is (= (cf/valid-level-int -10) -10)) - (is (= (cf/valid-level-int :info) 50)) - ;; (is (->> (cf/valid-level-int nil) (enc/throws? :common "Invalid level"))) ; Macro-time error - (is (->> ((fn [x] (cf/valid-level-int x)) nil) (enc/throws? :common "Invalid level"))) - - (is (cf/level>= :error :info)) - (is (cf/level>= :error :error)) - (is (not (cf/level>= :info :error))) - (is (cf/level>= :min -10))]) - - (testing "Utils" - [(testing "update-level-spec" - [(is (= (cf/update-level-spec nil nil) nil)) - (is (= (cf/update-level-spec nil :info) :info)) - (is (= (cf/update-level-spec :info :error) :error)) - (is (->> (cf/update-level-spec nil :__nx) (enc/throws? :common "Invalid level"))) - - (is (= (cf/update-level-spec nil nil [["ns1" :info]]) [["ns1" :info]])) - (is (= (cf/update-level-spec nil "ns1" :info) [["ns1" :info]])) - (is (->> (cf/update-level-spec nil "ns1" [["ns1" :info]]) (enc/throws? :common "Invalid level"))) - (is (->> (cf/update-level-spec nil nil [["ns1" :__nx]]) (enc/throws? :common "Invalid level"))) - (is (->> (cf/update-level-spec nil -1 :info) (enc/throws? :common "Invalid name filter spec"))) - - (is (= (cf/update-level-spec :debug "ns1" :info) [["ns1" :info] ["*" :debug]])) - (is (= (-> - :debug - (cf/update-level-spec "ns1" :info) - (cf/update-level-spec "ns2" :trace) - (cf/update-level-spec "ns3" -20) - (cf/update-level-spec "ns2" :warn)) - [["ns2" :warn] ["ns3" -20] ["ns1" :info] ["*" :debug]]))])]) - - (testing "Filtering" - [(testing "basics" - [(is (false? (cf/filter? nil nil nil nil))) - (is (false? (cf/filter? nil nil "ns1" :info))) - (is (false? (cf/filter? nil :info "ns1" :info))) - (is (true? (cf/filter? nil :warn "ns1" :info))) - - ;; (is (false? (cf/filter? (fn [_] true) nil "ns1" :info)) "Arb ns-spec fn") - ;; (is (true? (cf/filter? (fn [_] false) nil "ns1" :info)) "Arb ns-spec fn") - - (is (false? (cf/filter? "ns1" nil "ns1" :info))) - (is (true? (cf/filter? "ns2" nil "ns1" :info))) - (is (false? (cf/filter? "ns*" nil "ns1" :info))) - (is (false? (cf/filter? #{"ns1" "ns2"} nil "ns1" :info))) - (is (true? (cf/filter? #{"ns2" "ns3"} nil "ns1" :info))) - (is (false? (cf/filter? #"ns(\d*)?" nil "ns5" :info))) - (is (false? (cf/filter? #"ns(\d*)?" nil "ns" :info))) - - (is (false? (cf/filter? {:allow #{"ns1"}} nil "ns1" :info))) - (is (false? (cf/filter? {:allow #{"ns*"}} nil "ns1" :info))) - (is (false? (cf/filter? {:allow #{"*"}} nil "ns1" :info))) - (is (false? (cf/filter? {:allow #{:any}} nil "ns1" :info))) - (is (false? (cf/filter? {:allow "*"} nil "ns1" :info))) - (is (false? (cf/filter? {:allow :any} nil "ns1" :info))) - (is (true? (cf/filter? {:allow #{}} nil "ns1" :info))) - - (is (false? (cf/filter? {:allow #{"a.*.c"}} nil "a.b3.c" :info))) - (is (false? (cf/filter? {:allow #{"a.*.c"}} nil "a.b1.b2.c" :info))) - - (is (false? (cf/filter? {:deny #{}} nil "ns1" :info))) - (is (true? (cf/filter? {:deny #{"*"}} nil "ns1" :info))) - (is (true? (cf/filter? {:deny #{:any}} nil "ns1" :info))) - (is (true? (cf/filter? {:deny "*"} nil "ns1" :info))) - (is (true? (cf/filter? {:deny :any} nil "ns1" :info))) - - (is (true? (cf/filter? {:allow :any :deny :any} nil "ns1" :info)) "Deny > allow") - - (is (true? (cf/filter? "ns1" nil nil :info)) "ns-spec without ns") - (is (->> (cf/filter? nil :info nil nil) (enc/throws? :common "Invalid level")) "level-spec without level")]) - - (testing "ns-specific levels" - [(is (false? (cf/filter? nil [["*" :info]] "ns1" :info))) - (is (true? (cf/filter? nil [["*" :info]] "ns1" :debug))) - (is (false? (cf/filter? nil [["ns1" :info] ["ns1" :warn] ["*" :warn]] "ns1" :info)) "Match sequentially") - (is (true? (cf/filter? nil [["ns1" :warn] ["ns1" :info] ["*" :warn]] "ns1" :info)) "Match sequentially") - - (is (false? (cf/filter? nil [["ns.internal.*" :warn] ["ns.public.*" :info] ["*" :warn]] "ns.public.foo" :info))) - (is (true? (cf/filter? nil [["ns.internal.*" :warn] ["ns.public.*" :info] ["*" :warn]] "ns.internal.foo" :info))) - - (is (->> (cf/filter? nil [["*" :info]] "ns1" nil) (enc/throws? :common "Invalid level"))) - (is (->> (cf/filter? nil [["*" :info]] nil nil) (enc/throws? :common "Invalid level")))])])]) + [(is (= (sigs/get-level-int -10) -10)) + (is (= (sigs/get-level-int :info) 50)) + (is (= (sigs/get-level-int nil) nil)) + (is (= (sigs/get-level-int :__nx) nil)) + + (is (= (sigs/valid-level-int -10) -10)) + (is (= (sigs/valid-level-int :info) 50)) + ;; (is (->> (sigs/valid-level-int nil) (enc/throws? :common "Invalid level"))) ; Macro-time error + (is (->> ((fn [x] (sigs/valid-level-int x)) nil) (enc/throws? :common "Invalid level"))) + + (is (sigs/level>= :error :info)) + (is (sigs/level>= :error :error)) + (is (not (sigs/level>= :info :error))) + (is (sigs/level>= :min -10))]) + + (testing "update-min-level" + [(is (= (sigs/update-min-level nil nil) nil)) + (is (= (sigs/update-min-level nil :info) :info)) + (is (= (sigs/update-min-level :info :error) :error)) + (is (->> (sigs/update-min-level nil :__nx) (enc/throws? :common "Invalid level"))) + + (is (= (sigs/update-min-level nil nil [["ns1" :info]]) [["ns1" :info]])) + (is (= (sigs/update-min-level nil "ns1" :info) [["ns1" :info]])) + (is (->> (sigs/update-min-level nil "ns1" [["ns1" :info]]) (enc/throws? :common "Invalid level"))) + (is (->> (sigs/update-min-level nil nil [["ns1" :__nx]]) (enc/throws? :common "Invalid level"))) + (is (->> (sigs/update-min-level nil -1 :info) (enc/throws? :common "Invalid name filter"))) + + (is (= (sigs/update-min-level :debug "ns1" :info) [["ns1" :info] ["*" :debug]])) + (is (= (-> + :debug + (sigs/update-min-level "ns1" :info) + (sigs/update-min-level "ns2" :trace) + (sigs/update-min-level "ns3" -20) + (sigs/update-min-level "ns2" :warn)) + [["ns2" :warn] ["ns3" -20] ["ns1" :info] ["*" :debug]]))]) + + (testing "Filter basics [ns level]" + [(is (nil? (sf-filter? [nil nil] [nil nil]))) + (is (nil? (sf-filter? [nil "ns1"] [nil :info]))) + (is (false? (sf-filter? [nil "ns1"] [:info :info]))) + (is (true? (sf-filter? [nil "ns1"] [:warn :info]))) + + (is (false? (sf-filter? ["ns1" "ns1"] [nil :info]))) + (is (true? (sf-filter? ["ns2" "ns1"] [nil :info]))) + (is (false? (sf-filter? ["ns*" "ns1"] [nil :info]))) + (is (false? (sf-filter? [#{"ns1" "ns2"} "ns1"] [nil :info]))) + (is (true? (sf-filter? [#{"ns2" "ns3"} "ns1"] [nil :info]))) + (is (false? (sf-filter? [#"ns(\d*)?" "ns5"] [nil :info]))) + (is (false? (sf-filter? [#"ns(\d*)?" "ns"] [nil :info]))) + + (is (false? (sf-filter? [{:allow #{"ns1"}} "ns1"] [nil :info]))) + (is (false? (sf-filter? [{:allow #{"ns*"}} "ns1"] [nil :info]))) + (is (false? (sf-filter? [{:allow #{"*"}} "ns1"] [nil :info]))) + (is (false? (sf-filter? [{:allow #{:any}} "ns1"] [nil :info]))) + (is (false? (sf-filter? [{:allow "*"} "ns1"] [nil :info]))) + (is (false? (sf-filter? [{:allow :any} "ns1"] [nil :info]))) + (is (true? (sf-filter? [{:allow #{}} "ns1"] [nil :info]))) + + (is (false? (sf-filter? [{:allow #{"a.*.c"}} "a.b3.c"] [nil :info]))) + (is (false? (sf-filter? [{:allow #{"a.*.c"}} "a.b1.b2.c"] [nil :info]))) + + (is (false? (sf-filter? [{:deny #{}} "ns1"] [nil :info]))) + (is (true? (sf-filter? [{:deny #{"*"}} "ns1"] [nil :info]))) + (is (true? (sf-filter? [{:deny #{:any}} "ns1"] [nil :info]))) + (is (true? (sf-filter? [{:deny "*"} "ns1"] [nil :info]))) + (is (true? (sf-filter? [{:deny :any} "ns1"] [nil :info]))) + + (is (true? (sf-filter? [{:allow :any :deny :any} "ns1"] [nil :info])) "Deny > allow") + + (is (true? (sf-filter? ["ns1" nil] [nil :info])) "ns spec without ns") + (is (->> (sf-filter? [nil nil] [:info nil]) (enc/throws? :common "Invalid level")) "level spec without level")]) + + (testing "Filter with ns-specific min-levels" + [(is (false? (sf-filter? [nil "ns1"] [[["*" :info] ] :info]))) + (is (true? (sf-filter? [nil "ns1"] [[["*" :info] ] :debug]))) + (is (false? (sf-filter? [nil "ns1"] [[["ns1" :info] ["ns1" :warn] ["*" :warn]] :info])) "Match sequentially") + (is (true? (sf-filter? [nil "ns1"] [[["ns1" :warn] ["ns1" :info] ["*" :warn]] :info])) "Match sequentially") + + (is (false? (sf-filter? [nil "ns.public.foo"] [[["ns.internal.*" :warn] ["ns.public.*" :info] ["*" :warn]] :info]))) + (is (true? (sf-filter? [nil "ns.internal.foo"] [[["ns.internal.*" :warn] ["ns.public.*" :info] ["*" :warn]] :info]))) + + (is (->> (sf-filter? [nil "ns1"] [[["*" :info]] nil]) (enc/throws? :common "Invalid level"))) + (is (->> (sf-filter? [nil nil] [[["*" :info]] nil]) (enc/throws? :common "Invalid level")))]) + + (testing "Filter basics [ns kind id level]" + [(is (nil? (sf-filter? [nil nil] [nil nil] [nil nil] [nil nil]))) + (is (false? (sf-filter? [nil nil] [:k1 :k1] [nil nil] [nil nil]))) + (is (true? (sf-filter? [nil nil] [:k1 :k2] [nil nil] [nil nil]))) + + (is (false? (sf-filter? [:ns1 :ns1] [:k1 :k1] [nil nil] [nil nil]))) + (is (true? (sf-filter? [:ns1 :ns2] [:k1 :k1] [nil nil] [nil nil]))) + + (is (false? (sf-filter? [:ns1 :ns1] [:k1 :k1] [:id1 :id1] [nil nil]))) + (is (true? (sf-filter? [:ns1 :ns1] [:k1 :k1] [:id1 :id2] [nil nil]))) + + (is (false? (sf-filter? [:ns1 :ns1] [:k1 :k1] [:id1 :id1] [{:k1 10} 10]))) + (is (true? (sf-filter? [:ns1 :ns1] [:k1 :k1] [:id1 :id1] [{:k1 20} 10]))) + + (is (false? (sf-filter? [:ns1 :ns1] [:k1 :k1] [:id1 :id1] [{:default 10} 10]))) + (is (true? (sf-filter? [:ns1 :ns1] [:k1 :k1] [:id1 :id1] [{:default 20} 10])))])]) ;;;;