001 (ns cmp.exchange
002 ^{:author "wactbprot"
003 :doc "Handles the access to the exchange interface."}
004 (:require [com.brunobonacci.mulog :as mu]
005 [clojure.string :as string]
006 [cmp.st-mem :as st]
007 [cmp.st-utils :as stu]
008 [cmp.utils :as u]))
009
010 (defn exch-key
011 "Returns the base key for the exchange path.
012
013 ```clojure
014 (exch-key \"foo\" \"bar.baz\")
015 ;; \"foo@exchange@bar\"
016 (exch-key \"foo\" \"bar\")
017 ;; \"foo@exchange@bar\"
018 ```
019 "
020 [mp-id s]
021 {:pre [(not (nil? s))]}
022 (stu/exch-key mp-id (first (string/split s #"\."))))
023
024 (defn key->second-kw
025 "Returns the keyword or nil.
026
027 ```clojure
028 (key->second-kw \"foo\" )
029 ;; nil
030 (key->second-kw \"foo.bar\" )
031 ;; :bar
032 ```"
033 [s]
034 (when-let [x (second (string/split s #"\."))] (keyword x)))
035
036 (defn key->first-kw
037 "Returns the keyword or nil.
038
039 ```clojure
040 (key->second-kw \"foo\" )
041 ;; nil
042 (key->second-kw \"foo.bar\" )
043 ;; :bar
044 ```"
045 [s]
046 (when-let [x (first (string/split s #"\."))] (keyword x)))
047
048 (defn read!
049 "Returns e.g the *compare value* belonging to a `mp-id` and an
050 ExchangePath `k`. First try is to simply request to
051 `<mp-id>@exchange@<k>`. If this is `nil` Second try is to get the
052 *keyword* `kw` from `k` if `k` looks like this: `aaa.bbb`. If `kw`
053 is not `nil` it is used to extract the related value.
054
055 ```clojure
056 (read! \"ref\" \"A.Unit\")
057 ;; \"Pa\"
058 ;; or:
059 (read! \"devhub\" \"Vraw_block1\")
060 ;; [1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]
061 ```"
062 [mp-id p]
063 (if-let [val-p (st/key->val (stu/exch-key mp-id p))]
064 val-p
065 (let [val-k (st/key->val (exch-key mp-id p))]
066 (if-let [kw (key->second-kw p)]
067 (kw val-k)
068 val-k))))
069
070 (defn from!
071 "Builds a map by replacing the values of the input map `m`.
072 The replacements are gathered from the `exchange` interface with the
073 keys: `<mp-id>@exchange@<input-map-value>`
074
075 The example key: `ref@exchange@Vraw_block1` with the example value:
076 `[1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]` should return:
077 `{:%stateblock1 [1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]}`
078
079
080 ```clojure
081 (from! \"ref\" {:%stateblock1 \"Vraw_block1\"})
082 ;; =>
083 ;; {:%stateblock1 [1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0]}
084 ```
085
086 **Todo**
087
088 Check for non trivial `<input-map-value>` like
089 `{:%aaa \"bbb.ccc\"}`
090 "
091 [mp-id m]
092 (when (and (string? mp-id) (map? m))
093 (u/apply-to-map-values
094 (fn [v] (read! mp-id v))
095 m)))
096
097 (defn enclose-map
098 "Encloses the given map `m` with respect to the path `p`.
099
100 Example:
101 ```clojure
102 (enclose-map {:gg \"ff\"} \"mm.ll\")
103 ;; gives:
104 ;; {:mm {:ll {:gg \"ff\"}}}
105
106 (enclose-map {:gg \"ff\"} \"mm\")
107 ;; gives:
108 ;; {:mm {:gg \"ff\"}}
109
110 (enclose-map {:gg \"ff\"} nil)
111 ;; gives:
112 ;; {:gg \"ff\"}
113 ```"
114 [m p]
115 (if-not p
116 m
117 (let [a (key->first-kw p)]
118 (if-let [b (key->second-kw p)]
119 {a {b m}}
120 {a m}))))
121
122 (defn to!
123 "Writes `m` to the exchange interface. The first level keys of `m`
124 are used for the key. The return value of the storing
125 process (e.g. \"OK\") is converted to a `keyword`. After storing the
126 amounts of `:OK` is compared to `(count m)`.
127
128 Example:
129 ```clojure
130 {:A 1
131 :B 2}
132 ```
133 Stores the value `1` under the key
134 `<mp-id>@exchange@A` and a `2` under
135 `<mp-id>@exchange@B`.
136
137 If a path `p` is given the `enclose-map` function
138 respects `p`.
139 "
140 ([mp-id m p]
141 (to! mp-id (enclose-map m p)))
142 ([mp-id m]
143 (if (string? mp-id)
144 (if (map? m)
145 (let [res (map (fn [[k new-val]]
146 (let [exch-key (stu/exch-key mp-id (name k))
147 curr-val (st/key->val exch-key)
148 both-map? (and (map? new-val) (map? curr-val))]
149 (st/set-val! exch-key (if both-map? (merge curr-val new-val) new-val))))
150 m)
151 res-kw (map keyword res)
152 res-ok? (= (count m) (:OK (frequencies res-kw)))]
153 (if res-ok? {:ok true} {:error "not all write processes succeed"}))
154 {:error "second arg mus be a map"})
155 {:error "mp-id must be a string"})))
156
157 (defn ok?
158 "Checks a certain exchange endpoint to evaluate
159 to true"
160 [mp-id k]
161 (contains? u/ok-set (read! mp-id k)))
162
163 (defn exists? [mp-id k] (some? (read! mp-id k)))
164
165 (defn stop-if
166 "Checks if the exchange path given with `:MpName` and `:StopIf`
167 evaluates to true."
168 [{mp-id :MpName k :StopIf}]
169 (if k
170 (ok? mp-id k)
171 true))
172
173 (defn run-if
174 "Checks if the exchange path given with `:MpName` and `:RunIf`
175 evaluates to true."
176 [{mp-id :MpName k :RunIf}]
177 (if k
178 (ok? mp-id k)
179 true))
180
181 (defn only-if-not
182 "Runs the task `only-if-not` the exchange path given with `:MpName`
183 and `:OnlyIfNot` evaluates to true."
184 [{mp-id :MpName k :OnlyIfNot}]
185 (cond
186 (nil? k) true
187 (not (exists? mp-id k)) false
188 (not (ok? mp-id k)) true))