Minor typo fixed on components core

Minor typo fixed on components core

--- src/clj/hellhound/components/core.clj
+++ src/clj/hellhound/components/core.clj
@@ -150,7 +150,7 @@
   [system]
   ;; Read the configuration for the current runtime environment which
   ;; specified by `HH_ENV` environment. Default env is `:development`
-  (hellhound/load-runtime-configurations)
+  (hellhound/load-runtime-configuration)
   (iterate-components system start-component))

 (defn stop-system


Implement line drawing tool

Implement line drawing tool

--- src/cljs/goya/components/drawing.cljs
+++ src/cljs/goya/components/drawing.cljs
@@ -54,7 +54,12 @@
     (when (= paint-tool :brush)
       (om/transact! app
                     [:main-app :undo-history]
-                    #(conj % {:action (str "Painted Stroke") :icon "pencil"})
+                    #(conj % {:action (str "Painted Stroke") :icon "brush"})
+                    :add-to-undo))
+    (when (= paint-tool :line)
+      (om/transact! app
+                    [:main-app :undo-history]
+                    #(conj % {:action (str "Painted Line") :icon "pencil"})
                     :add-to-undo))
     (when (= paint-tool :box)
       (om/transact! app
@@ -297,6 +302,12 @@
       (visit-pixels-for-line-segment doc-x doc-y last-x last-y)
       (reset! guistate/transient-state
               (assoc @guistate/transient-state :last-mouse-pos [doc-x doc-y]))))
+
+  (when (= paint-tool :line)
+    (clear-preview-canvas)
+    (set! (.-fillStyle preview-context) paint-color)
+    (let [line-coords (bresenham/bresenham mouse-down-x mouse-down-y doc-x doc-y)]
+      (paint-pixels-for-pencil-tool line-coords pixel-size)))

   (when (= paint-tool :box)
     (set! (.-width preview-canvas-elem) (.-width preview-canvas-elem))
@@ -404,6 +415,10 @@
                               (assoc @guistate/transient-state :last-mouse-pos [])))
                     (when (= paint-tool :box)
                       (visit-pixels-for-rect-tool doc-x doc-y))
+                    (when (= paint-tool :line)
+                      (let [last-x ((get-in @guistate/transient-state [:mouse-down-pos]) 0)
+                            last-y ((get-in @guistate/transient-state [:mouse-down-pos]) 1)]
+                        (visit-pixels-for-line-segment doc-x doc-y last-x last-y)))
                     (when (= paint-tool :fill)
                       (visit-pixels-for-fill-tool doc-x doc-y (get-in @app [:main-app :image-data])))
                     (when (= paint-tool :picker)


handle live mode during MDA

handle live mode during MDA

git-svn-id: 03a8048b5ee8463be5048a3801110fb50f378627@8997 d0ab736e-dc22-4aeb-8dc9-08def0aa14fd

--- acqEngine/src/org/micromanager/acq_engine.clj
+++ acqEngine/src/org/micromanager/acq_engine.clj
@@ -430,11 +430,17 @@
   (when (and (@state :init-continuous-focus)
              (not (core isContinuousFocusEnabled)))
     (core enableContinuousFocus true))
+  (when (and (@state :live-mode-on)
+             (not (.isLiveModeOn gui)))
+    (.enableLiveMode gui true))
   (let [target-time (+ (@state :last-wake-time) interval-ms)
         delta (- target-time (jvm-time-ms))]
     (when (pos? delta)
       (interruptible-sleep delta))
     (await-resume)
+    (swap! state assoc :live-mode-on (.isLiveModeOn gui))
+    (when (.isLiveModeOn gui)
+      (.enableLiveMode gui false))
     (let [now (jvm-time-ms)
           wake-time (if (> now (+ target-time 10)) now target-time)]
       (state-assoc! :last-wake-time wake-time))))
@@ -532,6 +538,7 @@
              :pixel-size-um (core getPixelSizeUm)
              :source (core getCameraDevice)
              :pixel-type (get-pixel-type)
+             :live-mode-on (.isLiveModeOn gui)
       )))

 (defn cleanup []
@@ -551,6 +558,9 @@
       (when (and (@state :init-continuous-focus)
                  (not (core isContinuousFocusEnabled)))
         (core enableContinuousFocus true))
+      (when (and (@state :live-mode-on)
+                 (not (.isLiveModeOn gui)))
+        (.enableLiveMode gui true))
       (return-config))
     (catch Throwable t (ReportingUtils/showError t "Acquisition cleanup failed."))))


Don't talk to camera except when absolutely necessary, to avoid circular buffer overflow error for some fast bursts.

git-svn-id: 03a8048b5ee8463be5048a3801110fb50f378627@6920 d0ab736e-dc22-4aeb-8dc9-08def0aa14fd

--- acqEngine/src/org/micromanager/acq_engine.clj
+++ acqEngine/src/org/micromanager/acq_engine.clj
@@ -166,8 +166,8 @@
       (map-config (core getSystemStateCache))
       {
        "AxisPositions" (when-let [axes (get-in event [:position :axes])] (JSONObject. axes))
-       "Binning" (core getProperty (core getCameraDevice) "Binning")
-       "BitDepth" (core getImageBitDepth)
+       "Binning" (state :binning)
+       "BitDepth" (state :bit-depth)
        "Channel" (get-in event [:channel :name])
        "ChannelIndex" (:channel-index event)
        "ElapsedTime-ms" (elapsed-time state)
@@ -175,13 +175,13 @@
        "Frame" (:frame-index event)
        "Height" (state :init-height)
        "NextFrame" (:next-frame-index event)
-       "PixelSizeUm" (core getPixelSizeUm)
-       "PixelType" (get-pixel-type)
+       "PixelSizeUm" (state :pixel-size-um)
+       "PixelType" (state :pixel-type)
        "PositionIndex" (:position-index event)
        "PositionName" (if-let [pos (:position event)] (if-args #(.getLabel %) (get-msp pos)))
        "Slice" (:slice-index event)
        "SlicePosition" (:slice event)
-       "Source" (core getCameraDevice)
+       "Source" (state :source)
        "Time" (get-current-time-str)
        "UUID" (UUID/randomUUID)
        "Width"  (state :init-width)
@@ -304,8 +304,8 @@
         tags (if (and t (pos? t))
                (assoc tags "ElapsedTime-ms" t)
                (dissoc tags "ElapsedTime-ms"))]
-    (println "cam-t: " t ", collect-t: " (elapsed-time @state)
-         ", remaining-images: " (core getRemainingImageCount))
+   ; (println "cam-t: " t ", collect-t: " (elapsed-time @state)
+   ;      ", remaining-images: " (core getRemainingImageCount))
     {:pix pix :tags (dissoc tags "StartTime-ms")}))

 (defn collect-snap-image []
@@ -400,7 +400,13 @@
       :init-system-state (get-system-config-cached)
       :init-continuous-focus (core isContinuousFocusEnabled)
       :init-width (core getImageWidth)
-      :init-height (core getImageHeight))))
+      :init-height (core getImageHeight)
+      :binning (core getProperty (core getCameraDevice) "Binning")
+      :bit-depth (core getImageBitDepth)
+      :pixel-size-um (core getPixelSizeUm)
+      :source (core getCameraDevice)
+      :pixel-type (get-pixel-type)
+      )))

 (defn run-acquisition [this settings out-queue]
   (def acq-settings settings)


fix all crazy unquoted stuff problems

fix all crazy unquoted stuff problems

--- plugin/src/leiningen/figwheel.clj
+++ plugin/src/leiningen/figwheel.clj
@@ -60,7 +60,7 @@
   (run-local-project project all-builds
      '(require 'figwheel-sidecar.system)
      (if (fc/needs-lein-project-config?)
-       `(figwheel-sidecar.system/run-autobuilder ~autobuild-opts)
+       `(figwheel-sidecar.system/run-autobuilder '~autobuild-opts)
        `(figwheel-sidecar.system/load-config-run-autobuilder {:build-ids ~build-ids}))))

 (defn figwheel


fix namespace

fix namespace

--- src/babel/francais/qa.clj
+++ src/babel/francais/qa.clj
@@ -1,8 +1,8 @@
 (ns babel.francais.qa
   (:refer-clojure :exclude [get-in])
   (:require
+   [babel.francais :refer [analyze generate parse]]
    [babel.francais.grammar :refer [small medium]]
-   [babel.francais.workbook :refer [analyze generate parse]]
    [babel.engine :as engine]
    [babel.korma :as korma]
    [babel.francais.morphology :refer [fo]]


Read body to string

Read body to string

--- src/beehive_http/core.clj
+++ src/beehive_http/core.clj
@@ -33,7 +33,7 @@
 (defn parse-response [^NettyResponse response]
   {:status (.getStatusCode response)
    :headers (.getHeaders response)
-   :body (.getResponseBodyAsStream response)})
+   :body (.getResponseBody response)})

 (def response-transformer
   (reify Transformer


Use p/extract instead of from-maybe.

Use p/extract instead of from-maybe.

That implies performance improvement because
from-maybe is a public api and has additional
overhead with asserts.

--- src/cats/monad/maybe.cljc
+++ src/cats/monad/maybe.cljc
@@ -183,7 +183,7 @@
     (fmap [_ f mv]
       (if (nothing? mv)
         mv
-        (just (f (from-maybe mv)))))
+        (just (f (p/extract mv)))))

     p/Applicative
     (pure [_ v]
@@ -191,7 +191,7 @@
     (fapply [m af av]
       (if (nothing? af)
         af
-        (p/fmap m (from-maybe af) av)))
+        (p/fmap m (p/extract af) av)))

     p/Monad
     (mreturn [_ v]
@@ -199,7 +199,7 @@
     (mbind [_ mv f]
       (if (nothing? mv)
         mv
-        (f (from-maybe mv))))
+        (f (p/extract mv))))

     p/MonadZero
     (mzero [_]
@@ -247,7 +247,7 @@
                mv
                (fn [maybe-v]
                  (if (just? maybe-v)
-                   (f (from-maybe maybe-v))
+                   (f (p/extract maybe-v))
                    (p/mreturn inner-monad (nothing))))))

     p/MonadZero


fix previous commit

fix previous commit

--- src/chessground/drag.cljs
+++ src/chessground/drag.cljs
@@ -7,7 +7,7 @@
 (def over-class "drag-over")

 (defn get-target [pointer]
-  (if (common/is-touch-device)
+  (if common/is-touch-device
     (.elementFromPoint js/document (.-pageX pointer) (.-pageY pointer))
     (.-target pointer)))

@@ -18,7 +18,7 @@

 (defn- on-start [_ _ pointer]
   "Shift piece right under the cursor"
-  (when (false? (common/is-touch-device))
+  (when (false? common/is-touch-device)
     (let [$el ($ (get-target pointer))
           pos (.offset $el)
           center-x (+ (.-left pos) (/ (.width $el) 2))


[#2246] Fix arity function

[#2246] Fix arity function

--- backend/src/akvo/lumen/component/keycloak.clj
+++ backend/src/akvo/lumen/component/keycloak.clj
@@ -200,7 +200,7 @@
         (lib/ok (fetch-user-by-id headers api-root tenant user-id))
         (do
           (println (format "Tried to promote user: %s" user-id))
-          (lib/internal-server-error))))))
+          (lib/internal-server-error {}))))))

 (defn do-demote-user-from-admin
   [{:keys [api-root] :as keycloak} tenant author-claims user-id]
@@ -219,7 +219,7 @@
         (lib/ok (fetch-user-by-id headers api-root tenant user-id))
         (do
           (println (format "Tried to demote user: %s" user-id))
-          (lib/internal-server-error))))))
+          (lib/internal-server-error {}))))))

 (defn do-remove-user
   [{:keys [api-root] :as keycloak} tenant author-claims user-id]
@@ -238,7 +238,7 @@
         (lib/ok {})
         (do
           (println (format "Tried to remove user: %s" user-id))
-          (lib/internal-server-error))))))
+          (lib/internal-server-error {}))))))

 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; API Authorization


Adds evaluated literal support for sets, #41.

--- src/clj/cljs/compiler.clj
+++ src/clj/cljs/compiler.clj
@@ -239,6 +239,12 @@
   [{:keys [children env]}]
   (emit-wrap env
     (print (str "cljs.core.Vector.fromArray(["
+                (comma-sep (map emits children)) "])"))))
+
+(defmethod emit :set
+  [{:keys [children env]}]
+  (emit-wrap env
+    (print (str "cljs.core.set(["
                 (comma-sep (map emits children)) "])"))))

 (defmethod emit :constant
@@ -831,6 +837,12 @@
         items (disallowing-recur (vec (map #(analyze expr-env % name) form)))]
     (analyze-wrap-meta {:op :vector :env env :form form :children items} name)))

+(defn analyze-set
+  [env form name]
+  (let [expr-env (assoc env :context :expr)
+        items (disallowing-recur (vec (map #(analyze expr-env % name) form)))]
+    (analyze-wrap-meta {:op :set :env env :form form :children items} name)))
+
 (defn analyze-wrap-meta [expr name]
   (let [form (:form expr)]
     (if (meta form)
@@ -858,6 +870,7 @@
         (and (seq? form) (seq form)) (analyze-seq env form name)
         (map? form) (analyze-map env form name)
         (vector? form) (analyze-vector env form name)
+        (set? form) (analyze-set env form name)
         :else {:op :constant :env env :form form}))))

 (defn eval1


Kahn sort private functions

Kahn sort private functions

--- src/clj/sparqlab/util.clj
+++ src/clj/sparqlab/util.clj
@@ -22,17 +22,19 @@
 ;; the terms of this license.
 ;; You must not remove this notice, or any other, from this software.

-(defn without
+; ----- Private functions -----
+
+(defn- without
   "Returns set s with x removed."
   [s x] (difference s #{x}))

-(defn take-1
+(defn- take-1
   "Returns the pair [element, s'] where s' is set s with element removed."
   [s] {:pre [(not (empty? s))]}
   (let [item (first s)]
     [item (without s item)]))

-(defn no-incoming
+(defn- no-incoming
   "Returns the set of nodes in graph g for which there are no incoming
   edges, where g is a map of nodes to sets of nodes."
   [g]
@@ -40,12 +42,14 @@
         have-incoming (apply union (vals g))]
     (difference nodes have-incoming)))

-(defn normalize
+(defn- normalize
   "Returns g with empty outgoing edges added for nodes with incoming
   edges only.  Example: {:a #{:b}} => {:a #{:b}, :b #{}}"
   [g]
   (let [have-incoming (apply union (vals g))]
     (reduce #(if (get % %2) % (assoc % %2 #{})) g have-incoming)))
+
+; ----- Public functions -----

 (defn kahn-sort
   "Proposes a topological sort for directed graph g using Kahn's


Little bit more reaction optimization

Little bit more reaction optimization

--- src/clj/zetawar/subs.cljs
+++ src/clj/zetawar/subs.cljs
@@ -32,8 +32,17 @@
 (deftrack game-eid [conn]
   (-> @(app conn) :app/game e))

+(def game-pull [:game/credits-per-base
+                :game/max-count-per-unit
+                :game/scenario-id
+                :game/map
+                {:game/factions []}
+                :game/starting-faction
+                :game/current-faction
+                :game/round])
+
 (deftrack game [conn]
-  @(posh/pull conn '[*] @(game-eid conn)))
+  @(posh/pull conn game-pull @(game-eid conn)))

 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; Map
@@ -115,13 +124,9 @@
 ;;; Factions

 (deftrack faction-eids [conn]
-  (->> @(posh/q '[:find ?f
-                  :where
-                  [_  :app/game ?g]
-                  [?g :game/factions ?f]]
-                conn)
-       (map first)
-       (into [])))
+  (into []
+        (map e)
+        (:game/factions @(game conn))))

 (def faction-pull '[:faction/color
                     :faction/credits


revert index-cigar change

revert index-cigar change

--- src/cljam/algo/pileup.clj
+++ src/cljam/algo/pileup.clj
@@ -35,51 +35,34 @@
   [^long start ^long end alns]
   (seq-step start end 0 alns))

-(defn- two-vec-transducer
-  [transform-fn]
-  (fn [rf]
-    (let [va (volatile! (transient []))
-          vb (volatile! (transient []))]
-      (fn
-        ([] (rf))
-        ([acc]
-         (-> acc
-             (rf (persistent! @va))
-             (rf (persistent! @vb))))
-        ([acc x]
-         (let [[a b] (transform-fn x)]
-           (vswap! va conj! a)
-           (vswap! vb conj! b)
-           acc))))))
-
-(defn- index-to-seq-qual
-  [^String seqs ^String quals]
-  (let [empty-qual? (and (= (.length quals) 1)
-                         (= (.charAt quals 0) \*))]
-    (fn [[op x xs]]
-      (let [c (if (number? x) (.charAt seqs x) x)
-            q (if (or empty-qual? (char? x))
-                93
-                (qual/fastq-char->phred-byte (.charAt quals x)))]
-        (if (= op :m)
-          [[c] q]
-          (if (= op :d)
-            [[c xs] q]
-            [[c (subs seqs (first xs) (last xs))] q]))))))
+(defn- quals-at-ref
+  [idx ^String qual]
+  (let [empty-qual? (and (= (.length qual) 1)
+                         (= (.charAt qual 0) \*))]
+    (if empty-qual?
+      (vec (repeat (count idx) 93)) ;; \~
+      (mapv (fn [[_ x]]
+              (if (number? x)
+                (qual/fastq-char->phred-byte (.charAt qual x))
+                93))
+            idx))))
+
+(defn- seqs-at-ref
+  [idx ^String s]
+  (mapv (fn [[op x xs]]
+          (let [c (if (number? x) (.charAt s x) x)]
+            (case op
+              :m [c]
+              :d [c xs]
+              :i [c (subs s (first xs) (last xs))]))) idx))

 (defn index-cigar
   "Align bases and base quality scores with the reference coordinate."
   [^SAMAlignment aln]
-  (let [idx (cigar/to-index (.cigar aln))
-        ^String seqs (:seq aln)
-        ^String quals (.qual aln)
-        [seqs-at-ref quals-at-ref] (into []
-                                         (two-vec-transducer
-                                          (index-to-seq-qual seqs quals))
-                                         idx)]
+  (let [idx (cigar/to-index (.cigar aln))]
     (assoc aln
-           :seqs-at-ref seqs-at-ref
-           :quals-at-ref quals-at-ref)))
+           :seqs-at-ref (seqs-at-ref idx (:seq aln))
+           :quals-at-ref (quals-at-ref idx (.qual aln)))))

 (defn basic-mpileup-pred
   "Basic predicate function for filtering alignments for mpileup."
@@ -133,6 +116,23 @@
     (->> #(<= min-base-quality (.qual ^PileupBase %))
          (partial filterv)
          (update p 1))))
+
+(defn- unzip-2
+  [transform-fn]
+  (fn [rf]
+    (let [va (volatile! (transient []))
+          vb (volatile! (transient []))]
+      (fn
+        ([] (rf))
+        ([acc]
+         (-> acc
+             (rf (persistent! @va))
+             (rf (persistent! @vb))))
+        ([acc x]
+         (let [[a b] (transform-fn x)]
+           (vswap! va conj! a)
+           (vswap! vb conj! b)
+           acc))))))

 (defn- merge-corrected-quals
   "Merge corrected quals with the uncorrected part."
@@ -176,8 +176,7 @@
     (when (and (pos? (.pnext a1))
                (<= correct-start (.end a1)))
       (let [[quals1 quals2] (into []
-                                  (two-vec-transducer
-                                   (correct-pair-qual a1 a2))
+                                  (unzip-2 (correct-pair-qual a1 a2))
                                   (range correct-start (inc correct-end)))
             new-quals1 (merge-corrected-quals a1
                                               correct-start


Whitespace

Whitespace

--- src/cljs/geoforms/db.cljs
+++ src/cljs/geoforms/db.cljs
@@ -195,72 +195,72 @@

 (def app-cms
   (atom
-   {:en {:page-title      "Support local ideas!"
-         :page-subtitle   ""
-         :h-district "Step 1. Choose your district"
-         :h-vote     "Step 2. Check ideas you want to support!"
-         :h-add      "Step 3. Add your own ideas"
-         :h-sign     "Step 4. Sign your choices"
-         :person.alert-ideas?    "I want to get noticed about my supported ideas progress."
-         :person.alert-volunteer?  "I want to get noticed about volonteer opportunities regarding ideas I voted for."
-         :person.alert-districts?  "I want to get noticed about major updates in my supported district(s)."
-         :comments "Comments"
-         :email "Email"
-         :last-name "Last name"
-         :first-name "First name"
-         :age "Age"
-         :add-description "Add a description"
-         :add-idea "Add your idea"
-         :optional "(optional)"
-         :idea-title "Idea title"
-         :add-idea-to "Add idea to"
-         :idea "idea"
-         :select-category "Select a category..."
-         :add-url "Add an URL"
-         :select-age "Select your age..."
-         :submit "Submit"
-         :not-valid "not valid"
-         :required "required"
-         :not-set "not set"
-         :already-used "already used"
-         :title "Title"
-         :category "Category"
-         :yes "Yes"
-         :no "No"
+   {:en {:page-title                  "Support local ideas!"
+         :page-subtitle               ""
+         :h-district                  "Step 1. Choose your district"
+         :h-vote                      "Step 2. Check ideas you want to support!"
+         :h-add                       "Step 3. Add your own ideas"
+         :h-sign                      "Step 4. Sign your choices"
+         :person.alert-ideas?         "I want to get noticed about my supported ideas progress."
+         :person.alert-volunteer?     "I want to get noticed about volonteer opportunities regarding ideas I voted for."
+         :person.alert-districts?     "I want to get noticed about major updates in my supported district(s)."
+         :comments                    "Comments"
+         :email                       "Email"
+         :last-name                   "Last name"
+         :first-name                  "First name"
+         :age                         "Age"
+         :add-description             "Add a description"
+         :add-idea                    "Add your idea"
+         :optional                    "(optional)"
+         :idea-title                  "Idea title"
+         :add-idea-to                 "Add idea to"
+         :idea                        "idea"
+         :select-category             "Select a category..."
+         :add-url                     "Add an URL"
+         :select-age                  "Select your age..."
+         :submit                      "Submit"
+         :not-valid                   "not valid"
+         :required                    "required"
+         :not-set                     "not set"
+         :already-used                "already used"
+         :title                       "Title"
+         :category                    "Category"
+         :yes                         "Yes"
+         :no                          "No"
          :new-ideas-saved-once-signed "Your new idea(s) will be saved once you sign and submit (see the bottom of the form)"
          }
-    :fr {:page-title      "Appuyez et suggérez des idées pour votre quartier!"
-         :page-subtitle   ""
-         :h-district "Étape 1. Choisissez votre quartier (ou vos quartiers)"
-         :h-vote     "Étape 2. Cochez les idées que vous souhaitez appuyer!"
-         :h-add      "Étape 3. Ajoutez vos idées"
-         :h-sign     "Étape 4. Signez vos choix"
-         :person.alert-ideas?    "Je veux être tenu au courant de la progression des idées que j'ai appuyées."
-         :person.alert-volunteer?  "Je veux être informé des opportunités de bénévolat entourant les idées que j'ai appuyées."
-         :person.alert-districts?  "Je veux être informé de développements majeurs sur les activités de Votepour.ca dans mon quartier."
-         :comments "Commentaires"
-         :email "Courriel"
-         :last-name "Nom"
-         :first-name "Prénom"
-         :age "Âge"
-         :add-description "Ajoutez une description"
-         :add-idea "Ajoutez votre idée"
-         :optional "(facultatif)"
-         :idea-title "Titre de l'idée"
-         :add-idea-to "Ajoutez une idée à"
-         :idea "idée"
-         :select-category "Choisissez une catégorie"
-         :add-url "Ajoutez une URL de reférence"
-         :select-age "Selectionnez votre âge"
-         :submit "Envoyez"
-         :not-valid "non valide"
-         :required "requis"
-         :not-set "non choisie"
-         :already-used "déjà utilisé"
-         :title "Titre"
-         :category "Catégorie"
-         :yes "Oui"
-         :no "Non"
+    :fr {:page-title                  "Appuyez et suggérez des idées pour votre quartier!"
+         :page-subtitle               ""
+         :h-district                  "Étape 1. Choisissez votre quartier (ou vos quartiers)"
+         :h-vote                      "Étape 2. Cochez les idées que vous souhaitez appuyer!"
+         :h-add                       "Étape 3. Ajoutez vos idées"
+         :h-sign                      "Étape 4. Signez vos choix"
+         :person.alert-ideas?         "Je veux être tenu au courant de la progression des idées que j'ai appuyées."
+         :person.alert-volunteer?     "Je veux être informé des opportunités de bénévolat entourant les idées que j'ai appuyées."
+         :person.alert-districts?     "Je veux être informé de développements majeurs sur les activités de Votepour.ca dans mon quartier."
+         :comments                    "Commentaires"
+         :email                       "Courriel"
+         :last-name                   "Nom"
+         :first-name                  "Prénom"
+         :age                         "Âge"
+         :add-description             "Ajoutez une description"
+         :add-idea                    "Ajoutez votre idée"
+         :optional                    "(facultatif)"
+         :idea-title                  "Titre de l'idée"
+         :add-idea-to                 "Ajoutez une idée à"
+         :idea                        "idée"
+         :select-category             "Choisissez une catégorie"
+         :add-url                     "Ajoutez une URL de reférence"
+         :select-age                  "Selectionnez votre âge"
+         :submit                      "Envoyez"
+         :not-valid                   "non valide"
+         :required                    "requis"
+         :not-set                     "non choisie"
+         :already-used                "déjà utilisé"
+         :title                       "Titre"
+         :category                    "Catégorie"
+         :yes                         "Oui"
+         :no                          "Non"
          :new-ideas-saved-once-signed "Votre idée ou vos idées seront sauvegardées dès que vous signerez et enverrez vos choix (voir le bas du formulaire)"}}))


french wording correction

french wording correction

--- src/cljs/geoforms/db.cljs
+++ src/cljs/geoforms/db.cljs
@@ -303,7 +303,7 @@
          :add-idea                    "Ajoutez votre idée"
          :optional                    "(facultatif)"
          :idea-title                  "Titre de l'idée"
-         :add-idea-to                 "Ajoutez une idée à"
+         :add-idea-to                 "Ajoutez une idée pour"
          :idea                        "idée"
          :select-category             "Choisissez une catégorie"
          :add-url                     "Ajoutez une URL de reférence"


into - converts data structures into other data structures

--- src/clojure_noon/core.clj
+++ src/clojure_noon/core.clj
@@ -659,4 +659,9 @@
 (time (identify-vampire (range 0 1000000)))

 ;;Infinite sequences
-(concat (take 8 (repeat "na")) ["Batman!"])
+(concat (take 8 (repeat "na")) ["Batman!"]) ;; that repeat "na" is an infinite series of "na"s
+(take 3 (repeatedly (fn [] (rand-int 10)))) ;; repeatedly call a function that returns a random integer from 0 10 and then take 3 of those numbers
+
+(defn even-numbers
+  ([] (even-numbers 0))
+  ([n] (cons n (lazy-seq (even-numbers (+ n 2))))))


Add addition to numbers

Add addition to numbers

--- src/clojure_ruby/core.clj
+++ src/clojure_ruby/core.clj
@@ -58,7 +58,10 @@
   {:data n
    :methods {"<" (fn number-lt [this other]
                    (let [other (host-send other :number)]
-                     (< (:data this) other)))}
+                     (< (:data this) other)))
+             "+" (fn number-plus [this other]
+                   (let [other (host-send other :number)]
+                     (create-number (+ (:data this) other))))}
    :host-methods {:number (fn number-host-number [this] (:data this))}})

 (defn create-string [s]


Updated calendar module: doc etc.

Updated calendar module: doc etc.

--- src/clouseau/calendar.clj
+++ src/clouseau/calendar.clj
@@ -1,7 +1,7 @@
 ;;;
 ;;;   Clouseau
 ;;;
-;;;    Copyright (C) 2015 Pavel Tisnovsky <ptisnovs@redhat.com>
+;;;    Copyright (C) 2015, 2016  Pavel Tisnovsky <ptisnovs@redhat.com>
 ;;;
 ;;; Clouseau is free software; you can redistribute it and/or modify
 ;;; it under the terms of the GNU General Public License as published by
@@ -44,7 +44,7 @@

 (defn get-calendar
-    "Gets a calendar using the default time zone and default locale."
+    "Gets an instance of calendar using the default time zone and default locale."
     []
     (java.util.Calendar/getInstance))

@@ -55,7 +55,7 @@
         (.format date-format (.getTime calendar))))

 (defn format-date-time
-    "Format given date using the following format: 'yyyy-MM-dd HH:mm:ss'"
+    "Format given date using the following date/time format: 'yyyy-MM-dd HH:mm:ss'"
     [calendar]
     (format-date-using-desired-format calendar "yyyy-MM-dd HH:mm:ss"))


Add tests for Druid min / max aggregations using metrics :lightning:

--- test/metabase/timeseries_query_processor_test.clj
+++ test/metabase/timeseries_query_processor_test.clj
@@ -25,7 +25,7 @@
   []
   (doseq [engine event-based-dbs]
     (datasets/with-engine-when-testing engine
-      (data/do-with-temp-db (flattened-db-def) (fn [& _])))))
+      (data/do-with-temp-db (flattened-db-def) (constantly nil)))))

 (defmacro ^:private with-flattened-dbdef [& body]
   `(data/with-temp-db [~'_ (flattened-db-def)]
@@ -679,11 +679,19 @@

 ;;; MIN & MAX

+;; tests for dimension columns
+(expect-with-timeseries-dbs [4.0] (first-row (data/run-query checkins
+                                               (ql/aggregation (ql/max $venue_price)))))
+
 (expect-with-timeseries-dbs [1.0] (first-row (data/run-query checkins
-                                             (ql/aggregation (ql/min $venue_price)))))
-
-(expect-with-timeseries-dbs [4.0] (first-row (data/run-query checkins
-                                             (ql/aggregation (ql/max $venue_price)))))
+                                               (ql/aggregation (ql/min $venue_price)))))
+
+;; tests for metric columns
+(expect-with-timeseries-dbs [1.0] (first-row (data/run-query checkins
+                                               (ql/aggregation (ql/max $count)))))
+
+(expect-with-timeseries-dbs [1.0] (first-row (data/run-query checkins
+                                               (ql/aggregation (ql/min $count)))))

 (expect-with-timeseries-dbs
   [["1" 34.0071] ["2" 33.7701] ["3" 10.0646] ["4" 33.983]] ; some sort of weird quirk w/ druid where all columns in breakout get converted to strings


better error when browse-url fails in browser-repl

--- src/main/shadow/cljs/devtools/api.clj
+++ src/main/shadow/cljs/devtools/api.clj
@@ -544,7 +544,12 @@
                (worker/watch worker out-chan)
                (worker/compile! worker)

-               (browse-url (str "http" (when (:ssl http) "s") "://localhost:" (:port http) "/browser-repl-js"))
+               (let [url (str "http" (when (:ssl http) "s") "://localhost:" (:port http) "/browser-repl-js")]
+                 (try
+                   (browse-url url)
+                   (catch Exception e
+                     (println
+                       (format "Failed to open Browser automatically.\nPlease open the URL below in your Browser:\n\t%s" url)))))
                worker))]

      (repl :browser-repl)


Remove unneeded and possibly conflicting user.cljs

--- lein-light-nrepl/src/user.cljs
+++ lein-light-nrepl/src/user.cljs
@@ -1 +0,0 @@
-(ns user)


Fix cgens to deal with the case where arg names were overridden by the explicit bindings created with #'with-overloaded-ugens.

--- src/overtone/sc/machinery/defcgen.clj
+++ src/overtone/sc/machinery/defcgen.clj
@@ -3,7 +3,8 @@
       :author "Sam Aaron"}
   overtone.sc.machinery.defcgen

-  (:use [overtone.util lib]
+  (:use [clojure.walk :as walk]
+        [overtone.util lib]
         [overtone.sc.machinery.ugen fn-gen doc defaults]))

 (defn parse-cgen-params
@@ -77,15 +78,22 @@

     [summary doc params rated-bodies default-rate]))

+(defn- syms->sym-gensym-pairs
+  "Given a seq of symbols, creates a new seq of symbol gensym pairs"
+  [syms]
+  (into {} (doall (map (fn [sym] [(symbol (name sym)) (gensym (name sym))]) syms))))
+
 (defn- mk-cgen-fn
   "Make the function which gets executed when a cgen is called."
   [param-names defaults body]
-  (let [args     (gensym 'args)
-        bindings (reduce (fn [final param]
-                           (conj final (symbol (name param)) `(get (arg-mapper ~args ~param-names ~defaults) ~param)))
-                         []
-                         param-names)]
-    `(fn [& ~args]
+  (let [arg-sym     (gensym 'arg-sym)
+        sym-gensyms (syms->sym-gensym-pairs param-names)
+        bindings    (reduce (fn [final param]
+                              (conj final (get sym-gensyms (symbol (name param))) `(get (arg-mapper ~arg-sym ~param-names ~defaults) ~param)))
+                            []
+                            param-names)
+        body        (walk/prewalk-replace sym-gensyms body )]
+    `(fn [& ~arg-sym]
        (let [~@bindings]
          (with-overloaded-ugens
            ~body)))))


:bug: Properly handle grouping from multiple frames.


extractor explores

extractor explores

--- src/crawler/extractor.clj
+++ src/crawler/extractor.clj
@@ -24,6 +24,12 @@
   (map
    (fn [[xpath anchor]])))

+(defn explore-record-xpath
+  "For each record picked up we explore
+it and bubble scores up"
+  [[xpath records]]
+  records)
+
 (defn process-page
   "Store the page signature"
   [url body]
@@ -44,4 +50,19 @@
              clojure.set/union
              (into
               {}
-              (map vector xpaths records-anchors))))))
+              (map vector xpaths records-anchors)))
+
+      ;; now the exploration begins
+      (let [explore-strategy (reverse
+                              (sort-by
+                               (fn [xpath]
+                                 (count (@*xpath-records* xpath)))
+                               xpaths))
+            explore-records  (map
+                              (fn [xpath]
+                                (map-xpaths-records xpath))
+                              explore-strategy)]
+        (map
+         vector
+         explore-strategy
+         explore-records)))))


Phew works

Phew works

- Any size window
- Viewport created inside with correct aspect ratio
- And set up for ortho drawing
- Game and system seperated properly
- Next up
    - some minor boxy demo
    - cljs working in parity

--- go-fighweel/src/cloj/lwjgl/render.clj
+++ go-fighweel/src/cloj/lwjgl/render.clj
@@ -44,6 +44,8 @@
                 (:x vp-dims)
                 (:y vp-dims) ] )
      }))
+
+(get-vp (v2f 640 480) (v2f 16 9) )

 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 (defn draw-quad [x y w h r g b a]
@@ -115,7 +117,7 @@
             (GLU/gluOrtho2D 0 (:x canv-dims) 0 (:y canv-dims))
             (GL11/glMatrixMode GL11/GL_MODELVIEW)
             (GL11/glLoadIdentity)
-            (GL11/glScalef  scale scale 1)
+            ; (GL11/glScalef  scale scale 1)
             )))

       (save!    [this] (GL11/glPushMatrix))


Fix delete button, also cleanup

Fix delete button, also cleanup

--- src/cljs/org/broadinstitute/firecloud_ui/page/workspace/method_config_editor.cljs
+++ src/cljs/org/broadinstitute/firecloud_ui/page/workspace/method_config_editor.cljs
@@ -124,32 +124,36 @@
                                             :width 290}}
      [:div {:style {:fontSize "106%" :lineHeight 1 :textAlign "center"}}

-      [:div {:style {:display (when editing? "none") :padding "0.7em 0" :cursor "pointer"
-                     :backgroundColor "transparent" :color (:button-blue style/colors)
-                     :border (str "1px solid " (:line-gray style/colors))}
-             :onClick #(swap! state assoc :editing? true :prereqs-list (vals (config "prerequisites")))}
-       [:span {:style {:display "inline-block" :verticalAlign "middle"}}
-        (icons/font-icon {:style {:fontSize "135%"}} :pencil)]
-       [:span {:style {:marginLeft "1em"}} "Edit this page"]]
-
-      [DeleteButton
-       {:workspace-id (:workspace-id props)
-        :on-rm (:on-rm props)
-        :config config}]
-
-      [:div {:style {:display (when-not editing? "none") :padding "0.7em 0" :cursor "pointer"
-                     :backgroundColor (:success-green style/colors) :color "#fff" :borderRadius 4}
-             :onClick #(do (commit state refs config props) (stop-editing state refs))}
-       [:span {:style {:display "inline-block" :verticalAlign "middle"}}
-        (icons/font-icon {:style {:fontSize "135%"}} :status-done)]
-       [:span {:style {:marginLeft "1em"}} "Save"]]
-
-      [:div {:style {:display (when-not editing? "none") :padding "0.7em 0" :marginTop "0.5em" :cursor "pointer"
-                     :backgroundColor (:exception-red style/colors) :color "#fff" :borderRadius 4}
-             :onClick #(stop-editing state refs)}
-       [:span {:style {:display "inline-block" :verticalAlign "middle"}}
-        (icons/font-icon {:style {:fontSize "135%"}} :x)]
-       [:span {:style {:marginLeft "1em"}} "Cancel Editing"]]])])
+      (when-not editing?
+        [:div {:style {:padding "0.7em 0" :cursor "pointer"
+                       :backgroundColor "transparent" :color (:button-blue style/colors)
+                       :border (str "1px solid " (:line-gray style/colors))}
+               :onClick #(swap! state assoc :editing? true :prereqs-list (vals (config "prerequisites")))}
+         [:span {:style {:display "inline-block" :verticalAlign "middle"}}
+          (icons/font-icon {:style {:fontSize "135%"}} :pencil)]
+         [:span {:style {:marginLeft "1em"}} "Edit this page"]])
+
+      (when-not editing?
+        [DeleteButton
+         {:workspace-id (:workspace-id props)
+          :on-rm (:on-rm props)
+          :config config}])
+
+      (when editing?
+        [:div {:style {:padding "0.7em 0" :cursor "pointer"
+                       :backgroundColor (:success-green style/colors) :color "#fff" :borderRadius 4}
+               :onClick #(do (commit state refs config props) (stop-editing state refs))}
+         [:span {:style {:display "inline-block" :verticalAlign "middle"}}
+          (icons/font-icon {:style {:fontSize "135%"}} :status-done)]
+         [:span {:style {:marginLeft "1em"}} "Save"]])
+
+      (when editing?
+        [:div {:style {:padding "0.7em 0" :marginTop "0.5em" :cursor "pointer"
+                       :backgroundColor (:exception-red style/colors) :color "#fff" :borderRadius 4}
+               :onClick #(stop-editing state refs)}
+         [:span {:style {:display "inline-block" :verticalAlign "middle"}}
+          (icons/font-icon {:style {:fontSize "135%"}} :x)]
+         [:span {:style {:marginLeft "1em"}} "Cancel Editing"]])])])

 (defn- render-launch-analysis [state workspace-id editing?]
   [:div {:style {:width 200 :float "right" :display (when editing? "none")}}


Tweak logging when a conn closes on JVM shutdown

The library that gniazdo wraps (the Jetty WebSocket API/Client)
registers a JVM shutdown hook to (I think) close the connection when the
JVM shuts down. This causes the on-close hook to be called during JVM
shutdown. Currently, this is causing a WARN log message to be emitted
(written to stdout) when the JVM shuts down — even if the WS connection
has been closed beforehand.

Since this case (code 1001 and reason "Shutdown") is a normal,
uninteresting case, it will be better to use the :info log level, so
that in this case the log message will not be output when the JVM shuts
down.

--- src/clj_chrome_devtools/impl/connection.clj
+++ src/clj_chrome_devtools/impl/connection.clj
@@ -78,12 +78,22 @@
                  ", throwable: " t))))

 (defn- on-close [code reason]
-  (log/log (case code 1000 :info :warn)
-           "WebSocket connection closed with status code"
-           code
-           (str "(" (ws-close-status-code->reason code "UNKNOWN") ")")
-           "and reason:"
-           reason))
+  (log/log
+   ; The library that gniazdo wraps (the Jetty WebSocket API/Client) registers a
+   ; JVM shutdown hook to (I think) close the connection when the JVM shuts
+   ; down. This causes the on-close hook to be called during JVM shutdown. Since
+   ; this case (code 1001 and reason "Shutdown") is a normal, uninteresting
+   ; case, we want to use the :info log level. Same for when the code is 1000,
+   ; which is specifically for normal closures. Otherwise, we’ll use :warn
+   ; because the closure would appear to be abnormal.
+   (if (or (= code 1000)
+           (and (= code 1001) (re-seq #"(?i)shutdown" reason)))
+     :info :warn)
+   "WebSocket connection closed with status code"
+   code
+   (str "(" (ws-close-status-code->reason code "UNKNOWN") ")")
+   "and reason:"
+   reason))

 (defn- wait-for-remote-debugging-port [host port max-wait-time-ms]
   (let [wait-until (+ (System/currentTimeMillis) max-wait-time-ms)


comment instrumentation

comment instrumentation

--- src/cljs/daveconservatoire/support/dev.cljs
+++ src/cljs/daveconservatoire/support/dev.cljs
@@ -6,4 +6,4 @@
   (devtools.core/install! [:custom-formatters :sanity-hints]))

 (defn reload-cycle []
-  (s/instrument-all))
+  #_ (s/instrument-all))


The most useful functions from the core have been done.

--- src/clojure/uncomplicate/clojurecl/core.clj
+++ src/clojure/uncomplicate/clojurecl/core.clj
@@ -7,8 +7,8 @@
             [clojure.core.async :refer [go >!]])
   (:import [org.jocl CL cl_platform_id cl_context_properties cl_device_id
             cl_context cl_command_queue cl_mem cl_program cl_kernel cl_sampler
-            cl_event
-            Sizeof Pointer CreateContextFunction EventCallbackFunction]
+            cl_event Sizeof Pointer CreateContextFunction EventCallbackFunction
+            BuildProgramFunction]
            [java.nio ByteBuffer ByteOrder]))

 (def ^:dynamic *platform*)
@@ -334,7 +334,7 @@
      (loop [i 5 es es]
        (if (< i len)
          (do (aset res i (first es))
-             (recur (inc i) (rest es)))
+             (recur (inc i) (next es)))
          res)))))

 (defrecord EventCallbackInfo [event status data])
@@ -363,6 +363,13 @@
    (set-event-callback channel e CL/CL_COMPLETE nil)))

 ;; ============= Program ==========================================
+
+(defrecord BuildCallbackInfo [program data])
+
+(deftype BuildCallback [ch]
+  BuildProgramFunction
+  (function [this program data]
+    (go (>! ch (->BuildCallbackInfo program data)))))

 (defn program-with-source
   ([context source]
@@ -374,10 +381,26 @@
   ([source]
    (program-with-source *context* source)))

-;; TODO Callback function
-(defn build-program! [program]
-  (let [err (CL/clBuildProgram program 0 nil nil nil nil)]
-    (with-check err program)))
+(defn build-program!
+  ([ch program devices options user-data]
+   (with-check (CL/clBuildProgram program (count devices)
+                                  (if devices
+                                    (into-array cl_device_id devices)
+                                    nil)
+                                  options
+                                  (if ch (->BuildCallback ch) nil)
+                                  user-data)
+     ch))
+  ([ch program devices options]
+   (build-program! ch program devices options nil))
+  ([ch program options]
+   (build-program! ch program nil options nil))
+  ([ch program]
+   (build-program! ch program nil nil nil))
+  ([program]
+   (do
+     (build-program! nil program nil nil nil)
+     program)))

 ;; ============== Kernel =========================================

@@ -387,7 +410,7 @@
     (with-check err (aget res 0))))

 (defn kernels
-  ([program ^String name]
+  ([program name]
    (let [err (int-array 1)
          res (CL/clCreateKernel program name err)]
      (with-check-arr err res)))
@@ -397,17 +420,29 @@
          err (CL/clCreateKernelsInProgram program nk res nil)]
      (vec (with-check err res)))))

-(defn set-arg! [kernel ^long n memory]
-  (set-arg memory kernel n))
-
-(defn set-args! [kernel & memories]
-  (do (reduce (fn [^long i mem]
-               (do
-                 (set-arg! kernel i mem)
-                 (inc i)))
-             0
-             memories)
-      kernel))
+(defn set-arg! [kernel n cl-mem]
+  (set-arg cl-mem kernel n))
+
+(defn set-args!
+  ([kernel cl-mem]
+   (set-arg! kernel 0 cl-mem))
+  ([kernel cl-mem-0 cl-mem-1]
+   (-> (set-arg! kernel 0 cl-mem-0)
+       (set-arg! 1 cl-mem-1)))
+  ([kernel cl-mem-0 cl-mem-1 cl-mem-2]
+   (-> (set-arg! kernel 0 cl-mem-0)
+       (set-arg! 1 cl-mem-1)
+       (set-arg! 2 cl-mem-2)))
+  ([kernel cl-mem-0 cl-mem-1 cl-mem-2 cl-mem-3 & cl-mems]
+   (let [ker (-> (set-arg! kernel 0 cl-mem-0)
+                 (set-arg! 1 cl-mem-1)
+                 (set-arg! 2 cl-mem-2)
+                 (set-arg! 3 cl-mem-3))]
+     (loop [i 4 cl-mems cl-mems]
+       (if-let [mem (first cl-mems)]
+         (do (set-arg! ker i mem)
+             (recur (inc i) (next cl-mems)))
+         ker)))))

 ;; ============== Command Queue ===============================


reverted the previous change

reverted the previous change

--- src/mafia_votecount/scraper/team_liquid.clj
+++ src/mafia_votecount/scraper/team_liquid.clj
@@ -271,7 +271,8 @@
                              (is-cycle-change?))
         day-ranges (-> cycle-posts
                        (keys)
-                       (#(if (or (and (= cycle-type :day) (= first-cycle-type :day)) (and (= cycle-type :night) (= first-cycle-type :night)))
+                       ; hosts can just add a line break before the night 0 message though so maybe not so important after all...?
+                       (#(if (= cycle-type :day)
                            (cons (dec first-index) %)
                            %))
                        (to-day-ranges))


Wrote more robust get-page-numbers function

--- src/mafia_votecount/scraper/team_liquid.clj
+++ src/mafia_votecount/scraper/team_liquid.clj
@@ -45,16 +45,13 @@
       ((fn [pages] (remove string? pages)))
       ((fn [pages] (map #(first (get % :content)) pages)))))

-;; TODO: Test this
 (defn- get-page-numbers [content]
-  (let [three-last (->> (get-page-number-bar content)
-             (take-last 3))] ;; NOTE: This fails if last one is "All"
-    (cond
-      (= (first three-last) "1") [1]
-      (= (last three-last) "All")
-      (vec (range 1 (inc (Integer/parseInt (first three-last)))))
-      (= (last three-last) "Next")
-      (vec (range 1 (inc (Integer/parseInt (nth three-last 1))))))))
+  (let [page-number-bar (get-page-number-bar content)
+        last-page  (->> (reverse page-number-bar)
+                        (filter #(Character/isDigit (first %))) ;; Filter is lazy
+                        (first)
+                        (Integer/parseInt))]
+    (vec (range 1 (inc last-page)))))

 (defn- has-class? [part]
   (and (map? (:attrs part))


make the sente handler slightly easier to understand

--- src-cljs/frontend/sente.cljs
+++ src-cljs/frontend/sente.cljs
@@ -71,9 +71,14 @@
         mult (:ch-recv-mult sente-state)]
     (async/tap mult tap)
     (go-loop []
-             (when-let [res (<! tap)]
-               (utils/swallow-errors (handle-message app-state (first (second (:event res))) (second (second (:event res)))))
-               (recur)))))
+      (when-let [{[type data] :event} (<! tap)]
+        ;; other type is :chsk/state, sent when the ws is opened.
+        ;; might be a good thing to watch when we reconnect?
+        (when (= :chsk/recv type)
+          (utils/swallow-errors
+           (let [[message message-data] data]
+             (handle-message app-state message message-data))))
+        (recur)))))

 (defn init [app-state]
   (let [{:keys [chsk ch-recv send-fn state] :as sente-state} (sente/make-channel-socket! "/chsk" {:type :auto})]


Fix support button error display in flashes

--- src-cljs/frontend/utils.cljs
+++ src-cljs/frontend/utils.cljs
@@ -4,7 +4,7 @@
             [cljs.core.async :as async :refer [>! <! alts! chan sliding-buffer close!]]
             [cljs-time.core :as time]
             [clojure.string :as string]
-            [frontend.async :refer [raise!]]
+            [frontend.async :refer [raise! put!]]
             [frontend.config :as config]
             [goog.Uri]
             [goog.async.AnimationDelay]
@@ -84,8 +84,8 @@
     (.update container content)
     (crypt/byteArrayToHex (.digest container))))

-(defn notify-error [owner message]
-  (raise! owner [:error-triggered message]))
+(defn notify-error [ch message]
+  (put! ch [:error-triggered message]))

 (defn trim-middle [s length]
   (let [str-len (count s)]


Use schema's cond-pre instead of deprecated either

--- src/clj/comic_reader/macro_util.clj
+++ src/clj/comic_reader/macro_util.clj
@@ -5,11 +5,11 @@
   [(s/one s/Keyword :subscription-key) s/Any])

 (s/defschema SubscriptionCallSpec
-  (s/either s/Keyword SubscriptionVector))
+  (s/cond-pre s/Keyword SubscriptionVector))

 (s/defschema SubscriptionSpec
-  (s/either s/Symbol [(s/one s/Symbol :binding-symbol)
-                      (s/one SubscriptionCallSpec :call-spec)]))
+  (s/cond-pre s/Symbol [(s/one s/Symbol :binding-symbol)
+                        (s/one SubscriptionCallSpec :call-spec)]))

 (defprotocol PSubscriptionSpec
   (subscription-symbol [spec] "Extract the subscription binding symbol from this spec.")


More browsers!

More browsers!

--- src/cljs/browser_rotation/core.cljs
+++ src/cljs/browser_rotation/core.cljs
@@ -21,7 +21,12 @@
         {:firefox "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcRl842DwO1aFB0s2RjP2NOIatW34mTWmXpZeL8bfcvHR82cAz-yGGSuxh1c" }
         {:safari "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcShE2MlWD_XfWH8DnMlckF_e9Y4OougOzzDiL_9OMP_pGjtMfYAp4TJlOzg" }
         {:ie "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcSX1WBUmFYhJDHSA6qxeoFCN8h2zl3SLOcrXOdBmVc5Jaz3GcsAq4qeArHi" }
-        {:opera "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSc3W-bMSPcREOssHYIso-Wh8KGQylj4FAOUG_zGNIjrik04NdvRSUxlF4G" }]
+        {:vivaldi "data:image/jpeg;base64,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" }
+        {:mosaik "http://crossjam.net/mpr/media/Mosaic%20Browser%20Logo.png" }
+        {:netscape "data:image/jpeg;base64,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" }
+        {:lynx "data:image/jpeg;base64,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" }
+        {:opera "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSc3W-bMSPcREOssHYIso-Wh8KGQylj4FAOUG_zGNIjrik04NdvRSUxlF4G" }
+        ]
     cycle
     (drop n)
     (take 5)


Follow ordering convention for params in handler

--- src/cljs/comic_reader/handlers.cljs
+++ src/cljs/comic_reader/handlers.cljs
@@ -29,8 +29,8 @@
    (fn [db [page site location]]
      (api/get-comic-urls site location)
      (assoc db
+            :page page
             :site site
-            :page page
             :location location)))

   (rf/register-handler


more on inline implementation

more on inline implementation

--- src/language/protocol.clj
+++ src/language/protocol.clj
@@ -149,11 +149,116 @@

 (lookup (Point. 3 4) 1 0)

-
-
-
-
-
+;inline implementation
+
+(defprotocol ClashWhenInlined
+  (size [x]))
+
+#_(defrecord R []
+  ClashWhenInlined
+  (size [x]))
+
+
+(deftype R []
+  ClashWhenInlined
+  (size [x]))
+
+(defrecord R [])
+
+(extend-type R
+  ClashWhenInlined
+  (size [x]))
+
+(.size (R.));this result is exceptional!
+
+;inline implementation is not encouraged! But it is the only way to implement java interfaces!
+
+
+;extend Object by inline implementation(this is a special case, inline implementation does not support other classes)
+
+(deftype Point [x y]
+  Matrix
+  (lookup [pt i j]
+          (when (zero? j)
+            (case i
+              0 x
+              1 y)))
+  (update [pt i j val]
+          (if (zero? j)
+            (condp = i
+              0 (Point. val y)
+              1 (Point. x val))
+            pt))
+  (rows [pt] [[x] [y]])
+  (cols [pt] [x y])
+  (dims [pt] [2 1])
+  Object
+  (equals [this other]
+          (and (instance? (class this) other)
+               (= x (.x other))
+               (= y (.y other))))
+  (hashCode [this]
+            (-> x
+                hash
+                (hash-combine y))))
+
+(Point. 3 4)
+
+;(.hashCode (Point. 3 4))
+;(.hashCode (Point. 4 3))
+
+;(.equals (Point. 3 4) (Point. 3 4))
+
+
+;reify
+(count (.listFiles (java.io.File. ".")
+            (reify
+              java.io.FileFilter
+              (accept [this f]
+                      (.isDirectory f)))))
+
+(aget (.listFiles (java.io.File. ".")) 0)
+
+(count (.listFiles (java.io.File. ".")))
+
+
+
+(defrecord Banana [qty])
+(defrecord Grape  [qty])
+(defrecord Orange [qty])
+
+;;; 'subtotal' differs from each fruit.
+
+(defprotocol Fruit
+  (subtotal [item]))
+
+(extend-type Banana
+  Fruit
+  (subtotal [item]
+            (* 158 (:qty item))))
+
+(extend-type Grape
+  Fruit
+  (subtotal [item]
+            (* 178 (:qty item))))
+
+(extend-type Orange
+  Fruit
+  (subtotal [item]
+            (* 98 (:qty item))))
+
+;;; 'coupon' is the function returing a 'reify' of subtotal. This is
+;;; when someone uses a coupon ticket, the price of some fruits is
+;;; taken off 25%.
+
+(defn coupon [item]
+  (reify Fruit
+    (subtotal [_]
+              (int (* 0.75 (subtotal item))))))
+
+;;; Example: To compute the total when someone bought 10 oranges,
+;;;  15 bananas and 10 grapes using a coupon.
+(apply +  (map subtotal [(Orange. 10) (Banana. 15) (coupon (Grape. 10))]))

 ;;;;;;;;;;;;;;;;;;;tips;;;;;;;;;;;;;;
@@ -201,5 +306,17 @@
   #{3 4 5} :>> #(str "find this -> " %)
   #{4 5 6} "high!")

-
-
+;size is defined as a method for maps
+
+(.size {:a 2 :b 3 :c 4 :d 4})
+
+;instance?
+(instance? (class 3) 2)
+
+(class 3)
+
+(instance? (class (Integer. 3)) 3)
+
+(hash 'x)
+(hash-combine (hash 'x) 'y)
+


Try to improve readability in validate.

Try to improve readability in validate.

--- clj_record/validation.clj
+++ clj_record/validation.clj
@@ -8,14 +8,12 @@
 (defn validate [model-name record]
   (reduce
     (fn [errors [attr message validation-fn]]
-      (let [value (record attr)
-            valid (validation-fn value)]
-          (if valid
-            errors
-            (merge-with
-              (fn [result addl-val] (apply conj result addl-val))
-              errors
-              {attr [message]}))))
+      (if (validation-fn (record attr))
+        errors
+        (merge-with
+          (fn [result addl-val] (apply conj result addl-val))
+          errors
+          {attr [message]})))
     {}
     (validations-for model-name)))


Finally, use a macro to define the monteenth/tuesteenth etc. fns.

--- clojure/meetup/meetup.clj
+++ clojure/meetup/meetup.clj
@@ -1,9 +1,6 @@
 (ns meetup
   (:require [clj-time.core :as t]
-            [clj-time.predicates :as tpr]
-            [clojure.tools.namespace.repl :as repl :only [refresh]]))
-
-(repl/refresh)
+            [clj-time.predicates :as tpr]))

 (defn- simple-format [d]
   (when d
@@ -39,11 +36,18 @@
    (first (filter #(contains? (set date-range) (t/day %))
                   (dates-in yr mm dow)))))

-(def teens (range 13 20))
-(def monteenth    (partial schedule-in-date-range 1 teens))
-(def tuesteenth   (partial schedule-in-date-range 2 teens))
-(def wednesteenth (partial schedule-in-date-range 3 teens))
-(def thursteenth  (partial schedule-in-date-range 4 teens))
-(def friteenth    (partial schedule-in-date-range 5 teens))
-(def saturteenth  (partial schedule-in-date-range 6 teens))
-(def sunteenth    (partial schedule-in-date-range 7 teens))
+(defmacro schedule-in-date-range-fn [fname dow date-range]
+  "creates fn to partially apply schedule-in-date-range.
+   e.g. (schedule-in-date-range-fn 'monteenth' 1 teens)
+        defines the monteenth fn, which can be fully applied with:
+        (monteenth 5 2013)"
+  `(intern *ns*
+           (symbol ~fname)
+           (partial schedule-in-date-range ~dow ~date-range)))
+
+(let [days ["mon" "tues" "wednes" "thurs" "fri" "satur" "sun"]
+      teens (range 13 20)]
+  (doseq [d days]
+    (let [fname (str d "teenth")
+          dow (inc (.indexOf days d))]
+      (do (schedule-in-date-range-fn fname dow teens)))))


added tests for EOF in unterminated strings and arrays

Signed-off-by: Stuart Sierra <1d6e1cf70ec6f9ab28d3ea4b27a49a77654d370e@stuartsierra.com>

--- src/test/clojure/clojure/data/json_test.clj
+++ src/test/clojure/clojure/data/json_test.clj
@@ -237,6 +237,14 @@
 (deftest default-throws-on-eof
   (is (thrown? java.io.EOFException (json/read-str ""))))

+(deftest throws-eof-in-unterminated-array
+  (is (thrown-with-msg? java.io.EOFException #"end-of-file inside array"
+        (json/read-str "[1, "))))
+
+(deftest throws-eof-in-unterminated-string
+  (is (thrown-with-msg? java.io.EOFException #"end-of-file inside string"
+        (json/read-str "\"missing end quote"))))
+
 (deftest accept-eof
   (is (= ::eof (json/read-str "" :eof-error? false :eof-value ::eof))))


BUGFIX: Not Found handler broken for anonymous routes.

Use compojure/wrap-routes so that "Not Found" handler can be matched for both
secure and anonymous routes.

--- src/lunchlotto/routes.clj
+++ src/lunchlotto/routes.clj
@@ -3,7 +3,7 @@
             [lunchlotto.auth.handlers :as auth]
             [lunchlotto.lunches.handlers :as lunches]
             [lunchlotto.settings.handlers :as settings])
-  (:require [compojure.core :refer [GET POST PUT routes]]
+  (:require [compojure.core :refer [GET POST PUT routes wrap-routes]]
             [compojure.route :refer [not-found]]
             [cemerick.friend :as friend]
             (cemerick.friend [workflows :as workflows])))
@@ -19,12 +19,13 @@
     (GET "/login" [] auth/show-login-page)))

 (def ^:private secure-user
-  (friend/wrap-authorize
+  (wrap-routes
     (routes
       (GET "/lunches/upcoming" [] lunches/show-upcoming)
       (GET "/lunches/pending" [] lunches/show-pending)
       (GET "/lunches/recommended" [] lunches/show-recommended)
       (GET "/settings" [] settings/show-settings))
+    friend/wrap-authorize
     #{:lunchlotto.auth.handlers/user}))

 (def application-routes


correct grants delete test

correct grants delete test

--- test/clj_money/models/grants_test.clj
+++ test/clj_money/models/grants_test.clj
@@ -79,7 +79,7 @@
         entity (find-entity context "Business")
         user (find-user context "jane@doe.com")
         grant (find-grant context (:id entity) (:id user))
-        _ (grants/delete storage-spec grant)
+        _ (grants/delete storage-spec (:id grant))
         grant-list (grants/search storage-spec {:entity-id (:id entity)})]
     (is (empty? (filter #(= (:id grant) (:id %)) grant-list))
         "The grant is not present after delete")))


sidebar tabs switching

sidebar tabs switching

--- src_cljs/cljat_webapp/cljat_webapp/app.cljs
+++ src_cljs/cljat_webapp/cljat_webapp/app.cljs
@@ -336,12 +336,12 @@
       [:div {:class "title"} "Threads Panel"]])
   )

-(defn sidebar-header [user sidebar-stats]
+(defn sidebar-header [user]
   [:div {:id "sidebar-header" :class "header"}
    [user-profile user]])

 (defn sidebar-pane [sidebar-stats]
-  (fn [tab-stats]
+  (fn [sidebar-stats]
     (if (= (:active sidebar-stats) :friends)
       [sidebar-friends-pane sidebar-stats]
       [sidebar-threads-pane sidebar-stats])))
@@ -351,20 +351,23 @@
     (let [props-li (fn [id]
                      (if (= (:active sidebar-stats) id)
                        {:id (str id) :class "tab-btn active"}
-                       {:id (str id) :class "tab-btn"}))]
+                       {:id (str id) :class "tab-btn"}))
+          props-a (fn [name]
+                    {:on-click (fn [_]
+                                 (reset! sidebar-tab-stats (update sidebar-stats :active (constantly name))))})]
       [:div {:id "sidebar-tabs" :class "bottom-tabs"}
        [:ul {:class "tabs"}
         [:li (props-li :friends)
-         [:a "FRIENDS"]]
+         [:a (props-a :friends) "FRIENDS"]]
         [:li (props-li :chat)
-         [:a "CHAT"]]]])))
-
-(defn sidebar [sidebar-stats]
-  (fn [sidebar-stats]
+         [:a (props-a :chat) "CHAT"]]]])))
+
+(defn sidebar []
+  (fn []
     [:div {:id "sidebar"}
-     [sidebar-header @user-info sidebar-stats]
-     [sidebar-pane sidebar-stats]
-     [sidebar-tabs sidebar-stats]]))
+     [sidebar-header @user-info]
+     [sidebar-pane @sidebar-tab-stats]
+     [sidebar-tabs @sidebar-tab-stats]]))

 (defn chat []
   (fn []
@@ -397,7 +400,7 @@
        ]]])
   (let [ch-out (chan)
         {:keys [ch-in stop-ws]} (init-ws! "/app/ws" ch-out)
-        msg-handler (fn [{:keys [id [ev-id {:keys [data] :as ?data} :as event]] :as ev-msg} val] (js/console.log "id: " id " data: " (:data ?data) " event: " ev-id))]
+        msg-handler (fn [{:keys [id [ev-id {:keys [data] :as ?data} :as event]] :as ev-msg}] (js/console.log "id: " id " data: " (:data ?data) " event: " ev-id))]
     (r/create-class
       {:component-will-mount (fn [_]
                                (js/console.log "app component -- will mount")
@@ -414,7 +417,7 @@
                                  (stop-ws))
        :reagent-render (fn []
                          [:div {:id "window"}
-                          [sidebar @sidebar-tab-stats]
+                          [sidebar]
                           [chat]])})))

 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


added context editing via table widget

added context editing via table widget

--- src/conexp/gui/editors/contexts.clj
+++ src/conexp/gui/editors/contexts.clj
@@ -30,6 +30,16 @@
 ;;
 ;;
 ;;
+
+(defn string-to-cross
+  "Takes a string and decides, whether it should be a cross or not
+
+  Parameters:
+   s     _string
+  Returns true if there should be a cross"
+  [s]
+  (let [trimmed (.trim s)]
+    (if (re-matches #"0*[,.]?0*" trimmed) false true)))

 (defn map-to-unique-strings
   "Takes a sequence of (unique) keys and returns a map
@@ -226,9 +236,15 @@
     widget   _the widget object where the paste operation takes place
     old-rows _the number of rows that were present in the table before
     new-rows _the number of rows that will be needed for the paste"
-             [widget old-rows new-rows]
-             nil ;TODO update the other widgets row-counts and add dummy objs
-             )
+             [table old-rows new-rows]
+             (let [ other-widgets (filter
+                                    (fn [w] (not= (w :table) table))
+                                    @assoc-widgets)]
+               (do
+                 (one-by-one other-widgets
+                   (fn [w] (!!!! (w :table) :extend-rows-to new-rows)))
+                 (one-by-one (range old-rows new-rows)
+                   (fn [x] (!! table :set-index-at x 0 "new object"))))))

            extend-columns-hook
            (fn-doc "This hook is called when the number of columns is extended
@@ -238,9 +254,32 @@
     widget      _the widget object where the paste operation takes place
     old-columns _the number of rows that were present in the table before
     new-columns _the number of rows that will be needed for the paste"
-             [widget old-columns new-columns]
-             nil ;TODO update the other widgets col-counts and add dummy attrs
-             )
+             [table old-columns new-columns]
+             (let [ other-widgets (filter
+                                    (fn [w] (not= (w :table) table))
+                                    @assoc-widgets)]
+               (do
+                 (one-by-one other-widgets
+                   (fn [w] (!!!! (w :table) :extend-columns-to new-columns)))
+                 (one-by-one (range old-columns new-columns)
+                   (fn [x] (!! table :set-index-at 0 x "new attribute"))))))
+
+           update-incidence
+           (fn-doc "This function updates the incidence relation.
+
+  Parameters:
+    object     _the object
+    attribute  _the attribute
+    cross      _true, if object has attribute, false otherwise"
+             [object attribute cross]
+             (dosync-wait (commute context
+                            (fn [x]
+                              (let [ objs (objects x)
+                                     attrs (attributes x)
+                                     inc (incidence x)
+                                     op (if cross conj disj)]
+                                (make-context objs attrs
+                                  (op inc [object attribute])))))))

            change-attribute-name
            (fn-doc "This function will change the name of the attribute in
@@ -339,7 +378,18 @@
                              current-name (object-rows model-row)]
                         (if (= contents current-name) current-name
                           (change-object-name model-row contents)))
-                      true contents)
+                      true ;else
+                      (let [ cross (string-to-cross contents)
+                             obj-name (@obj-rows model-row)
+                             attr-name (@attr-cols model-column)
+                             fca-ctx (get-context @context)
+                             inc (incidence fca-ctx)
+                             current-state (contains? inc [obj-name attr-name])]
+                        (do
+                          (if (not= current-state cross)
+                            (update-incidence obj-name attr-name cross))
+                          (if cross "X" " ")))
+                      )
                     other-widgets (filter
                                     (fn [w] (not= (w :table) table))
                                     @assoc-widgets)]
@@ -360,6 +410,9 @@
              (let [ ctx @context
                     fca-ctx (get-context ctx)
                     attrs (attributes fca-ctx)
+                    inc (incidence fca-ctx)
+                    get-cross (fn [obj att] (if (contains? inc [obj att])
+                                              "X" " "))
                     attr-count (count attrs)
                     objs (objects fca-ctx)
                     obj-count (count objs)
@@ -372,7 +425,16 @@
                    (one-by-one attrs
                      (fn [x] (let [column (attrib-cols x)]
                                (!! table :set-index-at 0 column x))))
-                 )))
+                   (one-by-one objs
+                     (fn [x] (let [row (object-rows x)]
+                               (!! table :set-index-at row 0 x))))
+                   (one-by-one attrs
+                     (fn [a] (one-by-one objs
+                               (fn [o]
+                                 (let [ row (object-rows o)
+                                        column (attrib-cols a)]
+                                   (!! table :set-index-at row column
+                                     (get-cross o a))))))))))


Added CISI to Netflix lookup

Added CISI to Netflix lookup

--- update_episodes.clj
+++ update_episodes.clj
@@ -98,6 +98,19 @@
         :attrs
         :rel)))

+(defn get-netflix-from-cisi [cisi-id]
+  (let [resp (http/get
+               "http://www.canistream.it/services/query"
+               {:query-params {:movieId cisi-id
+                               :attributes true
+                               :mediaType "dvd"}
+                :accept :json
+                :as :json})]
+    (some-> resp
+            :body
+            :netflix_dvd
+            :external_id)))
+
 (defn find-ids [{:keys [media_title episode_id episode_title] :as ep}]
   (let [resp (http/get
                "http://api.rottentomatoes.com/api/public/v1.0/movies.json"
@@ -163,4 +176,22 @@
                :episode_title
                :episode_id])))))))

-(update-database)
+(def throttled-nf (throttle-fn get-netflix-from-cisi 1 :second 4))
+
+(defn add-nf-ids []
+  (let [eps (kcore/select episodes
+              (kcore/where {:netflix_id nil
+                            :ignore 0}))]
+    (println (count eps) "still not matched")
+    (doseq [ep eps]
+      (print "Updating" (str (:media_title ep) "..."))
+      (flush)
+      (if-let [nf-id (-> ep :cisi_id throttled-nf)]
+        (do
+          (println " ok")
+          (kcore/update episodes
+            (kcore/set-fields {:netflix_id nf-id})
+            (kcore/where {:episode_id (:episode_id ep)})))
+        (println " FAILED")))))
+
+(add-nf-ids)


refine transaction tests

refine transaction tests

--- test/clj_money/models/transactions_test.clj
+++ test/clj_money/models/transactions_test.clj
@@ -235,7 +235,11 @@
   (let [context (serialization/realize storage-spec balance-context)
         [checking-items
          salary-items
-         groceries-items] (map #(transactions/items-by-account storage-spec (:id %))
+         groceries-items] (map #(transactions/items-by-account
+                                  storage-spec
+                                  (:id %)
+                                  [(t/local-date 2016 1 1)
+                                   (t/local-date 2016 12 31)])
                                (:accounts context))]
            ; Transactions are returned with most recent first
     (is (= [900M 1000M]
@@ -457,16 +461,17 @@
         [checking
          salary
          groceries] (:accounts context)
-        checking-items-before (transactions/items-by-account
+        items-fn (fn []
+                   (transactions/items-by-account
                                 storage-spec
                                 (:id checking)
-                                [(t/local-date 2016 1 1) (t/local-date 2016 12 31)])
+                                [(t/local-date 2016 1 1) (t/local-date 2016 12 31)]))
+        checking-items-before (items-fn)
         trans (-> context
                   :transactions
                   second)
         _ (transactions/delete storage-spec (:id trans) (:transaction-date trans))
-        checking-items-after (transactions/items-by-account storage-spec
-                                                             (:id checking))]
+        checking-items-after (items-fn)]
     (testing "transaction item balances are adjusted"
       (let [expected-before [{:index 2 :amount 102M :balance 797M}
                              {:index 1 :amount 101M :balance 899M}
@@ -476,8 +481,16 @@
             expected-after[{:index 1 :amount 102M :balance 898M}
                            {:index 0 :amount 1000M :balance 1000M}]
             actual-after (map #(select-keys % [:index :amount :balance]) checking-items-after)]
+        (if-not (= expected-before actual-before)
+          (pprint {:expected expected-before
+                   :actual actual-before
+                   :diff (diff expected-before actual-before)}))
         (is (= expected-before actual-before)
             "Checking should have the correct items before delete")
+        (if-not (= expected-after actual-after)
+          (pprint {:expected expected-after
+                   :actual actual-after
+                   :diff (diff expected-after actual-after)}))
         (is (= expected-after actual-after)
             "Checking should have the correct items after delete")))
     (testing "account balances are adjusted"


Attach ^:riak metadata to ns

Attach ^:riak metadata to ns

Previously, the fixture was still run when running lein test. This meant
that the test attempted to connect to Riak, which didn't work.

--- test/icecap/store/riak_integration_test.clj
+++ test/icecap/store/riak_integration_test.clj
@@ -1,4 +1,4 @@
-(ns icecap.store.riak-integration-test
+(ns ^:riak icecap.store.riak-integration-test
   (:require [icecap.store.riak :refer :all]
             [icecap.store.test-props :refer :all]
             [clojure.test.check.clojure-test :refer [defspec]]
@@ -21,8 +21,8 @@

 (use-fixtures :once connect)

-(defspec ^:riak riak-store-roundtrip
+(defspec riak-store-roundtrip
   (roundtrip-prop riak-test-store))

-(defspec ^:riak riak-store-delete
+(defspec riak-store-delete
   (delete-prop riak-test-store))