Add backoff when unable to find broker. Imp perf

--- src/onyx/plugin/kafka.clj
+++ src/onyx/plugin/kafka.clj
@@ -39,7 +39,8 @@
 (def defaults
   {:kafka/receive-buffer-bytes 65536
    :kafka/commit-interval 2000
-   :kafka/wrap-with-metadata? false})
+   :kafka/wrap-with-metadata? false
+   :kafka/unable-to-find-broker-backoff-ms 8000})

 (defn seek-offset! [log-prefix consumer kpartition task-map topic checkpoint]
   (let [policy (:kafka/offset-reset task-map)
@@ -86,12 +87,17 @@
      (k-cluster/all-brokers zk-utils))))

 (defn find-brokers [zk-addr]
-  (let [results (vals (id->broker zk-addr))]
+  (let [zk-addr (:kafka/zookeeper task-map)
+        results (vals (id->broker zk-addr))]
     (if (seq results)
       results
-      (throw (ex-info "Could not locate any Kafka brokers to connect to."
-                      {:recoverable? true
-                       :zk-addr zk-addr})))))
+      (do
+       (info "Could not locate any Kafka brokers to connect to. Backing off.")
+       (Thread/sleep (or (:kafka/unable-to-find-broker-backoff-ms task-map)
+                         (:kafka/unable-to-find-broker-backoff-ms defaults)))
+       (throw (ex-info "Could not locate any Kafka brokers to connect to."
+                       {:recoverable? true
+                        :zk-addr zk-addr}))))))

 (defn check-num-peers-equals-partitions
   [{:keys [onyx/min-peers onyx/max-peers onyx/n-peers kafka/partition] :as task-map} n-partitions]
@@ -123,7 +129,9 @@
   p/Plugin
   (start [this event]
     (let [{:keys [kafka/group-id kafka/consumer-opts]} task-map
-          brokers (find-brokers (:kafka/zookeeper task-map))
+          brokers (find-brokers task-map)
+
+
           _ (s/validate onyx.tasks.kafka/KafkaInputTaskMap task-map)
           consumer-config (merge {:bootstrap.servers (find-brokers (:kafka/zookeeper task-map))
                                   :group.id group-id
@@ -171,7 +179,7 @@

   (next-state [this _]
     (if (and iter (.hasNext ^java.util.Iterator iter))
-      (let [rec (.next ^java.util.Iterator iter)
+      (let [rec ^ConsumerRecord (.next ^java.util.Iterator iter)
             new-offset (if rec
                          (.offset rec)
                          offset)]
@@ -258,12 +266,13 @@
     [true this])

   (write-batch [this {:keys [onyx.core/results]} replica _]
-    (let [messages (mapcat :leaves (:tree results))]
-      (->> messages
-           (map (fn [msg]
-                  (->> (:message msg)
-                       (message->producer-record serializer-fn topic)
-                       (send-async! producer))))
+    (let [xf (comp (mapcat :leaves)
+                   (map (fn [msg]
+                          (->> (:message msg)
+                               (message->producer-record serializer-fn topic)
+                               (send-async! producer)))))]
+      (->> (:tree results)
+           (sequence xf)
            (doall)
            ;; could perform the deref in synchonized? to block less often
            (run! deref))


graphical repl working

graphical repl working

--- src/overtone/gui/repl.clj
+++ src/overtone/gui/repl.clj
@@ -11,7 +11,9 @@
   (:use clojure.contrib.seq-utils)
   (:use [clojure.stacktrace :only (e)])

-  (:use overtone.core.event))
+  (:use overtone.core.event)
+
+  (:use clj-scenegraph.core))

 (defn outputstream-with-cb [cb]
   (let [buffer (StringBuffer.)]
@@ -35,36 +37,38 @@

 (comment
   (def temp (ref ""))
-  (capture-out-write #(dosync (ref-set temp (str "from callback: " %))) (println "hiiii"))
+  (capture-out-write #(dosync (ref-set temp (str "from callback: " %))) (println "capture me please"))
   (println @temp))

-(defn start-repl-thread [] (future (try
-                                 (capture-out-write #(event ::repl-print :text %)
-                                                    (let [read-q (LinkedBlockingQueue.)
-                                                          exit (ref false)]
-
-                                                      (on ::repl-write #(.put read-q (PushbackReader. (StringReader. (:text %)))))
-                                                      (on ::repl-exit #((dosync (ref-set exit true))
-                                                                        (Thread/sleep 300)
-                                                                        (event ::repl-write :text "(println \"exit hack\")")))
-
-                                                      (clojure.main/repl
-                                                       :init (fn [] (event ::repl-ready))
-                                                       :caught (fn [x]
-                                                                 (binding [*e x] (e))
-                                                                 (.flush *out*)
-                                                                 (println ""))
-                                                       :need-prompt (constantly true)
-                                                       :prompt #(do (event ::repl-prompt :text (str (ns-name *ns*))))
-                                                       :read (fn [a b]
-                                                               (if @exit
-                                                                 b
-                                                                 (binding [*in* (.take read-q)]
-                                                                   (r/repl-read a b))))))
-
-                                                    (event ::repl-exited))
-                                 (catch Exception e
-                                   (event ::repl-exception :text (pr-str e))))))
+(defn start-repl-thread []
+  (future (try
+           (capture-out-write
+            #(event ::repl-print :text %)
+            (let [read-q (LinkedBlockingQueue.)
+                  exit (ref false)]
+
+              (on ::repl-write #(.put read-q (PushbackReader. (StringReader. (:text %)))))
+              (on ::repl-exit #((dosync (ref-set exit true))
+                                (Thread/sleep 300)
+                                (event ::repl-write :text "(println \"exit hack\")")))
+
+              (clojure.main/repl
+               :init (fn [] (event ::repl-ready))
+               :caught (fn [x]
+                         (binding [*e x] (e))
+                         (.flush *out*)
+                         (println ""))
+               :need-prompt (constantly true)
+               :prompt #(do (event ::repl-prompt :text (str (ns-name *ns*))))
+               :read (fn [a b]
+                       (if @exit
+                         b
+                         (binding [*in* (.take read-q)]
+                           (r/repl-read a b))))))
+
+            (event ::repl-exited))
+           (catch Exception e
+             (event ::repl-exception :text (pr-str e))))))

 (comment

@@ -81,5 +85,63 @@

   (event ::repl-write :text "(println \"hello world\")"))

+(import '(com.sun.scenario.scenegraph SGComponent))
+
+(defn sg-component
+  ([] (SGComponent.))
+  ([comp]
+     (doto (sg-component)
+       (.setComponent comp)))
+  ([comp w h]
+     (doto (sg-component comp)
+       (.setSize w h))))
+
+(def #^{:doc "balanced pairs"}
+     pairs '((\( \))
+             (\[ \])
+             (\" \")
+             (\{ \})))
+
+(defn balanced?
+  "are all the pairs balanced in this string?"
+  [string]
+  ((comp not some)
+   false?
+   (map
+    (fn [pair] (-> pair set (filter string) count (mod 2) zero?))
+    pairs)))
+
+
 (defn repl []
-  )+  (let [group (sg-group)
+        history (javax.swing.JEditorPane.)
+        input (javax.swing.JEditorPane.)]
+
+    (doto history
+      (.setEditable false))
+
+    (doto group
+      (add! (sg-component history 400 100))
+      (add! (translate 0 105 (sg-component input 400 25))))
+
+    (doto (.getKeymap input)
+      (.addActionForKeyStroke
+       (javax.swing.KeyStroke/getKeyStroke (java.awt.event.KeyEvent/VK_ENTER) 0)
+       (proxy [javax.swing.AbstractAction] [] (actionPerformed [evt]  (let [text (.getText input)]
+                                                                        (if (and (not (empty? text))
+                                                                                (balanced? text))
+                                                                          (do (event ::repl-write :text text)
+                                                                              (.setText input "")
+                                                                              (.requestFocus input))))))))
+
+    (on ::repl-print #( (let [ doc (.getDocument history)
+                              length (.getLength doc)]
+                          (.insertString doc length (:text %) nil))))
+
+    group))
+
+(comment
+  (start-repl-thread)
+  (def panel (sg-panel 640 480))
+  (sg-window panel)
+  (set-scene! panel (translate 30 30 (repl))))


idify synth args. Fixes #255.

idify synth args. Fixes #255.

--- src/overtone/sc/synth.clj
+++ src/overtone/sc/synth.clj
@@ -233,7 +233,7 @@
                                    :rate  DEFAULT-RATE} p-val)

             :else {:name (str p-name)
-                   :default `(float ~p-val)
+                   :default `(float (to-id ~p-val))
                    :rate DEFAULT-RATE})]
       (ensure-param-keys! param-map)
       param-map)))


More setup for editing

More setup for editing

--- src/pianola/notation.cljs
+++ src/pianola/notation.cljs
@@ -44,15 +44,52 @@
          (> y (.-y box))
          (< y (+ (.-y box) (.-h box))))))

-(defn handle-click [notes e]
-  (let [target (.-currentTarget e)
-        x (- (.-pageX e) (.-offsetLeft target))
-        y (- (.-pageY e) (.-offsetTop target))]
-    (println (first (drop-while #(not (within-note? x y %)) notes)))))
+(defn target-pos [e]
+  (let [target (.-currentTarget e)]
+    [(- (.-pageX e) (.-offsetLeft target))
+     (- (.-pageY e) (.-offsetTop target))]))
+
+(defn partition-edit [notes e]
+  (let [[x y] (target-pos e)
+        [head tail] (split-with #(not (within-note? x y %)) notes)
+        editing (first tail)
+        taking (second tail)
+        tail (drop 2 tail)]
+    [head editing taking tail]))
+
+(defn stretch [x editing taking]
+  (println "stretching" x)
+  [editing taking])
+
+(defn shift [y editing]
+  (println "shifting" y)
+  editing)
+
+(defn shift-or-stretch [dx dy x y editing taking]
+  (if (> dx dy)
+    (stretch x editing taking)
+    [(shift y editing) taking]))
+
+(defn abs [n] (max n (- n)))
+
+(defn edit-selected [{:keys [start partition]} [x y]]
+  (let [[head editing taking tail] partition
+        [start-x start-y] start
+        dx (abs (- x start-x))
+        dy (abs (- y start-y))
+        [editing taking] (shift-or-stretch dx dy x y editing taking)]
+    (apply flatten head editing taking tail)))

 (defn notation [phrase]
   (let [locals (r/atom {:context nil
-                        :notes []})]
+                        :notes []
+                        :editing nil})
+        start-edit #(swap! locals assoc :editing {:start (target-pos %)
+                                                  :partition (partition-edit (:notes @locals) %)})
+        edit (fn [e]
+               (if-let [editing (:editing @locals)]
+                 (swap! locals assoc :notes (edit-selected editing (target-pos e)))))
+        end-edit #(swap! locals assoc :editing nil)]
     (r/create-class
      {:display-name "notation"
       :component-did-mount
@@ -67,4 +104,6 @@
           (swap! locals assoc :notes (render-phrase context (:music (r/props this))))))
       :reagent-render
       (fn [phrase]
-        [:div {:onClick #(handle-click (:notes @locals) %)}])})))
+        [:div {:onMouseDown start-edit
+               :onMouseUp end-edit
+               :onMouseMove edit}])})))


remove schedules list

remove schedules list

--- src/cljs/early_vote_site/election_detail/views.cljs
+++ src/cljs/early_vote_site/election_detail/views.cljs
@@ -26,5 +26,4 @@
     [:div
      [:button.button {:on-click #(re-frame/dispatch [:election-detail/go-back])} "go back to all elections"]
      [election-details]
-     [evs.list/early-vote-sites-list]
-     schedules]))
+     [evs.list/early-vote-sites-list]]))


Added `:transform-header` option to mappify.

--- src/semantic_csv/core.clj
+++ src/semantic_csv/core.clj
@@ -67,19 +67,26 @@
   "Takes a sequence of row vectors, as commonly produced by csv parsing libraries, and returns a sequence of
   maps. By default, the first row vector will be interpreted as a header, and used as the keys for the maps.
   However, this and other behaviour are customizable via an optional `opts` map with the following options:
-
+
   * `:keyify` - bool; specify whether header/column names should be turned into keywords (default: `true`).
+    If `:transform-header` is present, this option will be ignored.
+  * `:transform-header` - A function that transforms the header/column names for each column.
+    This takes precedence over `keyify` and should be a function that takes a string.
   * `:header` - specify the header to use for map keys, preventing first row of data from being consumed as header.
   * `:structs` - bool; use structs instead of hash-maps or array-maps, for performance boost (default: `false`)."
   ([rows]
    (mappify {} rows))
-  ([{:keys [keyify header structs] :or {keyify true} :as opts}
+  ([{:keys [keyify transform-header header structs] :or {keyify true} :as opts}
     rows]
    (let [consume-header (not header)
          header (if header
                   header
                   (first rows))
-         header (if keyify (mapv keyword header) header)
+         header (if transform-header
+                  (mapv transform-header header)
+                  (if keyify
+                    (mapv keyword header)
+                    header))
          map-fn (if structs
                   (let [s (apply create-struct header)]
                     (partial apply struct s))
@@ -331,6 +338,24 @@
 ;;                :cast-fns {:this #(Integer/parseInt %)})

+;;

+;; # A Helper function to use with mappify to replace spaces in headers.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; ## replace-space
+
+(defn replace-space
+  "Takes a string that is used to replace spaces and a bool, that when true, will make a keyword out of the header.
+  When no bool is passed, true is used, and headers will be made into keywords.
+  Returns a function, accepting a single parameter."
+  ([replace-string]
+   (replace-space replace-string true))
+  ([replace-string keyify]
+   (fn [r-string]
+     (let [header (clojure.string/replace r-string \space replace-string)]
+       (if keyify
+         (keyword header)
+         header)))))

 ;;

 ;; # Some casting functions for your convenience


reuse the delete request

reuse the delete request

--- test/bzzz/core_handler_test.clj
+++ test/bzzz/core_handler_test.clj
@@ -13,18 +13,18 @@
 (def id default-identifier)
 (def hosts [id id id [id id [id id id id id id id id id [id] [id id [id]]]]])

+(def query {:term {:field "name"
+                   :value "doe"}})
+
 (def store-request
   {:index test-index-name
    :documents [{:name "jack doe"}
                {:name "john doe"}]
    :facets {:name {}}})

-(def query {:term {:field "name"
-                   :value "doe"}})
 (def delete-request
   {:index test-index-name
    :query query})
-

 (def put-request
   {:index test-index-name
@@ -32,6 +32,10 @@
    :query query
    :facets {:name {:size 7}}})

+(defn send-delete-request []
+  (http-client/delete host {:accept :json
+                            :as :json
+                            :body (json/write-str delete-request)}))
 (defonce server
   (run-jetty handler {:port 3000
                       :join? false}))
@@ -44,9 +48,7 @@
     (discover))

   (testing "delete"
-    (is (= (:body (http-client/delete host {:accept :json
-                                            :as :json
-                                            :body (json/write-str delete-request)}))
+    (is (= (:body (send-delete-request))
            {test-index-name "name:doe"})))

   (testing "store"
@@ -63,10 +65,8 @@
       (is (= (* 2 (count (flatten hosts))) (:total r)))))

   (testing "delete-and-should-be-zero"
-    (is (= (:body (http-client/delete host {:accept :json
-                                            :as :json
-                                            :body (json/write-str delete-request)}))
-           {test-index-name "name:doe"}))
+    (is (= (:body (send-delete-request)
+                  {test-index-name "name:doe"})))
     (refresh-search-managers)
     (let [r (:body (http-client/put host {:accept :json
                                           :as :json


Exercise 2.20, 2.23

Exercise 2.20, 2.23

--- src/sicp/chapter2/2_1.clj
+++ src/sicp/chapter2/2_1.clj
@@ -51,7 +51,18 @@
 ;; TODO

 ;; Exercise 2.20
-;; TODO
+(defn same-parity [x & xs]
+  (letfn [(same? [n]
+            (= (mod x 2) (mod n 2)))]
+    (cons x (filter same? xs))))
+
+(defn my-filter [f coll]
+  (lazy-seq
+   (when-let [s (seq coll)]
+     (if (f (first s))
+       (cons (first s)
+             (my-filter f (rest s)))
+       (my-filter f (rest s))))))

 ;; Exercise 2.21
 ;; TODO
@@ -60,4 +71,8 @@
 ;; TODO

 ;; Exercise 2.23
-;; TODO
+(defn for-each [f coll]
+  (if (empty? coll)
+    nil
+    (do (f (first coll))
+        (recur f (rest coll)))))


fix Exercise 2.28

fix Exercise 2.28

--- src/sicp/chapter2/2_2.clj
+++ src/sicp/chapter2/2_2.clj
@@ -45,11 +45,13 @@

 ;; Exercise 2.28
 (defn fringe [tree]
-  (cond
-    (not (seq? tree)) [tree]
-    (empty? tree) []
-    :else (concat (fringe (first tree))
-                  (fringe (fnext tree)))))
+  (letfn [(fri [coll acc]
+            (cond
+              (not (seq? coll)) (cons coll acc)
+              (empty? coll) acc
+              :else (recur (rest coll)
+                           (fri (first coll) acc))))]
+    (reverse (fri tree []))))

 (def x'' (list (list 1 2) (list 3 4)))
 (fringe x'')


Generating random starting strings

Generating random starting strings

--- src/staplo/generator.cljs
+++ src/staplo/generator.cljs
@@ -1,6 +1,13 @@
 (ns staplo.generator
   (:require
     [staplo.operations :as operations]))
+
+(defn generate-string [length chars]
+  (print chars)
+  (reduce
+    #(str % (rand-nth chars))
+    ""
+    (range 0 length)))

 (defn generate-ops [start-text steps operations]
   (letfn [
@@ -14,7 +21,9 @@
       (range 0 steps))))

 (defn generate-challenge [config]
-  (let [start "abc"
+  (let [char-sets [["a" "b"] ["a" "b" "c"]]
+        char-set (rand-nth char-sets)
+        start (generate-string 4 char-set)
         target (generate-ops start 3 (:operations config))]
     {:start start
      :target target}))


Even though the current and only test set is bogus, correct core.clj test so I can add to it later.

--- test/htmltohiccup/test/core.clj
+++ test/htmltohiccup/test/core.clj
@@ -1,7 +1,7 @@
 (ns htmltohiccup.test.handler
   (:use clojure.test
         ring.mock.request
-        html2hiccup.handler))
+        htmltohiccup.core))

 (deftest test-app
   (testing "main route"


a whole new world

a whole new world

--- ros/angelic_planning/src/ros/robot_actions.clj
+++ ros/angelic_planning/src/ros/robot_actions.clj
@@ -229,7 +229,7 @@
 	       (move-arm-to-state nh (arm-joint-state true "tucked"))
 	       true))
 	(recur (if (isa? (:class (nth actions i)) ::GripperAction) (dec i) i))
-	(recur (inc i))))))
+	(recur (inc i)))))

 (defn execute-robot-plan-hr  [#^NodeHandle nh actions]


Fixing tests

Fixing tests

- all-entities API function do not grant entities order

--- test/larva/program_api_test.clj
+++ test/larva/program_api_test.clj
@@ -12,8 +12,10 @@
     (is (= 0 (-> {:model no-entities-no-about-edge-case}
                  pa/all-entities
                  count)))
-    (is (= ["Musician" "Band" "Festival"] (-> {:model standard-program-1}
-                                              pa/all-entities)))
-    (is (= ["Musician" "Band" "Category" "Festival" "SocialMediaProfile"]
+    (is (= #{"Musician" "Band" "Festival"} (-> {:model standard-program-1}
+                                               pa/all-entities
+                                               set)))
+    (is (= #{"Musician" "Band" "Category" "Festival" "SocialMediaProfile"}
            (-> {:model references-1}
-               pa/all-entities)))))
+               pa/all-entities
+               set)))))


everything gets mostly proxied through nginx

--- src/terraboot/cluster.clj
+++ src/terraboot/cluster.clj
@@ -365,14 +365,11 @@
                    :lifecycle {:create_before_destroy true}
                    :elb [{:name "internal-lb"
                           :listeners [(elb-listener {:port 80 :protocol "HTTP"})
-                                      (elb-listener {:port 5050 :protocol "HTTP"})
                                       (elb-listener {:port 2181 :protocol "TCP"})
-                                      (elb-listener {:port 8181 :protocol "HTTP"})
-                                      (elb-listener {:port 8080 :protocol "HTTP"})
                                       (elb-listener {:port 53 :protocol "TCP"})]
                           :health_check {:healthy_threshold 2
                                          :unhealthy_threshold 3
-                                         :target "HTTP:8181/exhibitor/v1/cluster/status"
+                                         :target "HTTP:80/exhibitor/exhibitor/v1/cluster/status"
                                          :timeout 5
                                          :interval 30}
                           :subnets elb-subnets


🔊 Improve logging of exceptions from API commands

Why:
- clojure.tools.logging/error does not log ex-data, so it does not
  log the event which failed validation.

--- src/territory_bro/api.clj
+++ src/territory_bro/api.clj
@@ -189,7 +189,11 @@
         (log/warn e "Forbidden command:" command)
         (forbidden {:message "Forbidden"}))
       (catch Throwable t
-        (log/error t "Command failed:" command)
+        ;; XXX: clojure.tools.logging/error does not log the ex-data by default https://clojure.atlassian.net/browse/TLOG-17
+        (log/error t (str "Command failed: "
+                          (pr-str command)
+                          "\n"
+                          (pr-str t)))
         (internal-server-error {:message "Internal Server Error"})))))

 (defn add-user [request]


FIx pay logic.

FIx pay logic.

--- src/ombs/handler/eventacts.clj
+++ src/ombs/handler/eventacts.clj
@@ -19,14 +19,13 @@
     (if (isvalid/ids? eid uid)
       (dbpay/create-fee uid eid parts))))

-(defn participate [{eid :eid ename :event-name date :date}]
+(defn participate [{eid :eid ename :event-name date :date :as params}]
   "Add participation of current user and selected event(given as param from
   post)"
-  (let [uname (sess/get :username)]
-    (when (isvalid/participation? ename date uname)
-      (core/add-participant (db/get-eid ename date) (db/get-uid uname)))))
+  (if-let [uname (sess/get :username)]
+    (when (isvalid/participation? eid)
+      (core/add-participant eid (db/get-uid uname)))))

-(defn start [{eid :eid ename :event-name date :date}]
-  ;isvalid: not started, exists.
+(defn start [{eid :eid ename :event-name date :date :as params}]
   (if (db/is-initial? eid)
     (core/start-event eid)))


Add mult.

Add mult.

--- src/onyx/coordinator/async.clj
+++ src/onyx/coordinator/async.clj
@@ -1,24 +1,54 @@
 (ns onyx.coordinator.async
-  (:require [clojure.core.async :refer [chan thread timeout >!! <!!]]
+  (:require [clojure.core.async :refer [chan thread mult tap timeout >!! <!!]]
             [onyx.coordinator.extensions :as extensions]
             [onyx.coordinator.log.datomic]
             [onyx.coordinator.sync.zookeeper]))

 (def eviction-delay 5000)

-(def planning-ch (chan 1000))
+(def ch-capacity 1000)

-(def born-peer-ch (chan 1000))
+(def planning-ch-head (chan ch-capacity))

-(def dead-peer-ch (chan 1000))
+(def born-peer-ch-head (chan ch-capacity))

-(def evict-ch (chan 1000))
+(def dead-peer-ch-head (chan ch-capacity))

-(def offer-ch (chan 1000))
+(def evict-ch-head (chan ch-capacity))

-(def ack-ch (chan 1000))
+(def offer-ch-head (chan ch-capacity))

-(def completion-ch (chan 1000))
+(def ack-ch-head (chan ch-capacity))
+
+(def completion-ch-head (chan ch-capacity))
+
+(def planning-ch-tail (chan ch-capacity))
+
+(def born-peer-ch-tail (chan ch-capacity))
+
+(def dead-peer-ch-tail (chan ch-capacity))
+
+(def evict-ch-tail (chan ch-capacity))
+
+(def offer-ch-tail (chan ch-capacity))
+
+(def ack-ch-tail (chan ch-capacity))
+
+(def completion-ch-tail (chan ch-capacity))
+
+(def planning-mult (mult planning-ch-head))
+
+(def born-peer-mult (mult born-peer-ch-head))
+
+(def dead-peer-mult (mult dead-peer-ch-head))
+
+(def evict-mult (mult evict-ch-head))
+
+(def offer-mult (mult offer-ch-head))
+
+(def ack-mult (mult ack-ch-head))
+
+(def completion-mult (mult completion-ch-head))

 (defn mark-peer-birth [log sync peer death-cb]
   (extensions/on-change sync death-cb)
@@ -54,58 +84,66 @@

 (defn born-peer-ch-loop [log sync]
   (loop []
-    (let [place (<!! born-peer-ch)]
-      (mark-peer-birth log sync place #(>!! dead-peer-ch %))
-      (>!! offer-ch place)
+    (let [place (<!! born-peer-ch-head)]
+      (mark-peer-birth log sync place #(>!! dead-peer-ch-head %))
+      (>!! offer-ch-head place)
       (recur))))

 (defn dead-peer-ch-loop [log]
   (loop []
-    (let [peer (<!! dead-peer-ch)]
+    (let [peer (<!! dead-peer-ch-head)]
       (mark-peer-death log peer)
-      (>!! evict-ch peer)
+      (>!! evict-ch-head peer)
       (recur))))

 (defn planning-ch-loop [log]
   (loop []
-    (let [job (<!! planning-ch)]
+    (let [job (<!! planning-ch-head)]
       (plan-job log job)
-      (<!! offer-ch job)
+      (<!! offer-ch-head job)
       (recur))))

 (defn ack-ch-loop [log]
   (loop []
-    (let [task (<!! ack-ch)]
+    (let [task (<!! ack-ch-head)]
       (acknowledge-task log task)
       (recur))))

 (defn evict-ch-loop [log sync]
   (loop []
-    (let [task (<!! evict-ch)]
+    (let [task (<!! evict-ch-head)]
       (evict-task log sync task)
-      (>!! offer-ch task)
+      (>!! offer-ch-head task)
       (recur))))

 (defn offer-ch-loop [log sync]
   (loop []
-    (let [event (<!! offer-ch)]
+    (let [event (<!! offer-ch-head)]
       (when (offer-task log sync
-                        #(>!! ack-ch %)
-                        #(>!! completion-ch %))
+                        #(>!! ack-ch-head %)
+                        #(>!! completion-ch-head %))
         (thread (<!! (timeout eviction-delay))
-                (>!! evict-ch nil)))
+                (>!! evict-ch-head nil)))
       (recur))))

 (defn completion-ch-loop [log sync queue]
   (loop []
-    (let [task (<!! completion-ch)]
+    (let [task (<!! completion-ch-head)]
       (complete-task log sync queue task)
-      (>!! offer-ch task)
+      (>!! offer-ch-head task)
       (recur))))

 (def start-async!
   (memoize
    (fn [log sync queue]
+     (tap planning-mult planning-ch-tail)
+     (tap born-peer-mult born-peer-ch-tail)
+     (tap dead-peer-mult dead-peer-ch-tail)
+     (tap evict-mult evict-ch-tail)
+     (tap offer-mult offer-ch-tail)
+     (tap ack-mult ack-ch-tail)
+     (tap completion-mult completion-ch-tail)
+
      (thread (born-peer-ch-loop log sync))
      (thread (dead-peer-ch-loop log))
      (thread (planning-ch-loop))


Do not swallow assign-watermark-fn exceptions.

--- src/onyx/peer/task_compile.clj
+++ src/onyx/peer/task_compile.clj
@@ -18,8 +18,7 @@
   (if-let [fkw (:onyx/assign-watermark-fn task-map)]
     (let [f (resolve-fn {:onyx/fn fkw})]
       (fn [segment]
-        (or (try (f segment) (catch Exception _))
-            0)))))
+        (or (f segment) 0)))))

 (s/defn filter-triggers
   [windows :- [WindowExtension]


support profiles and args

support profiles and args

--- src/org/spootnik/fleet/api.clj
+++ src/org/spootnik/fleet/api.clj
@@ -1,7 +1,29 @@
 (ns org.spootnik.fleet.api
   "Minimal file base keyvalue database for scenarios"
   (:require [clojure.java.io :as io]
+            [clojure.string  :as str]
             [clj-yaml.core   :as yaml]))
+
+
+(defn interpol
+    [input args]
+    (let [clean   #(str/replace % #"(\{\{|\}\})" "")
+          args    (assoc (into {} (map-indexed #(vector (str %1) %2) args))
+                    "*" (str/join " " args))
+          extract (fn [k] (get args (clean k) ""))]
+      (str/replace input #"\{\{[0-9*]+\}\}" extract)))
+
+(defn prepare-command
+  [args {:keys [shell literal] :as command}]
+  (if (and shell (not literal))
+    (assoc command :shell (interpol shell args))
+    command))
+
+(defn prepare
+  [scenario profile args]
+  (let [{:keys [script] :as scenario}
+        (merge scenario (get-in scenario [:profiles profile]))]
+    (assoc scenario :script (map (partial prepare-command args) script))))

 (defprotocol ScenarioStore
   (upsert! [this scenario])


Task for periodically updating the link count

Calculating this for all nodes currently takes about 40 seconds, but
is only likely to get slower. Setting it to run every two hours for now.

--- src/tropefest/handler.clj
+++ src/tropefest/handler.clj
@@ -20,28 +20,58 @@
            (route/resources "/")
            (route/not-found "Not Found"))

-(defn seed-database []
-  (timbre/info "Seeding...")
-  (if-not (db/query-by-id (db/get-connection) "Anime/SamuraiFlamenco")
-    (->
-      (p/load-resource-url "http://tvtropes.org/pmwiki/pmwiki.php/Anime/SamuraiFlamenco")
-      p/save-page-links!)))

-(defn update-handler [t opts]
+;
+; Tasks
+;
+
+(defn crawl-handler [t opts]
   (timbre/info (str "Remaining " (count (db/query-nodes-to-crawl (db/get-connection) 9999999)) " updating " (:total opts) "... "))
   (try
     (p/crawl-and-update! (db/get-connection) (Integer. (:total opts)))
+    (catch Throwable t (timbre/error (str "Exception while crawling: " t)))
+    )
+  (timbre/info "Done crawling."))
+
+(defn update-handler [t opts]
+  (timbre/info "Updating link totals")
+  (try
+    (db/update-link-count! (db/get-connection))
     (catch Throwable t (timbre/error (str "Exception while updating: " t)))
     )
-  (timbre/info "Done"))
+  (timbre/info "Done updating."))
+
+
+
+(def crawl-task
+  {:id       "crawl-task"
+   :handler  crawl-handler
+   :schedule (:update-cron env)
+   :opts     {:total (:update-size env)}})

 (def update-task
   {:id       "update-task"
    :handler  update-handler
-   :schedule (:update-cron env)
-   :opts     {:total (:update-size env)}})
+   :schedule "0 0 /2 * * * *"
+   :opts     {}})

-(def cj (cronj/cronj :entries [update-task]))
+(def cj (cronj/cronj :entries [crawl-task update-task]))
+
+
+;
+; Functions
+;
+
+(defn seed-database []
+  (do
+    (timbre/info "Seeding...")
+    (if-not (db/query-by-id (db/get-connection) "Anime/SamuraiFlamenco")
+      (->
+        (p/load-resource-url "http://tvtropes.org/pmwiki/pmwiki.php/Anime/SamuraiFlamenco")
+        p/save-page-links!))
+    (update-handler nil nil)
+    (timbre/info "Done seeding.")))
+

 (defn init
   "init will be called once when


Refactor camera configuration

Refactor camera configuration

--- src/try_lwjgl/display.clj
+++ src/try_lwjgl/display.clj
@@ -88,18 +88,19 @@
   (GL20/glValidateProgram @program)
   (exitOnGLError "Error in setupOpenGL"))

+(defn build-camera []
+  (.build
+   (doto (EulerCamera$Builder.)
+     (.setAspectRatio
+      (/ (float (Display/getWidth)) (Display/getHeight)))
+     (.setRotation (float -1.12) (float 0.16) (float 0))
+     (.setPosition (float -1.38) (float 1.36) (float 7.95))
+     (.setFieldOfView 60))))
+
 (defn setup-camera []
-  (let [builder (EulerCamera$Builder.)
-        _ (doto builder
-            (.setAspectRatio
-             (/ (float (Display/getWidth)) (Display/getHeight)))
-            (.setRotation (float -1.12) (float 0.16) (float 0))
-            (.setPosition (float -1.38) (float 1.36) (float 7.95))
-            (.setFieldOfView 60))
-        c (.build builder)]
-    (swap! camera (fn [_] c))
-    (.applyOptimalStates c)
-    (.applyPerspectiveMatrix c)))
+  (doto (swap! camera (fn [_] (build-camera)))
+    (.applyOptimalStates)
+    (.applyPerspectiveMatrix)))

 (defmacro do-shape [type & commands]
   `(do


Double the bass and slow it down.

Double the bass and slow it down.

--- src/overtunes/songs/before.clj
+++ src/overtunes/songs/before.clj
@@ -22,7 +22,7 @@
   (chord :Eb4 :major)
   (chord :Eb4 :major)
   (chord :Bb3 :major)
-  (chord :Bb3 :major)
+  ()
 ])

 (def variation-section [
@@ -41,26 +41,27 @@
   (chord :Eb4 :major)
   (chord :Eb4 :major)
   (chord :Bb3 :major)
-  (chord :Bb3 :major)
+  ()
 ])

-
 (defn play-chord [notes duration] ( do
-  (defn play-note [note] (organ-cornet note (/ duration (* 1000 0.9))))
-  (let [
-    root (first notes)
-    bass (- root 12)
-    with-bass (cons bass notes)
-  ]
-    (mix (map play-note (map midi->hz with-bass)))
+  (defn play-note [note] (organ-cornet note (/ duration 1000)))
+  (if (not (empty? notes))
+    (let [
+      root (first notes)
+      bass [(- root 12) (- root 24)]
+      with-bass (concat bass notes)
+    ]
+      (mix (map play-note (map midi->hz with-bass)))
+    )
   )
 ))

-(def metro (metronome 250))
+(def metro (metronome 90))
 (defn beat-length [metro] (- (metro 1) (metro 0)))

 (defn play-progression [progression metro start] ( do
-  (if (> (count progression) 0) (do
+  (if (not (empty? progression)) (do
     (at (metro (+ start 0)) (play-chord (nth progression 0) (* 4 (beat-length metro))))
     (play-progression (rest progression) metro (+ start 4))
   ))
@@ -71,6 +72,8 @@
     (concat
       starting-section
       middle-section
+      starting-section
+      middle-section
       variation-section
     )
     metro


Offset the start by a beat so we don't risk the chords and the melody starting at different times.

--- src/overtunes/songs/before.clj
+++ src/overtunes/songs/before.clj
@@ -90,7 +90,8 @@

 (defn before []
   "The full version of 'Before', grave."
-  (play (concat start middle start middle variation) (metronome 30)))
+  (play (concat start middle start middle variation)
+        (metronome-from (metronome 30) 1)))

 (defn before-short []
   "A short version of 'Before', adante."


Allow creating new tiled maps, layers, and cells

--- src/play_clj/core_graphics.clj
+++ src/play_clj/core_graphics.clj
@@ -20,10 +20,12 @@
 ; tiled maps

 (defn tiled-map*
-  [s]
-  (if (string? s)
-    (.load (TmxMapLoader.) s)
-    s))
+  ([]
+    (TiledMap.))
+  ([s]
+    (if (string? s)
+      (.load (TmxMapLoader.) s)
+      s)))

 (defmacro tiled-map
   "Returns a [TiledMap](http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/maps/tiled/TiledMap.html).
@@ -51,12 +53,14 @@
       (.get layers i))))

 (defn tiled-map-layer*
-  [screen layer]
-  (if (isa? (type layer) MapLayer)
-    layer
-    (->> (tiled-map-layers screen)
-         (drop-while #(not= layer (.getName ^MapLayer %)))
-         first)))
+  ([width height tile-width tile-height]
+    (TiledMapTileLayer. width height tile-width tile-height))
+  ([screen layer]
+    (if (isa? (type layer) MapLayer)
+      layer
+      (->> (tiled-map-layers screen)
+           (drop-while #(not= layer (.getName ^MapLayer %)))
+           first))))

 (defmacro tiled-map-layer
   "Returns a [TiledMapTileLayer](http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/maps/tiled/TiledMapTileLayer.html)
@@ -83,8 +87,10 @@
     (tiled-map-layer! layer :get-name)))

 (defn tiled-map-cell*
-  [screen layer x y]
-  (.getCell ^TiledMapTileLayer (tiled-map-layer screen layer) x y))
+  ([]
+    (TiledMapTileLayer$Cell.))
+  ([screen layer x y]
+    (.getCell ^TiledMapTileLayer (tiled-map-layer screen layer) x y)))

 (defmacro tiled-map-cell
   "Returns a [TiledMapTileLayer.Cell](http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/maps/tiled/TiledMapTileLayer.Cell.html)


DM + CM #20 - Clean up invitation integration tests

--- integration/objective8/api/invitations_integration_tests.clj
+++ integration/objective8/api/invitations_integration_tests.clj
@@ -9,10 +9,6 @@
             [objective8.users :as users]
             [objective8.objectives :as objectives]
             [objective8.middleware :as m]))
-
-
-;; Testing from http request -> making correct calls within writers namespace
-;; Mock or stub out 'writers' namespace

 (def app (helpers/test-context))
 (def OBJECTIVE_ID 1)
@@ -29,10 +25,6 @@
     :objective-id objective-id
     :invited-by-id invited-by-id }))

-(def the-invitation (an-invitation))
-(def the-invitation-as-json (json/generate-string (an-invitation)))
-(def the-stored-invitation (assoc (an-invitation) :_id INVITATION_ID))
-
 (facts "POST /api/v1/objectives/:id/writer-invitations" :integration
        (against-background
         (m/valid-credentials? anything anything anything) => true)
@@ -43,14 +35,13 @@

         (fact "the invitation is stored"
               (let [{obj-id :_id created-by-id :created-by-id} (sh/store-an-objective)
-                    the-invitation (an-invitation obj-id created-by-id)
+                    invitation (an-invitation obj-id created-by-id)
                     {response :response} (p/request app (str "/api/v1/objectives/" obj-id "/writer-invitations")
                                                     :request-method :post
                                                     :content-type "application/json"
-                                                    :body (json/generate-string the-invitation))]
+                                                    :body (json/generate-string invitation))]
                 (:status response) => 201
-                (:body response) => (helpers/json-contains (assoc the-invitation
-                                                                  :_id integer?))
+                (:body response) => (helpers/json-contains (assoc invitation :_id integer?))
                 (:headers response) => (helpers/location-contains (str "/api/v1/objectives/" obj-id "/writer-invitations/"))))

         (fact "a 423 (resource locked) status is returned when drafting has started on the objective"
@@ -66,117 +57,110 @@
               (against-background
                (invitations/store-invitation! anything) =throws=> (org.postgresql.util.PSQLException.
                                                                    (org.postgresql.util.ServerErrorMessage. "" 0)))
-              (:response (p/request app (str "/api/v1/objectives/" OBJECTIVE_ID "/writer-invitations")
-                                    :request-method :post
-                                    :content-type "application/json"
-                                    :body the-invitation-as-json)) => (contains {:status 400}))))
+              (get-in (p/request app (str "/api/v1/objectives/" OBJECTIVE_ID "/writer-invitations")
+                                 :request-method :post
+                                 :content-type "application/json"
+                                 :body (json/generate-string (an-invitation)))
+                      [:response :status]) => 400)))

-(facts "invitations" :integration
+(facts "GET /api/v1/invitations?uuid=<UUID>" :integration
        (against-background
          [(m/valid-credentials? anything anything anything) => true
-          (before :contents (do
-                              (helpers/db-connection)
-                              (helpers/truncate-tables)))
+          (before :contents (do (helpers/db-connection)
+                                (helpers/truncate-tables)))
           (after :facts (helpers/truncate-tables))]

-         (facts "GET /api/v1/invitations?uuid=<UUID>"
-                (fact "retrieves the active invitation with the given uuid if it exists"
-                      (let
-                        [created-by-id (:_id (users/store-user! {:twitter-id "some-twitter-id" :username "username"}))
-                         objective-id (:_id (objectives/store-objective! {:created-by-id created-by-id :end-date "2015-01-01"}))
-                         stored-invitation (invitations/store-invitation! {:invited-by-id created-by-id
-                                                                       :objective-id objective-id})
-                         uuid (:uuid stored-invitation)]
-                        (helpers/peridot-response-json-body->map (p/request app (str "/api/v1/invitations?uuid=" uuid))) => stored-invitation))
+         (fact "retrieves the active invitation with the given uuid if it exists"
+               (let [{uuid :uuid :as stored-invitation} (sh/store-an-invitation)
+                     {response :response} (p/request app (str "/api/v1/invitations?uuid=" uuid))]
+                 (:body response) => (helpers/json-contains stored-invitation)))

-                (fact "returns a 404 status if an invitation with uuid=<UUID> doesn't exist"
-                      (p/request app "/api/v1/invitations?uuid=non-existent-uuid") => (contains {:response (contains {:status 404})}))
+         (fact "returns a 404 status if an invitation with uuid=<UUID> doesn't exist"
+               (get-in (p/request app "/api/v1/invitations?uuid=non-existent-uuid")
+                       [:response :status]) => 404)

-                (fact "returns a 400 status when a PSQLException is raised"
-                      (against-background
-                        (invitations/get-active-invitation anything) =throws=> (org.postgresql.util.PSQLException.
-                                                                                (org.postgresql.util.ServerErrorMessage. "" 0)))
-                      (p/request app "/api/v1/invitations?uuid=some-uuid") => (contains {:response (contains {:status 400})})))))
+         (fact "returns a 400 status when a PSQLException is raised"
+               (against-background
+                (invitations/get-active-invitation anything) =throws=> (org.postgresql.util.PSQLException.
+                                                                        (org.postgresql.util.ServerErrorMessage. "" 0)))
+               (get-in (p/request app "/api/v1/invitations?uuid=some-uuid")
+                       [:response :status]) => 400)))

-(facts "accepting invitations" :integration
+(facts "POST /api/v1/objectives/:obj-id/candidate-writers" :integration
      (against-background
        [(m/valid-credentials? anything anything anything) => true
-          (before :contents (do
-                              (helpers/db-connection)
-                              (helpers/truncate-tables)))
+          (before :contents (do (helpers/db-connection)
+                                (helpers/truncate-tables)))
           (after :facts (helpers/truncate-tables))]

-       (facts "POST /api/v1/objectives/:obj-id/candidate-writers"
-              (fact "creating a candidate writer accepts the invitation"
-                    (let [{invitation-id :_id
-                           objective-id :objective-id
-                           invitation-reason :reason
-                           writer-name :writer-name
-                           invitation-uuid :uuid} (sh/store-an-invitation)
+       (fact "creates a candidate writer and accepts the invitation"
+             (let [{invitation-id :_id
+                    objective-id :objective-id
+                    invitation-reason :reason
+                    writer-name :writer-name
+                    invitation-uuid :uuid} (sh/store-an-invitation)

-                           {invitee-id :_id} (users/store-user! {:twitter-id "some-other-twitter-id" :username "otherUsername"})
-                           candidate-data-as-json (json/generate-string {:invitation-uuid invitation-uuid
-                                                                         :invitee-id invitee-id
-                                                                         :objective-id objective-id})
-                           {response :response} (p/request app (str "/api/v1/objectives/" objective-id
-                                                                    "/candidate-writers")
-                                                           :request-method :post
-                                                           :content-type "application/json"
-                                                           :body candidate-data-as-json)
-                           updated-invitation (sh/retrieve-invitation invitation-id)]
-                      (:status updated-invitation) => "accepted"
-                      (:status response) => 201
-                      (:headers response) => (helpers/location-contains (str "/api/v1/objectives/" objective-id
-                                                                             "/candidate-writers/"))
-                      (:body response) => (helpers/json-contains {:_id integer?
-                                                                  :user-id invitee-id
-                                                                  :invitation-id invitation-id
-                                                                  :objective-id objective-id
-                                                                  :invitation-reason invitation-reason
-                                                                  :writer-name writer-name})))
+                    {invitee-id :_id} (sh/store-a-user)
+                    candidate-data {:invitation-uuid invitation-uuid
+                                    :invitee-id invitee-id
+                                    :objective-id objective-id}
+                    {response :response} (p/request app (str "/api/v1/objectives/" objective-id
+                                                             "/candidate-writers")
+                                                    :request-method :post
+                                                    :content-type "application/json"
+                                                    :body (json/generate-string candidate-data))
+                    updated-invitation (sh/retrieve-invitation invitation-id)]
+               (:status updated-invitation) => "accepted"
+               (:status response) => 201
+               (:headers response) => (helpers/location-contains (str "/api/v1/objectives/" objective-id
+                                                                      "/candidate-writers/"))
+               (:body response) => (helpers/json-contains {:_id integer?
+                                                           :user-id invitee-id
+                                                           :invitation-id invitation-id
+                                                           :objective-id objective-id
+                                                           :invitation-reason invitation-reason
+                                                           :writer-name writer-name})))

-              (fact "Cannot create a candidate writer when no invitation exists with given uuid"
-                    (let [{invitee-id :_id} (sh/store-a-user)
-                          {objective-id :_id} (sh/store-an-objective)
-                          candidate-data-as-json (json/generate-string {:invitation-uuid "nonexistent uuid"
-                                                                        :invitee-id invitee-id
-                                                                        :objective-id objective-id
-                                                                        :invitation-reason "some reason"
-                                                                        :writer-name "writer name"})
-                          {response :response} (p/request app (str "/api/v1/objectives/" objective-id
-                                                                   "/candidate-writers")
-                                                          :request-method :post
-                                                          :content-type "application/json"
-                                                          :body candidate-data-as-json)]
-                      (:status response) => 403)))))
+       (fact "returns 403 status when no invitation exists with given uuid"
+             (let [{invitee-id :_id} (sh/store-a-user)
+                   {objective-id :_id} (sh/store-an-objective)
+                   candidate-data {:invitation-uuid "nonexistent uuid"
+                                   :invitee-id invitee-id
+                                   :objective-id objective-id
+                                   :invitation-reason "some reason"
+                                   :writer-name "writer name"}
+                   {response :response} (p/request app (str "/api/v1/objectives/" objective-id
+                                                            "/candidate-writers")
+                                                   :request-method :post
+                                                   :content-type "application/json"
+                                                   :body (json/generate-string candidate-data))]
+               (:status response) => 403))))

-(facts "declining invitations" :integration
+(facts "PUT /api/v1/objectives/:obj-id/writer-invitations/:inv-id" :integration
        (against-background
         [(m/valid-credentials? anything anything anything) => true
-         (before :contents (do
-                             (helpers/db-connection)
-                             (helpers/truncate-tables)))
+         (before :contents (do (helpers/db-connection)
+                               (helpers/truncate-tables)))
          (after :facts (helpers/truncate-tables))]

-        (facts "PUT /api/v1/objectives/:obj-id/writer-invitations/:inv-id"
-               (fact "declines the invitation"
-                     (let [{invitation-id :_id objective-id :objective-id invitation-uuid :uuid} (sh/store-an-invitation)
-                            invitation-response-as-json (json/generate-string {:invitation-uuid invitation-uuid})
-                            {response :response} (p/request app (str "/api/v1/objectives/" objective-id
-                                                                     "/writer-invitations/" invitation-id)
-                                                            :request-method :put
-                                                            :content-type "application/json"
-                                                            :body invitation-response-as-json)
-                            updated-invitation (sh/retrieve-invitation invitation-id)]
-                       (:status response) => 200
-                       (:body response) => (helpers/json-contains updated-invitation)
-                       (:status updated-invitation) => "declined"))
+        (fact "declines the invitation"
+              (let [{invitation-id :_id objective-id :objective-id invitation-uuid :uuid} (sh/store-an-invitation)
+                    invitation-response {:invitation-uuid invitation-uuid}
+                    {response :response} (p/request app (str "/api/v1/objectives/" objective-id
+                                                             "/writer-invitations/" invitation-id)
+                                                    :request-method :put
+                                                    :content-type "application/json"
+                                                    :body (json/generate-string invitation-response))
+                    updated-invitation (sh/retrieve-invitation invitation-id)]
+                (:status response) => 200
+                (:body response) => (helpers/json-contains updated-invitation)
+                (:status updated-invitation) => "declined"))

-               (fact "returns 404 when no active invitation exists with the given id"
-                     (let [invitation-response-as-json (json/generate-string {:invitation-uuid "nonexistent uuid"})
-                           {response :response} (p/request app (str "/api/v1/objectives/" "3"
-                                                                    "/writer-invitations/" "10")
-                                                           :request-method :put
-                                                           :content-type "application/json"
-                                                           :body invitation-response-as-json)]
-                       (:status response) => 404)))))
+        (fact "returns 404 when no active invitation exists with the given id"
+              (let [invitation-response-as-json (json/generate-string {:invitation-uuid "nonexistent uuid"})
+                    {response :response} (p/request app (str "/api/v1/objectives/" "3"
+                                                             "/writer-invitations/" "10")
+                                                    :request-method :put
+                                                    :content-type "application/json"
+                                                    :body invitation-response-as-json)]
+                (:status response) => 404))))


cosmetic changes to error-html

cosmetic changes to error-html

--- src/saved_for_reddit/core.cljs
+++ src/saved_for_reddit/core.cljs
@@ -37,14 +37,11 @@
   [:p {:class "navbar-text navbar-right"} "Logged in as " (:username @app-state)])

 (defn error-html []
-  [:div {:class "row"}
-   [:div {:class "col-md-12"}
-    [:h4 "Error"]
-    [:div {:class "alert alert-danger" :role "alert"}
-     [:pre @error-msg]]
-    [:input {:type "button" :value "Clear app LocalStorage and refresh!"
-             :on-click (fn [] (alandipert.storage-atom/remove-local-storage! :saved-for-reddit-app-state)
-                         (set! (.-location js/window) "/"))}]]])
+  [:div {:class "alert alert-danger" :role "alert"}
+   [:pre @error-msg]
+   [:input {:type "button" :value "Clear app LocalStorage and refresh!"
+            :on-click (fn [] (alandipert.storage-atom/remove-local-storage! :saved-for-reddit-app-state)
+                        (set! (.-location js/window) "/"))}]])

 (defn post-html [p]
   (let [link? (nil? (:title p))


Add streaming validator

Add streaming validator

--- src/structural_typing/frob.clj
+++ src/structural_typing/frob.clj
@@ -69,3 +69,6 @@

+(defn mkst:validator [pred exploder]
+  (fn [coll]
+    (map #(if (pred %) % (exploder coll %)) coll)))


Remove deprecated function

Remove deprecated function

--- src/structural_typing/type.clj
+++ src/structural_typing/type.clj
@@ -158,15 +158,6 @@
   ([type-shorthand candidate]
      (built-like? @global-type/repo type-shorthand candidate)))

-(depr/defn described-by?
-  "Use [[built-like?]] instead."
-  {:deprecated {:in "0.13.0"
-                :use-instead built-like?}}
-  ([type-repo type-shorthand candidate]
-     (built-like? type-repo type-shorthand candidate))
-  ([type-shorthand candidate]
-     (described-by? @global-type/repo type-shorthand candidate)))
-
 (defn origin
   "Returns the original condensed type description associated with the `type-signifier`.
    Uses the global type repo if none is given.


Add copy-icon event.

Add copy-icon event.

--- src/uxbox/main/data/icons.cljs
+++ src/uxbox/main/data/icons.cljs
@@ -319,6 +319,25 @@
   [id]
   (ToggleIconSelection. id))

+;; --- Copy Icon
+
+(defrecord CopySelected [id]
+  rs/WatchEvent
+  (-apply-watch [_ state stream]
+    (let [selected (get-in state [:dashboard :icons :selected])
+          selected (map #(get-in state [:icons-by-id %]) selected)]
+      (->> (rx/from-coll selected)
+           (rx/map #(dissoc % :id))
+           (rx/map #(assoc % :collection id))
+           (rx/flat-map #(rp/req :create/icon %))
+           (rx/map :payload)
+           (rx/map icon-created)))))
+
+(defn copy-selected
+  [id]
+  {:pre [(or (uuid? id) (nil? id))]}
+  (CopySelected. id))
+
 ;; --- Delete Selected

 (defrecord DeleteSelected []


:sparkles: Finish 'remove-demo-profile' async task implementation.

--- backend/src/uxbox/tasks/remove_demo_profile.clj
+++ backend/src/uxbox/tasks/remove_demo_profile.clj
@@ -31,59 +31,59 @@
 (defn handler
   [{:keys [props] :as task}]
   (us/verify ::props props)
-  (prn "handler" props (.getName (Thread/currentThread)))
   (db/with-atomic [conn db/pool]
     (remove-file-images conn (:id props))
     (remove-images conn (:id props))
-    (remove-profile conn (:id props))
-    (prn "finished" (.getName (Thread/currentThread)))))
+    (remove-profile conn (:id props))))

-(def ^:private sql:file-images-to-delete
-  "select pfi.id, pfi.path, pfi.thumb_path
-     from project_file_images as pfi
-    inner join project_files as pf on (pf.id = pfi.file_id)
-    inner join projects as p on (p.id = pf.project_id)
-    where p.user_id = $1
-    limit 2")
-
-(defn remove-file-images
-  [conn id]
-  (p/loop []
-    (p/let [files (db/query conn [sql:file-images-to-delete id])]
-      (prn "remove-file-images" files)
-      (when-not (empty? files)
-        (-> (vu/blocking
-             (doseq [item files]
-               (ust/delete! media/media-storage (:path item))
-               (ust/delete! media/media-storage (:thumb-path item))))
-            (p/then' #(p/recur)))))))
-
-(def ^:private sql:images
-  "select img.id, img.path, img.thumb_path
-     from images as img
-    where img.user_id = $1
-    limit 5")
-
-(defn remove-files
+(defn- remove-files
   [files]
-  (prn "remove-files" (.getName (Thread/currentThread)))
   (doseq [item files]
     (ust/delete! media/media-storage (:path item))
     (ust/delete! media/media-storage (:thumb-path item)))
   files)

-(defn remove-images
+(def ^:private sql:delete-file-images
+  "with images_part as (
+     select pfi.id
+       from project_file_images as pfi
+      inner join project_files as pf on (pf.id = pfi.file_id)
+      inner join projects as p on (p.id = pf.project_id)
+      where p.user_id = $1
+      limit 10
+   )
+   delete from project_file_images
+    where id in (select id from images_part)
+   returning id, path, thumb_path")
+
+(defn remove-file-images
   [conn id]
-  (prn "remove-images" (.getName (Thread/currentThread)))
-  (vu/loop [i 0]
-    (prn "remove-images loop" i (.getName (Thread/currentThread)))
-    (-> (db/query conn [sql:images id])
+  (vu/loop []
+    (-> (db/query conn [sql:delete-file-images id])
         (p/then (vu/wrap-blocking remove-files))
         (p/then (fn [images]
-                  (prn "ending" (.getName (Thread/currentThread)))
-                  (when (and (not (empty? images))
-                             (< i 1000))
-                    (p/recur (inc i))))))))
+                  (when (not (empty? images))
+                    (p/recur)))))))
+
+(def ^:private sql:delete-images
+  "with images_part as (
+     select img.id
+       from images as img
+      where img.user_id = $1
+      limit 10
+   )
+   delete from images
+    where id in (select id from images_part)
+   returning id, path, thumb_path")
+
+(defn- remove-images
+  [conn id]
+  (vu/loop []
+    (-> (db/query conn [sql:delete-images id])
+        (p/then (vu/wrap-blocking remove-files))
+        (p/then (fn [images]
+                  (when (not (empty? images))
+                    (p/recur)))))))

 (defn remove-profile
   [conn id]


fix example

fix example

--- example/srcs/ui/core.cljs
+++ example/srcs/ui/core.cljs
@@ -56,50 +56,45 @@
                                   :other-owner {:validators {:not-blank true}}}}
               :value {:owner {:name "Mike"}
                       :other-owner {:name "Marat"}}}]
-    (form/init form)
-
-    (fn []
-      [:div.row
-       [:div.col
-        [:h1 "Select widget"]
-
-        [:label "Owner: "]
-        [form/input {:form :selects-form
-                     :items items
-                     :label-fn :name
-                     :path [:owner]
-                     :input w/radio-input}]
-        [:br]
-        [:br]
-        [:label "Horizontal owner:"]
-        [form/input {:form :selects-form
-                     :items items
-                     :label-fn :name
-                     :path [:other-owner]
-                     :input w/button-select-input}]
-        [:div.col [form/form-data {:form :selects-form}]]]])))
+    (fn []
+      [form/form form
+       [:div.row
+        [:div.col
+         [:h1 "Select widget"]
+
+         [:label "Owner: "]
+         [form/input {:items items
+                      :label-fn :name
+                      :path [:owner]
+                      :input w/radio-input}]
+         [:br]
+         [:br]
+         [:label "Horizontal owner:"]
+         [form/input {:items items
+                      :label-fn :name
+                      :path [:other-owner]
+                      :input w/button-select-input}]
+         [:div.col [form/form-data {:form :selects-form}]]]]])))

 (defn switchbox-page []
   (let [form {:name :switches-form
               :value {:admin true
                       :superuser false}}]
-    (form/init form)
-    (fn []
-      [:div.row
-       [:div.col
-        [:h1 "Switch widget"]
-        [:label "Is admin?"]
-        [form/input {:form :switches-form
-                     :path [:admin]
-                     :label "admin"
-                     :input w/switchbox-input}]
-        [:label "Is superuser?"]
-        [form/input {:form :switches-form
-                     :path [:superuser]
-                     :label "superuser"
-                     :input w/switchbox-input}]]
-       [:div.col
-        [form/form-data {:form :switches-form}]]])))
+    (fn []
+      [form/form form
+       [:div.row
+        [:div.col
+         [:h1 "Switch widget"]
+         [:label "Is admin?"]
+         [form/input {:path [:admin]
+                      :label "admin"
+                      :input w/switchbox-input}]
+         [:label "Is superuser?"]
+         [form/input {:path [:superuser]
+                      :label "superuser"
+                      :input w/switchbox-input}]]
+        [:div.col
+         [form/form-data {:form :switches-form}]]]])))

 #_(defn list-page []
     (let [form {:path [:forms :myform]
@@ -127,23 +122,23 @@
   (let [form {:name :calendars-form
               :value {:birthdate-one {:d 5 :m 3 :y 1980}
                       :birthdate-two {:d 17 :m 4 :y 1995}}}]
-    (rf/dispatch [:re-form/init form])
-    (fn []
-      [:div.row
-       [:div.col
-        [:h1 "Calendar"]
-        [:div.form-row
-         [:label "Birth Date"]
-         [form/input {:form :calendars-form
-                      :path [:birthdate-one]
-                      :input w/calendar-input}]]
-        [:div.form-row
-         [:label "Birth Date 2"]
-         [form/input {:form :calendars-form
-                      :path [:birthdate-two]
-                      :input w/calendar-input}]]]
-       [:div.col
-        [form/form-data {:form :calendars-form}]]])))
+    (fn []
+      [form/form form
+       [:div.row
+        [:div.col
+         [:h1 "Calendar"]
+         [:div.form-row
+          [:label "Birth Date"]
+          [form/input {:form :calendars-form
+                       :path [:birthdate-one]
+                       :input w/calendar-input}]]
+         [:div.form-row
+          [:label "Birth Date 2"]
+          [form/input {:form :calendars-form
+                       :path [:birthdate-two]
+                       :input w/calendar-input}]]]
+        [:div.col
+         [form/form-data {:form :calendars-form}]]]])))

 (defn inputs-page []
@@ -222,16 +217,15 @@
 (defn textarea-page []
   (let [form {:name :textarea-form
               :value {:area-one "Fill me"}}]
-    (rf/dispatch [:re-form/init form])
-    (fn []
-      [:div [:h1 "Text field widget"]
-       [:div.row
-        [:div.col
-         [form/input {:form :textarea-form
-                      :path [:area-one]
-                      :input w/textarea-input}]]
-        [:div.col
-         [form/form-data {:form :textarea-form}]]]])))
+    (fn []
+      [form/form form
+       [:div [:h1 "Text field widget"]
+        [:div.row
+         [:div.col
+          [form/input {:path [:area-one]
+                       :input w/textarea-input}]]
+         [:div.col
+          [form/form-data {:form :textarea-form}]]]]])))

 (def pages
   {:index {:title "Form builder"


:nail_care:

--- test/com/wsscode/pathom/connect/run_graph_test.cljc
+++ test/com/wsscode/pathom/connect/run_graph_test.cljc
@@ -131,9 +131,21 @@
            {::pcrg/unreachable #{}
             ::pcrg/root        1
             ::pcrg/nodes       {1 {::pcrg/node-id  1
-                             ::pc/sym        'a
-                             ::pcrg/requires {:a {}}
-                             ::pcrg/provides {:a {}}}}})))
+                                   ::pc/sym        'a
+                                   ::pcrg/requires {:a {}}
+                                   ::pcrg/provides {:a {}}}}})))
+
+  (testing "broken chain"
+    (is (= (compute-run-graph*
+             {::resolvers [{::pc/sym    'b
+                            ::pc/input  #{:a}
+                            ::pc/output [:b]}
+                           {::pc/sym    'c
+                            ::pc/input  #{:b}
+                            ::pc/output [:c]}]
+              ::eql/query [:c]})
+           '#::pcrg{:nodes       {}
+                    :unreachable #{:a}})))

   (testing "extra provides"
     (is (= (compute-run-graph*
@@ -143,11 +155,11 @@
            {::pcrg/unreachable #{}
             ::pcrg/root        1
             ::pcrg/nodes       {1 {::pcrg/node-id  1
-                             ::pc/sym        'a
-                             ::pcrg/requires {:a {}}
-                             ::pcrg/provides {:a {}
-                                              :b {}
-                                              :c {}}}}})))
+                                   ::pc/sym        'a
+                                   ::pcrg/requires {:a {}}
+                                   ::pcrg/provides {:a {}
+                                                    :b {}
+                                                    :c {}}}}})))

   (testing "multiple paths"
     (is (= (compute-run-graph*
@@ -157,18 +169,18 @@
                             ::pc/output [:a]}]
               ::eql/query [:a]})
            '#::pcrg{:unreachable #{}
-                    :nodes       {1 {::pcrg/node-id 1
-                               ::pcrg/provides {:a {}}
-                               ::pcrg/requires {:a {}}
-                               ::pc/sym        a}
-                            2 {::pcrg/node-id  2
-                               ::pcrg/provides {:a {}}
-                               ::pcrg/requires {:a {}}
-                               ::pc/sym        a2}
-                            3 #::pcrg{:node-id  3
-                                      :requires {:a {}}
-                                      :run-or   [1
-                                                 2]}}
+                    :nodes       {1 {::pcrg/node-id  1
+                                     ::pcrg/provides {:a {}}
+                                     ::pcrg/requires {:a {}}
+                                     ::pc/sym        a}
+                                  2 {::pcrg/node-id  2
+                                     ::pcrg/provides {:a {}}
+                                     ::pcrg/requires {:a {}}
+                                     ::pc/sym        a2}
+                                  3 #::pcrg{:node-id  3
+                                            :requires {:a {}}
+                                            :run-or   [1
+                                                       2]}}
                     :root        3})))

   (testing "multiple attribute request"
@@ -181,17 +193,17 @@
            {::pcrg/unreachable #{}
             ::pcrg/root        3
             ::pcrg/nodes       {1 {::pcrg/node-id  1
-                             ::pc/sym        'a
-                             ::pcrg/requires {:a {}}
-                             ::pcrg/provides {:a {}}}
-                          2 {::pcrg/node-id  2
-                             ::pc/sym        'b
-                             ::pcrg/requires {:b {}}
-                             ::pcrg/provides {:b {}}}
-                          3 {::pcrg/node-id  3
-                             ::pcrg/run-and  [1 2]
-                             ::pcrg/requires {:a {}
-                                              :b {}}}}})))
+                                   ::pc/sym        'a
+                                   ::pcrg/requires {:a {}}
+                                   ::pcrg/provides {:a {}}}
+                                2 {::pcrg/node-id  2
+                                   ::pc/sym        'b
+                                   ::pcrg/requires {:b {}}
+                                   ::pcrg/provides {:b {}}}
+                                3 {::pcrg/node-id  3
+                                   ::pcrg/run-and  [1 2]
+                                   ::pcrg/requires {:a {}
+                                                    :b {}}}}})))

   (testing "multiple attribute request on a single resolver"
     (is (= (compute-run-graph*
@@ -201,11 +213,11 @@
            {::pcrg/unreachable #{}
             ::pcrg/root        1
             ::pcrg/nodes       {1 {::pcrg/node-id  1
-                             ::pc/sym        'a
-                             ::pcrg/requires {:a {}
-                                              :b {}}
-                             ::pcrg/provides {:a {}
-                                              :b {}}}}})))
+                                   ::pc/sym        'a
+                                   ::pcrg/requires {:a {}
+                                                    :b {}}
+                                   ::pcrg/provides {:a {}
+                                                    :b {}}}}})))

   (testing "single dependency"
     (is (= (compute-run-graph*
@@ -218,16 +230,16 @@
            {::pcrg/unreachable #{}
             ::pcrg/root        2
             ::pcrg/nodes       {1 {::pcrg/node-id  1
-                             ::pc/sym        'b
-                             ::pcrg/requires {:b {}}
-                             ::pcrg/provides {:b {}}}
-                          2 {::pcrg/node-id  2
-                             ::pc/sym        'a
-                             ::pcrg/run-next 1
-                             ::pcrg/requires {:a {}
-                                              :b {}}
-                             ::pcrg/provides {:a {}
-                                              :b {}}}}})))
+                                   ::pc/sym        'b
+                                   ::pcrg/requires {:b {}}
+                                   ::pcrg/provides {:b {}}}
+                                2 {::pcrg/node-id  2
+                                   ::pc/sym        'a
+                                   ::pcrg/run-next 1
+                                   ::pcrg/requires {:a {}
+                                                    :b {}}
+                                   ::pcrg/provides {:a {}
+                                                    :b {}}}}})))

   (testing "dependency chain"
     (is (= (compute-run-graph*
@@ -241,36 +253,24 @@
                             ::pc/output [:c]}]
               ::eql/query [:c]})
            '#::pcrg{:unreachable #{}
-                    :nodes       {1 {::pcrg/node-id 1
-                               ::pcrg/provides {:c {}}
-                               ::pcrg/requires {:c {}}
-                               ::pc/sym        c}
-                            2 {::pcrg/node-id  2
-                               ::pcrg/provides {:b {}
-                                                :c {}}
-                               ::pcrg/requires {:b {}
-                                                :c {}}
-                               ::pcrg/run-next 1
-                               ::pc/sym        b}
-                            3 {::pcrg/node-id  3
-                               ::pcrg/provides {:a {}
-                                                :b {}
-                                                :c {}}
-                               ::pcrg/requires {:a {}
-                                                :b {}
-                                                :c {}}
-                               ::pcrg/run-next 2
-                               ::pc/sym        a}}
-                    :root        3})))
-
-  (testing "broken chain"
-    (is (= (compute-run-graph*
-             {::resolvers [{::pc/sym    'b
-                            ::pc/input  #{:a}
-                            ::pc/output [:b]}
-                           {::pc/sym    'c
-                            ::pc/input  #{:b}
-                            ::pc/output [:c]}]
-              ::eql/query [:c]})
-           '#::pcrg{:nodes       {}
-                    :unreachable #{:a}}))))
+                    :nodes       {1 {::pcrg/node-id  1
+                                     ::pcrg/provides {:c {}}
+                                     ::pcrg/requires {:c {}}
+                                     ::pc/sym        c}
+                                  2 {::pcrg/node-id  2
+                                     ::pcrg/provides {:b {}
+                                                      :c {}}
+                                     ::pcrg/requires {:b {}
+                                                      :c {}}
+                                     ::pcrg/run-next 1
+                                     ::pc/sym        b}
+                                  3 {::pcrg/node-id  3
+                                     ::pcrg/provides {:a {}
+                                                      :b {}
+                                                      :c {}}
+                                     ::pcrg/requires {:a {}
+                                                      :b {}
+                                                      :c {}}
+                                     ::pcrg/run-next 2
+                                     ::pc/sym        a}}
+                    :root        3}))))


Fix users tests.

Fix users tests.

--- test/uxbox/tests/test_users.clj
+++ test/uxbox/tests/test_users.clj
@@ -76,8 +76,8 @@
     (let [user (th/create-user conn 1)]
       (with-server {:handler (urt/app)}
         (let [token (#'usu/request-password-recovery conn "user1")
-              uri1 (str th/+base-url+ "/api/auth/password/recovery/not-existing")
-              uri2 (str th/+base-url+ "/api/auth/password/recovery/" token)
+              uri1 (str th/+base-url+ "/api/auth/recovery/not-existing")
+              uri2 (str th/+base-url+ "/api/auth/recovery/" token)
               [status1 data1] (th/http-get user uri1)
               [status2 data2] (th/http-get user uri2)]
           ;; (println "RESPONSE:" status1 data1)
@@ -95,7 +95,7 @@
       (t/is (nil? res))

       (with-server {:handler (urt/app)}
-        (let [uri (str th/+base-url+ "/api/auth/password/recovery")
+        (let [uri (str th/+base-url+ "/api/auth/recovery")
               data {:username "user1"}
               [status data] (th/http-post user uri {:body data})]
           ;; (println "RESPONSE:" status data)
@@ -110,7 +110,7 @@
     (let [user (th/create-user conn 1)]
       (with-server {:handler (urt/app)}
         (let [token (#'usu/request-password-recovery conn (:username user))
-              uri (str th/+base-url+ "/api/auth/password/recovery")
+              uri (str th/+base-url+ "/api/auth/recovery")
               data {:token token :password "mytestpassword"}
               [status data] (th/http-put user uri {:body data})


Port changes

Port changes

--- dev/src/yada/dev/system.clj
+++ dev/src/yada/dev/system.clj
@@ -72,8 +72,8 @@
    system
    :clostache-templater (make new-clostache-templater config)
    :user-guide (make new-user-guide config
-                     :prefix "http://localhost:8080"
-                     :ext-prefix "http://localhost:8081")
+                     :prefix "http://localhost:8090"
+                     :ext-prefix "http://localhost:8091")
    :website (make new-website config)
    :jquery (make new-web-resources config
                  :key :jquery
@@ -107,7 +107,7 @@
   (assoc system
     :http-server
     (make new-webserver config
-          :port 8080)
+          :port 8090)
     ))

 (defn external-server-components [system config]
@@ -118,7 +118,7 @@
                      (make new-router config)
                      [:external-resources])
    :external-server (using
-                     (new-webserver :port 8081)
+                     (new-webserver :port 8091)
                      {:request-handler :external-router
                       :user-guide :user-guide})))


Dry off tests slightly

Dry off tests slightly

--- test/superstring/core_test.clj
+++ test/superstring/core_test.clj
@@ -5,6 +5,11 @@
              [generators :as gen]
              [properties :as prop]]
             [superstring.core :as str]))
+
+(defmacro defexamples [name & examples]
+  `(deftest ~name
+     (are [actual expected] (= actual expected)
+       ~@examples)))

 (defspec appending-separator-and-chomping-does-not-alter-length 100
   (prop/for-all [s gen/string
@@ -34,15 +39,14 @@
   (prop/for-all [s gen/string]
     (= (.length (str/chomp (str s "\r\n"))) (.length s))))

-(deftest chomp
-  (are [expected actual] (= expected actual)
-    "" (str/chomp "" "")
-    "" (str/chomp "foo" "foo")
-    "foo"(str/chomp "foobar" "bar")
-    "foo" (str/chomp "foo\n")
-    "foo" (str/chomp "foo\r")
-    "foo" (str/chomp "foo\r\n")
-    "foo\n" (str/chomp "foo\n\r")))
+(defexamples chomp-examples
+  (str/chomp "" "") ""
+  (str/chomp "foo" "foo") ""
+  (str/chomp "foobar" "bar") "foo"
+  (str/chomp "foo\n") "foo"
+  (str/chomp "foo\r") "foo"
+  (str/chomp "foo\r\n") "foo"
+  (str/chomp "foo\n\r") "foo\n")

 (defspec ends-with?-acts-like-endsWith 100
   (prop/for-all [s gen/string
@@ -144,18 +148,17 @@
                  index gen/int]
     (nil? (str/slice s index len))))

-(deftest slice
-  (are [expected actual] (= expected actual)
-    nil (str/slice "" 0)
-    nil (str/slice "" 1)
-    nil (str/slice "12" 1 -1)
-    "1" (str/slice "1" 0)
-    "2" (str/slice "12" 1)
-    "12" (str/slice "12" 0 2)
-    "3456" (str/slice "0123456" 3 100)
-    "6" (str/slice "0123456" -1)
-    "45" (str/slice "0123456" -3 2)
-    "456" (str/slice "0123456" -3 100)))
+(defexamples slice
+  (str/slice "" 0) nil
+  (str/slice "" 1) nil
+  (str/slice "12" 1 -1) nil
+  (str/slice "1" 0) "1"
+  (str/slice "12" 1) "2"
+  (str/slice "12" 0 2) "12"
+  (str/slice "0123456" 3 100) "3456"
+  (str/slice "0123456" -1) "6"
+  (str/slice "0123456" -3 2) "45"
+  (str/slice "0123456" -3 100) "456")

 (defspec right-pad-results-in-strings-with-new-width 100
   (prop/for-all
@@ -195,16 +198,15 @@
                               (gen/such-that #(> % (.length s)) gen/pos-int 100))))]
     (= (.length (str/pad-left s width)) width)))

-(deftest center
-  (are [expected actual] (= expected actual)
-    "" (str/center "" 0)
-    " " (str/center "" 1)
-    "foo "(str/center "foo" 4)
-    " foo " (str/center "foo" 5)
-    "foo.!" (str/center "foo" 5 ".!")
-    "foo." (str/center "foo" 4 ".!")
-    ".!foo." (str/center "foo" 6 ".!")
-    ".!foo.!." (str/center "foo" 8 ".!")))
+(defexamples center
+  (str/center "" 0) ""
+  (str/center "" 1) " "
+  (str/center "foo" 4) "foo "
+  (str/center "foo" 5) " foo "
+  (str/center "foo" 5 ".!") "foo.!"
+  (str/center "foo" 4 ".!") "foo."
+  (str/center "foo" 6 ".!") ".!foo."
+  (str/center "foo" 8 ".!") ".!foo.!.")

 (defspec center-results-in-strings-with-new-width 100
   (prop/for-all
@@ -215,43 +217,40 @@
                               (gen/such-that #(> % (.length s)) gen/pos-int 100))))]
     (= (.length (str/center s width)) width)))

-(deftest chop-suffix
-  (are [expected actual] (= expected actual)
-    "" (str/chop-suffix "" "foo")
-    "" (str/chop-suffix "foo" "foo")
-    "foo"(str/chop-suffix "foobar" "bar")
-    "foo" (str/chop-suffix "foo " " ")
-    "foo" (str/chop-suffix "foo" "bar")
-    "foo" (str/chop-suffix "foo" "FOO")
-    "foo" (str/chop-suffix "foo" "O")
-    "fooÅ" (str/chop-suffix "fooÅ" "å")
-    "" (str/chop-suffix "foo" "FOO" :ignore-case)
-    "fo" (str/chop-suffix "foo" "O" :ignore-case)
-    "foo" (str/chop-suffix "fooÅ" "å" :ignore-case)))
-
-(deftest chop-prefix
-  (are [expected actual] (= expected actual)
-    "" (str/chop-prefix "" "foo")
-    "" (str/chop-prefix "foo" "foo")
-    "bar"(str/chop-prefix "foobar" "foo")
-    "foo" (str/chop-prefix " foo" " ")
-    "foo" (str/chop-prefix "foo" "bar")
-    "foo" (str/chop-prefix "foo" "FOO")
-    "foo" (str/chop-prefix "foo" "F")
-    "Åfoo" (str/chop-prefix "Åfoo" "å")
-    "" (str/chop-prefix "foo" "FOO" :ignore-case)
-    "oo" (str/chop-prefix "foo" "F" :ignore-case)
-    "foo" (str/chop-prefix "Åfoo" "Å" :ignore-case)))
-
-(deftest contains-test?
-  (are [expected actual] (= expected actual)
-    "" (str/contains? "" "")
-    "1" (str/contains? "1" "1")
-    "foo" (str/contains? "foo" "fo")
-    nil (str/contains? "foobar" "qux")
-    nil (str/contains? "foobar" "BAR")
-    "foobar" (str/contains? "foobar" "BAR" :ignore-case)
-    "Albert Åberg"(str/contains? "Albert Åberg" "åberg" :ignore-case)))
+(defexamples chop-suffix
+  (str/chop-suffix "" "foo") ""
+  (str/chop-suffix "foo" "foo") ""
+  (str/chop-suffix "foobar" "bar") "foo"
+  (str/chop-suffix "foo " " ") "foo"
+  (str/chop-suffix "foo" "bar") "foo"
+  (str/chop-suffix "foo" "FOO") "foo"
+  (str/chop-suffix "foo" "O") "foo"
+  (str/chop-suffix "fooÅ" "å") "fooÅ"
+  (str/chop-suffix "foo" "FOO" :ignore-case) ""
+  (str/chop-suffix "foo" "O" :ignore-case) "fo"
+  (str/chop-suffix "fooÅ" "å" :ignore-case) "foo")
+
+(defexamples chop-prefix
+  (str/chop-prefix "" "foo") ""
+  (str/chop-prefix "foo" "foo") ""
+  (str/chop-prefix "foobar" "foo")   "bar"
+  (str/chop-prefix " foo" " ") "foo"
+  (str/chop-prefix "foo" "bar") "foo"
+  (str/chop-prefix "foo" "FOO") "foo"
+  (str/chop-prefix "foo" "F") "foo"
+  (str/chop-prefix "Åfoo" "å") "Åfoo"
+  (str/chop-prefix "foo" "FOO" :ignore-case) ""
+  (str/chop-prefix "foo" "F" :ignore-case) "oo"
+  (str/chop-prefix "Åfoo" "Å" :ignore-case) "foo")
+
+(defexamples contains-test?
+  (str/contains? "" "") ""
+  (str/contains? "1" "1") "1"
+  (str/contains? "foo" "fo") "foo"
+  (str/contains? "foobar" "qux") nil
+  (str/contains? "foobar" "BAR") nil
+  (str/contains? "foobar" "BAR" :ignore-case) "foobar"
+  (str/contains? "Albert Åberg" "åberg" :ignore-case) "Albert Åberg")

 (defspec contains?-finds-generated-strings 100
   (prop/for-all [before gen/string
@@ -275,19 +274,18 @@
                  after gen/string]
     (str/contains? (str before needle after) needle :ignore-case)))

-(deftest contains-all?
-  (are [expected actual] (= expected actual)
-    "" (str/contains-all? "" [])
-    "" (str/contains-all? "" [""])
-    nil (str/contains-all? "" [nil])
-    nil (str/contains-all? nil [""])
-    "12" (str/contains-all? "12" ["1" "2"])
-    "foo" (str/contains-all? "foo" ["fo" "o"])
-    nil (str/contains-all? "foobar" ["qux"])
-    nil (str/contains-all? "foobar" ["foo" "qux"])
-    nil (str/contains-all? "foobar" ["BAR"])
-    "foobar" (str/contains-all? "foobar" ["BAR" "Foo"] :ignore-case)
-    "Albert Åberg"(str/contains-all? "Albert Åberg" ["åberg" "al"] :ignore-case)))
+(defexamples contains-all?
+  (str/contains-all? "" []) ""
+  (str/contains-all? "" [""]) ""
+  (str/contains-all? "" [nil]) nil
+  (str/contains-all? nil [""]) nil
+  (str/contains-all? "12" ["1" "2"]) "12"
+  (str/contains-all? "foo" ["fo" "o"]) "foo"
+  (str/contains-all? "foobar" ["qux"]) nil
+  (str/contains-all? "foobar" ["foo" "qux"]) nil
+  (str/contains-all? "foobar" ["BAR"]) nil
+  (str/contains-all? "foobar" ["BAR" "Foo"] :ignore-case) "foobar"
+  (str/contains-all? "Albert Åberg" ["åberg" "al"] :ignore-case)   "Albert Åberg")

 (defspec contains-all? 100
   (prop/for-all [s1 gen/string
@@ -318,23 +316,21 @@
                                       (gen/choose 3 (max 4 (- (.length s) 3)) ))))]
     (= (.length (str/truncate s len)) (min (.length s) len))))

-(deftest truncate
-  (are [expected actual] (= expected actual)
-    "" (str/truncate "" 3)
-    "..." (str/truncate "123456" 3)
-    "123..." (str/truncate "123456" 6)
-    "1" (str/truncate "1" 3)
-    "12" (str/truncate "12" 3)
-    "123" (str/truncate "123" 3)))
-
-(deftest common-prefix
-  (are [expected actual] (= expected actual)
-    "" (str/common-prefix "321" "123")
-    "Åffø" (str/common-prefix "Åffø123456" "Åfføo8yuidfg")
-    "123" (str/common-prefix "123456" "123o8yuidfg")
-    "" (str/common-prefix "Åberg" "åberg")
-    "Åberg" (str/common-prefix "åberg" "Åberg" :ignore-case)
-    "åberg" (str/common-prefix "Åberg" "åberg" :ignore-case)))
+(defexamples truncate
+  (str/truncate "" 3) ""
+  (str/truncate "123456" 3) "..."
+  (str/truncate "123456" 6) "123..."
+  (str/truncate "1" 3) "1"
+  (str/truncate "12" 3) "12"
+  (str/truncate "123" 3) "123")
+
+(defexamples common-prefix
+  (str/common-prefix "321" "123") ""
+  (str/common-prefix "Åffø123456" "Åfføo8yuidfg") "Åffø"
+  (str/common-prefix "123456" "123o8yuidfg") "123"
+  (str/common-prefix "Åberg" "åberg") ""
+  (str/common-prefix "åberg" "Åberg" :ignore-case) "Åberg"
+  (str/common-prefix "Åberg" "åberg" :ignore-case) "åberg")

 (defspec common-prefix-finds-common-prefixes 100
   (prop/for-all [prefix (gen/not-empty gen/string)
@@ -343,14 +339,13 @@
     (.startsWith (str/common-prefix (str prefix s1) (str prefix s2)) prefix)))

-(deftest common-suffix
-  (are [expected actual] (= expected actual)
-    "" (str/common-suffix "321" "123")
-    "Åffø" (str/common-suffix "123456Åffø" "o8yuidfgÅffø")
-    "123" (str/common-suffix "456123" "o8yuidfg123")
-    "" (str/common-suffix "åberG" "åberg")
-    "åberG" (str/common-suffix "åberg" "åberG" :ignore-case)
-    "åberg" (str/common-suffix "Åberg" "åberg" :ignore-case)))
+(defexamples common-suffix
+  (str/common-suffix "321" "123") ""
+  (str/common-suffix "123456Åffø" "o8yuidfgÅffø") "Åffø"
+  (str/common-suffix "456123" "o8yuidfg123") "123"
+  (str/common-suffix "åberG" "åberg") ""
+  (str/common-suffix "åberg" "åberG" :ignore-case) "åberG"
+  (str/common-suffix "Åberg" "åberg" :ignore-case) "åberg")

 (defspec common-suffix-finds-common-suffixes 100
   (prop/for-all [suffix (gen/not-empty gen/string)
@@ -418,61 +413,54 @@
                 true
                 (str/split-lines (str/wrap-words (str/join " " words) width))))))

-(deftest lower-case-test
-  (are [expected actual] (= expected actual)
-    "upper" (str/lower-case? "upper")
-    "123upper!" (str/lower-case? "123upper!")))
+(defexamples lower-case-test
+  (str/lower-case? "upper") "upper"
+  (str/lower-case? "123upper!") "123upper!")

 (defspec lisp-case-is-all-lower 100
   (prop/for-all [s (gen/not-empty gen/string)]
     (is (reduce (fn [acc c] (and acc (lower-if-lower-exists? c))) true
                 (str/lisp-case s)))))

-(deftest lisp-case-test
-  (are [expected actual] (= expected actual)
-    "pascal-case" (str/lisp-case "PascalCase")
-    "set-id" (str/lisp-case "setID")
-    "http-request" (str/lisp-case "HTTPRequest")
-    "snake-case" (str/lisp-case "snake_case")
-    "screaming-snake-case" (str/lisp-case "SCREAMING_SNAKE_CASE")))
-
-(deftest camel-case-test
-  (are [expected actual] (= expected actual)
-    "pascalCase" (str/camel-case "PascalCase")
-    "setId" (str/camel-case "setID")
-    "httpRequest" (str/camel-case "HTTPRequest")
-    "snakeCase" (str/camel-case "snake_case")
-    "screamingSnakeCase" (str/camel-case "SCREAMING_SNAKE_CASE")))
-
-(deftest pascal-case-test
-  (are [expected actual] (= expected actual)
-    "PascalCase" (str/pascal-case "PascalCase")
-    "SetId" (str/pascal-case "setID")
-    "HttpRequest" (str/pascal-case "HTTPRequest")
-    "SnakeCase" (str/pascal-case "snake_case")
-    "ScreamingSnakeCase" (str/pascal-case "SCREAMING_SNAKE_CASE")))
-
-(deftest snake-case-test
-  (are [expected actual] (= expected actual)
-    "pascal_case" (str/snake-case "PascalCase")
-    "set_id" (str/snake-case "setID")
-    "http_request" (str/snake-case "HTTPRequest")
-    "snake_case" (str/snake-case "snake_case")
-    "screaming_snake_case" (str/snake-case "SCREAMING_SNAKE_CASE")))
-
-(deftest screaming-snake-case-test
-  (are [expected actual] (= expected actual)
-    "PASCAL_CASE" (str/screaming-snake-case "PascalCase")
-    "SET_ID" (str/screaming-snake-case "setID")
-    "HTTP_REQUEST" (str/screaming-snake-case "HTTPRequest")
-    "SNAKE_CASE" (str/screaming-snake-case "snake_case")
-    "SCREAMING_SNAKE_CASE" (str/screaming-snake-case "SCREAMING_SNAKE_CASE")))
-
-(deftest strip-accents-test
-  (are [expected actual] (= expected actual)
-    "Et ca sera sa moitie" (str/strip-accents "Et ça sera sa moitié")
-    "aaaaaaaæaccceeeeeghiiiijlnnooooooøssssttuuuuuunyyczzz"
-    (str/strip-accents "ąàáäâãåæăćčĉęèéëêĝĥìíïîĵľńňòóöőôõøśșšŝťțŭùúüűûñÿýçżźž")))
+(defexamples lisp-case-test
+  (str/lisp-case "PascalCase") "pascal-case"
+  (str/lisp-case "setID") "set-id"
+  (str/lisp-case "HTTPRequest") "http-request"
+  (str/lisp-case "snake_case") "snake-case"
+  (str/lisp-case "SCREAMING_SNAKE_CASE") "screaming-snake-case")
+
+(defexamples camel-case-test
+  (str/camel-case "PascalCase") "pascalCase"
+  (str/camel-case "setID") "setId"
+  (str/camel-case "HTTPRequest") "httpRequest"
+  (str/camel-case "snake_case") "snakeCase"
+  (str/camel-case "SCREAMING_SNAKE_CASE") "screamingSnakeCase")
+
+(defexamples pascal-case-test
+  (str/pascal-case "PascalCase") "PascalCase"
+  (str/pascal-case "setID") "SetId"
+  (str/pascal-case "HTTPRequest") "HttpRequest"
+  (str/pascal-case "snake_case") "SnakeCase"
+  (str/pascal-case "SCREAMING_SNAKE_CASE") "ScreamingSnakeCase")
+
+(defexamples snake-case-test
+  (str/snake-case "PascalCase") "pascal_case"
+  (str/snake-case "setID") "set_id"
+  (str/snake-case "HTTPRequest") "http_request"
+  (str/snake-case "snake_case") "snake_case"
+  (str/snake-case "SCREAMING_SNAKE_CASE") "screaming_snake_case")
+
+(defexamples screaming-snake-case-test
+  (str/screaming-snake-case "PascalCase") "PASCAL_CASE"
+  (str/screaming-snake-case "setID") "SET_ID"
+  (str/screaming-snake-case "HTTPRequest") "HTTP_REQUEST"
+  (str/screaming-snake-case "snake_case") "SNAKE_CASE"
+  (str/screaming-snake-case "SCREAMING_SNAKE_CASE") "SCREAMING_SNAKE_CASE")
+
+(defexamples strip-accents-test
+  (str/strip-accents "Et ça sera sa moitié") "Et ca sera sa moitie"
+  (str/strip-accents "ąàáäâãåæăćčĉęèéëêĝĥìíïîĵľńňòóöőôõøśșšŝťțŭùúüűûñÿýçżźž")
+  "aaaaaaaæaccceeeeeghiiiijlnnooooooøssssttuuuuuunyyczzz")

 (def string-ascii
   (gen/fmap #(apply str %) (gen/vector gen/char-alpha)))
@@ -481,10 +469,9 @@
   (prop/for-all [s (gen/not-empty gen/string-ascii)]
     (is (= s (str/ascii? s)))))

-(deftest ascii?-test
-  (are [expected actual] (= expected actual)
-    "ascii" (str/ascii? "ascii")
-    nil (str/ascii? "Et ça sera sa moitié")))
+(defexamples ascii?-test
+  (str/ascii? "ascii") "ascii"
+  (str/ascii? "Et ça sera sa moitié") nil)

 (defspec slug-contains-only-ascii-chars 100
   (prop/for-all [s (gen/not-empty gen/string)]
@@ -502,10 +489,9 @@
   (prop/for-all [s (gen/not-empty gen/string)]
     (is (not (re-matches #"[^A-Za-z0-9_.~-]+" (str/slug s))))))

-(deftest slug-test
-  (are [expected actual] (= expected actual)
-    "this-that-the-other-various-outre-considerations"
-    (str/slug "This, That & the Other! Various Outré   Considerations")))
+(defexamples slug-test
+  (str/slug "This, That & the Other! Various Outré   Considerations")
+  "this-that-the-other-various-outre-considerations")

 (defspec mixed-case-is-true-for-mixed-case-strings 100
   (prop/for-all [s1 (gen/not-empty gen/string)
@@ -516,10 +502,9 @@
   (prop/for-all [s string-ascii]
     (is (not (str/mixed-case? (str/upper-case s))))))

-(deftest mixed-case?-test
-  (are [expected actual] (= expected actual)
-    "FooBar" (str/mixed-case? "FooBar")
-    nil (str/mixed-case? "foo")))
+(defexamples mixed-case?-test
+  (str/mixed-case? "FooBar") "FooBar"
+  (str/mixed-case? "foo") nil)

 (def whitespace
   (gen/fmap #(apply str %)
@@ -533,12 +518,11 @@
     (is (= (+ (.length s) 2)
            (.length (str/collapse-whitespace (str ws1 s ws2)))))))

-(deftest collapse-whitespace-test
-  (are [expected actual] (= expected actual)
-    "foo bar baz" (str/collapse-whitespace "foo
-
-bar    	baz")
-    " foo bar baz " (str/collapse-whitespace " foo bar baz ")))
+(defexamples collapse-whitespace-test
+  (str/collapse-whitespace "foo
+
+bar    	baz") "foo bar baz"
+(str/collapse-whitespace " foo bar baz ") " foo bar baz ")

 (defspec levenshtein-distance-is-at-least-difference-between-string-lenghts 100
   (prop/for-all [s1 (gen/not-empty gen/string)
@@ -572,48 +556,44 @@
         (+ (str/distance s1 s3)
            (str/distance s2 s3)))))

-(deftest distance-test
-  (are [expected actual] (= expected actual)
-    0 (str/distance "foo" "foo")
-    0 (str/distance "foo" "foo" :levenshtein)
-    1 (str/distance "foo" "fo")
-    3 (str/distance "karolin" "kathrin" :hamming)
-    3 (str/distance "karolin" "kerstin" :hamming)
-    2 (str/distance "1011101" "1001001" :hamming)
-    3 (str/distance "2173896" "2233796" :hamming)
-    3 (str/distance "foo" "foobar" :hamming)))
+(defexamples distance-test
+  (str/distance "foo" "foo") 0
+  (str/distance "foo" "foo" :levenshtein) 0
+  (str/distance "foo" "fo") 1
+  (str/distance "karolin" "kathrin" :hamming) 3
+  (str/distance "karolin" "kerstin" :hamming) 3
+  (str/distance "1011101" "1001001" :hamming) 2
+  (str/distance "2173896" "2233796" :hamming) 3
+  (str/distance "foo" "foobar" :hamming) 3)

 (deftest distance-throws-on-unknown
   (is (thrown? IllegalArgumentException (str/distance :unknown-algorithm))))

 (deftest longest-common-substring
-  (are [expected actual] (= expected actual)
-    #{"foo" "bar"} (str/longest-common-substring "fooquxbar" "foobar")
-    #{"bar"} (str/longest-common-substring "FOOquxbar" "foobar")
-    #{} (str/longest-common-substring "foo" "bar")))
+  (str/longest-common-substring "fooquxbar" "foobar") #{"foo" "bar"}
+  (str/longest-common-substring "FOOquxbar" "foobar") #{"bar"}
+  (str/longest-common-substring "foo" "bar") #{})

 (defspec finds-common-substring 100
   (prop/for-all [s1 (gen/such-that #(< (count %) 5) (gen/not-empty gen/string)
-                                   500)
+                                   1000)
                  s2 (gen/such-that #(< (count %) 5) (gen/not-empty gen/string)
-                                   500)
+                                   1000)
                  lcs (gen/such-that #(> (count %) 5) (gen/not-empty gen/string)
-                                    500)]
+                                    1000)]
     (is ((str/longest-common-substring (str s1 lcs) (str lcs s2)) lcs))))

 (defspec length-test 100
   (prop/for-all [s gen/string]
     (is (= (.length s) (str/length s)))))

-(deftest index-of-test
-  (are [expected actual] (= expected actual)
-    0 (str/index-of "foo" "foo")
-    nil (str/index-of "foo" "bar")))
-
-(deftest last-index-of-test
-  (are [expected actual] (= expected actual)
-    nil (str/last-index-of "foo" "bar")
-    3 (str/last-index-of "foofoo" "foo")))
+(defexamples index-of-test
+  (str/index-of "foo" "foo") 0
+  (str/index-of "foo" "bar") nil)
+
+(defexamples last-index-of-test
+  (str/last-index-of "foo" "bar") nil
+  (str/last-index-of "foofoo" "foo") 3)

 (deftest added-metadata-is-removed-from-aliased-vars
   (is (not (:added (meta #'str/trim)))))


figwheel.repl: don't open browser for node target

--- figwheel-repl/src/figwheel/repl.cljc
+++ figwheel-repl/src/figwheel/repl.cljc
@@ -1066,12 +1066,13 @@

   ;; open a url
   (when-let [open-url
-             (when-let [url (:open-url repl-env)]
-               ;; TODO the host port thing needs to be fixed ealier
-               (fill-server-url-template
-                url
-                (merge (select-keys repl-env [:host :port])
-                       (select-keys (:ring-server-options repl-env) [:host :port]))))]
+             (and (not (= :nodejs (:target opts)))
+              (when-let [url (:open-url repl-env)]
+                ;; TODO the host port thing needs to be fixed ealier
+                (fill-server-url-template
+                 url
+                 (merge (select-keys repl-env [:host :port])
+                        (select-keys (:ring-server-options repl-env) [:host :port])))))]
     (println "Opening URL" open-url)
     (browse/browse-url open-url)))


Add test for linking the completion-hint to another entry.

--- test/cli4clj/test/cli.clj
+++ test/cli4clj/test/cli.clj
@@ -184,6 +184,16 @@
     (is (= "Arguments: my args" (.get arr-lst 0)))
     (is (= "test hint" (.get arr-lst 1)))))

+(deftest create-arg-hint-completers-link-hint-test
+  (let [cmd-map {:a {:foo "Linking to foo hint test."
+                     :completion-hint :foo}}
+        completers (create-arg-hint-completers cmd-map)
+        arr-lst (ArrayList.)]
+    (is (= 2 (.complete (nth completers 0) "a " 2 arr-lst)))
+    (is (= 2 (.size arr-lst)))
+    (is (= "Linking to foo hint test." (.get arr-lst 0)))
+    (is (= "" (.get arr-lst 1)))))
+

 (deftest simple-jline-readfn-mock-test