diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e0ddb1e0..0facc68b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -11,6 +11,7 @@ Community - David Goeke [@dgoeke] - Dave Dixon [@sparkofreason] - Baptiste Fontaine [@bfontaine] +- Wanderson Ferreira [@bartuka] [@rbrush]: https://github.com/rbrush [@mrrodriguez]: https://github.com/mrrodriguez @@ -20,4 +21,5 @@ Community [@dgoeke]: https://github.com/dgoeke [@sparkofreason]: https://github.com/sparkofreason [@bfontaine]: https://github.com/bfontaine -[@sunilgunisetty]: https://github.com/sunilgunisetty \ No newline at end of file +[@sunilgunisetty]: https://github.com/sunilgunisetty +[@bartuka]: https://github.com/wandersoncferreira diff --git a/src/main/clojure/clara/rules.cljc b/src/main/clojure/clara/rules.cljc index c2f6e5c7..7e901649 100644 --- a/src/main/clojure/clara/rules.cljc +++ b/src/main/clojure/clara/rules.cljc @@ -338,6 +338,8 @@ information might prove useful for debugging compilation errors within the rulebase, eg. rulebase serialization (ie. via Clara's durability support). Defaults to true, see clara.rules.compiler/omit-compile-ctx-default for more information. + * :blacklist, a vector of qualified namespace rules that will be blacklisted from the created session. + Defaults to [], all the rules provided will be considered. This is not supported in ClojureScript, since it requires eval to dynamically build a session. ClojureScript users must use pre-defined rule sessions using defsession." @@ -425,4 +427,4 @@ See the [query authoring documentation](http://www.clara-rules.org/docs/queries/ (map first) ; Take the symbols for the rule/query vars )] (doseq [psym production-syms] - (ns-unmap *ns* psym)))))) \ No newline at end of file + (ns-unmap *ns* psym)))))) diff --git a/src/main/clojure/clara/rules/compiler.clj b/src/main/clojure/clara/rules/compiler.clj index 8d9699cd..b925637c 100644 --- a/src/main/clojure/clara/rules/compiler.clj +++ b/src/main/clojure/clara/rules/compiler.clj @@ -2036,6 +2036,16 @@ (vary-meta production assoc ::rule-load-order (or n 0))) (range) productions)) + +(defn remove-blacklisted-rules + "If an option :blacklist was provided by the user, we filter out the production names indicated." + [options productions] + (let [blacklist (set (map str (:blacklist options))) + not-contains? (complement contains?)] + (if (empty? blacklist) + productions + (filter #(not-contains? blacklist (:name %)) productions)))) + (defn mk-session "Creates a new session using the given rule source. The resulting session is immutable, and can be used with insert, retract, fire-rules, and query functions." @@ -2047,6 +2057,7 @@ (mapcat #(if (satisfies? IRuleSource %) (load-rules %) %)) + (remove-blacklisted-rules options) add-production-load-order ;; Ensure that we choose the earliest occurrence of a rule for the purpose of rule order. ;; There are Clojure core functions for distinctness, of course, but none of them seem to guarantee diff --git a/src/test/common/clara/test_blacklist.cljc b/src/test/common/clara/test_blacklist.cljc new file mode 100644 index 00000000..f6abf9e9 --- /dev/null +++ b/src/test/common/clara/test_blacklist.cljc @@ -0,0 +1,107 @@ +#?(:clj + (ns clara.test-blacklist + (:require [clara.tools.testing-utils :refer [def-rules-test] :as tu] + [clara.rules :refer [fire-rules + insert + insert! + query]] + + [clara.rules.testfacts :refer [->Temperature + ->Cold + ->Hot + ->TemperatureHistory + ->ColdAndWindy]] + [clojure.test :refer [is]] + [clara.rules.accumulators]) + (:import [clara.rules.testfacts + Temperature + Cold + Hot + ColdAndWindy + TemperatureHistory])) + + :cljs + (ns clara.test-blacklist + (:require [clara.rules :refer [fire-rules + insert + insert! + query]] + [clara.rules.testfacts :refer [->Temperature Temperature + ->Cold Cold + ->Hot Hot + ->ColdAndWindy ColdAndWindy + ->TemperatureHistory TemperatureHistory]] + [clara.rules.accumulators] + [cljs.test]) + (:require-macros [clara.tools.testing-utils :refer [def-rules-test]] + [cljs.test :refer [is]]))) + +(def-rules-test test-blacklist-a-rule + {:rules [cold-rule [[[Temperature (< temperature 20) (= ?t temperature)]] + (insert! (->Cold ?t))] + history-rule [[[Temperature (< temperature 20) (= ?t temperature)]] + (insert! (->TemperatureHistory [?t]))]] + + :queries [cold-query [[] [[Cold (= ?c temperature)]]] + history-query [[] [[TemperatureHistory (= ?t temperatures)]]]] + + :sessions [empty-session [cold-rule cold-query history-rule history-query] {:blacklist ['cold-rule]}]} + + (let [session (-> empty-session + (insert (->Temperature 10 "MCI")) + (fire-rules))] + + (is (empty? (query session cold-query))) ;removed rule + + (is (= #{{:?t [10]}} + (set (query session history-query)))))) + +(def-rules-test test-blacklist-many-rules + {:rules [cold-rule [[[Temperature (< temperature 20) (= ?t temperature)]] + (insert! (->Cold ?t))] + history-rule [[[Temperature (< temperature 20) (= ?t temperature)]] + (insert! (->TemperatureHistory [?t]))] + hot-rule [[[Temperature (> temperature 50) (= ?t temperature)]] + (insert! (->Hot ?t))] + cold-and-windy-rule [[[Cold (> temperature 30) (= ?t temperature)]] + (insert! (->ColdAndWindy ?t 60))]] + + :queries [cold-query [[] [[Cold (= ?c temperature)]]] + history-query [[] [[TemperatureHistory (= ?ht temperatures)]]] + hot-query [[] [[Hot (= ?h temperature)]]] + cold-windy-query [[] [[ColdAndWindy (= ?cw temperature)]]]] + + :sessions [empty-session [cold-rule cold-query + history-rule history-query + hot-rule hot-query + cold-and-windy-rule cold-windy-query] {:blacklist ['history-rule + 'hot-rule + 'cold-and-windy-rule]}]} + + (let [session (-> empty-session + (insert (->Temperature 10 "MCI")) + (insert (->Cold 40)) + (insert (->Temperature 70 "MCI")) + (fire-rules))] + + (is (empty? (query session history-query))) + (is (empty? (query session hot-query))) + (is (empty? (query session cold-windy-query))) + + (is (= #{{:?c 10} {:?c 40}} + (set (query session cold-query)))))) + + +(def-rules-test test-blacklist-a-query + {:rules [cold-rule [[[Temperature (< temperature 20) (= ?t temperature)]] + (insert! (->Cold ?t))]] + + :queries [cold-query [[] [[Cold (= ?c temperature)]]]] + + :sessions [empty-session [cold-rule cold-query] {:blacklist ['cold-query]}]} + + (let [session (-> empty-session + (insert (->Temperature 10 "MCI")) + (fire-rules))] + (is (thrown? java.lang.IllegalArgumentException + (query session cold-query)))))