adding even distribution test/train; DBZ bug fix for degenerate case; const key defs

--- src/clojure/zensols/dataset/db.clj
+++ src/clojure/zensols/dataset/db.clj
@@ -27,12 +27,26 @@
     zensols.dataset.db
   (:require [clojure.java.io :as io]
             [clojure.tools.logging :as log]
-            [clojure.pprint :refer (pprint)]
-            [clj-excel.core :as excel])
+            [clojure.string :as s]
+            [clojure.set :refer (rename-keys)]
+            [clojure.data.csv :as csv])
+  (:require [clj-excel.core :as excel])
   (:require [zensols.actioncli.dynamic :refer (defa) :as dyn]
             [zensols.actioncli.resource :as res]
             [zensols.util.spreadsheet :as ss]
             [zensols.dataset.elsearch :refer (with-context) :as es]))
+
+(def instance-key :instance)
+
+(def class-label-key :class-label)
+
+(def ^:private dataset-key :dataset)
+
+(def ^:private es-class-label-key
+  (->> [dataset-key class-label-key] (map name) (s/join ".")))
+
+(def ^:private es-instance-key
+  (-> dataset-key name (str ".instance")))

 (def ^:private id-state-key "id-state")

@@ -85,7 +99,7 @@
     :or {create-instances-fn identity
          population-use 1.0
          set-type :train
-         mapping-type-def {:instance {:type "nested"}}
+         mapping-type-def {instance-key {:type "nested"}}
          url "http://localhost:9200"}}]
   {:index-name index-name
    :ids-inst (atom nil)
@@ -96,7 +110,7 @@
                       :url url
                       :settings {"index.mapping.ignore_malformed" true}
                       :mapping-type-defs
-                      {:dataset {:properties mapping-type-def}})
+                      {dataset-key {:properties mapping-type-def}})
    :stats-context (es/create-context
                    index-name "stats"
                    :url url
@@ -180,7 +194,8 @@
   ([id]
    (use-connection
      (with-context [instance-context]
-       (:dataset (es/document-by-id id))))))
+       (->> (es/document-by-id id)
+            dataset-key)))))

 (defn clear
   "Clear the in memory instance data.  If key `:wipe-persistent?` is `true` all
@@ -305,11 +320,11 @@
   ----
   * **:set-type** is either `:train`, `:test`, `:train-test` (all) and defaults
   to [[set-default-set-type]] or `:train` if not set
-  * **:include-keys?** if non-`nil` return keys in the map as well"
-  [& {:keys [set-type include-keys?] :as keys}]
+  * **:include-ids?** if non-`nil` return keys in the map as well"
+  [& {:keys [set-type include-ids?]}]
   (let [conn (connection)]
     (->> (ids :set-type set-type)
-         (map (if include-keys?
+         (map (if include-ids?
                 #(assoc (instance-by-id conn %) :id %)
                 #(instance-by-id conn %))))))

@@ -320,10 +335,13 @@
   (use-connection
     (if @ids-inst
       (let [train (count (:train (:train-test @ids-inst)))
-            test (count (:test (:train-test @ids-inst)))]
+            test (count (:test (:train-test @ids-inst)))
+            total (+ train test)]
         {:train train
          :test test
-         :split (double (/ train (+ train test)))}))))
+         :split (if (= 0 total)
+                  0.0
+                  (double (/ train total)))}))))

 (defn- id-info []
   {:train (ids :set-type :train)
@@ -351,30 +369,6 @@
                      (merge {:train-test (compute-folds data)})
                      (persist-id-state))))))
     (stats)))
-
-(defn divide-by-set
-  "Divide the dataset into a test and training *buckets*.
-
-  * **train-ratio** this is the percentage of data in the train bucket, which
-  defaults to `0.5`
-
-  Keys
-  ----
-  * **:shuffle?** if `true` then shuffle the set before partitioning, otherwise
-  just update the *demarcation* boundary"
-  ([]
-   (divide-by-set 0.5))
-  ([train-ratio & {:keys [shuffle?] :or {shuffle? true}}]
-   (use-connection
-     (let [ids (->> (db-ids) ((if shuffle? shuffle identity)))
-           sz (count ids)
-           train-count (* train-ratio sz)
-           id-data {:train-test {:train (take train-count ids)
-                                 :test (drop train-count ids)}}]
-       (reset! ids-inst id-data)
-       (log/infof "shuffled: %s" (stats))
-       (persist-id-state id-data)
-       (stats)))))

 (defn divide-by-preset
   "Divide the data into test and training *buckets*.  The respective train/test
@@ -430,13 +424,16 @@
   ([id instance class-label set-type]
    (log/infof "loading instance (%s): %s => <%s>"
                id class-label
-               (let [s (pr-str instance)]
-                 (subs s 0 (min 80 (count s)))))
+               (let [maxlen (count s)
+                     s (pr-str instance)
+                     len (count s)]
+                 (str (subs s 0 (min maxlen len))
+                      (if (> len maxlen) "..."))))
    (log/debugf "instance: %s" instance)
    (use-connection
      (with-context [instance-context]
-       (let [doc (merge {:dataset {:class-label class-label
-                                   :instance instance}}
+       (let [doc (merge {dataset-key {class-label-key class-label
+                                      instance-key instance}}
                         (if set-type {:set-type set-type}))
              res (if id
                    (es/put-document id doc)
@@ -468,53 +465,189 @@
                                            :test (lazy-seq test)}})
             (persist-id-state @ids-inst)))))))

-(defn write-dataset-to-excel
-  "Write the data set to an excel file.
+(defn distribution
+  "Return maps representing the data set distribution by class label.  Each
+  element of the returned sequence has the following keys:
+
+  * **:class-label** the class-label fo the instances
+  * **:count** the number of instances for **:class-label**"
+  []
+  (use-connection
+    (with-context [instance-context]
+      (->> (es/buckets es-class-label-key)
+           (map #(rename-keys % {:name class-label-key}))))))
+
+(defn instances-by-class-label
+  "Return a map with class-labels for keys and corresponding instances for that
+  class-label.

   Keys
   ----
-
+  * **:max-instances** the maximum number of instances per class
+  * **:type* the data to grab, which is one of the following symbols:
+    * **ids** returns only IDs (not the document data)
+    * **document** returns the document data (`:class-label` and `:instance` keys)
+  * **:seed** if given, seed the random number generator, otherwise don't
+  return random documents"
+  [& {:keys [max-instances type seed]
+      :or {max-instances Integer/MAX_VALUE
+           type 'document}}]
+  (use-connection
+    (with-context [instance-context]
+      (letfn ([query [class-label]
+               (merge {:query
+                       (if seed
+                         {:function_score
+                          {:filter {:term {es-class-label-key class-label}}
+                           :functions [{:random_score
+                                        {:seed seed}}]}}
+                         {:term {es-class-label-key class-label}})}
+                      (if (= type 'ids) {:fields []}))])
+        (->> (distribution)
+             (map class-label-key)
+             (map (fn [class-label]
+                    (->> (query class-label)
+                         es/search
+                         (take max-instances)
+                         (map (case type
+                                document #(-> % :doc dataset-key)
+                                ids #(-> % :id Integer/parseInt)))
+                         (array-map class-label))))
+             (into {})
+             doall)))))
+
+(defn- divide-by-uneven-distribution-set
+  "Divide the dataset into a test and training *buckets*.
+
+  * **train-ratio** this is the percentage of data in the train bucket.
+
+  Keys
+  ----
+  * **:shuffle?** if `true` then shuffle the set before partitioning, otherwise
+  just update the *demarcation* boundary"
+  [train-ratio & {:keys [shuffle? ids
+                         max-instances]
+                  :or {shuffle? true
+                       max-instances Integer/MAX_VALUE}}]
+  (use-connection
+    (let [ids (->> (or ids (db-ids))
+                   ((if shuffle? shuffle identity))
+                   (take max-instances))
+          sz (count ids)
+          train-count (* train-ratio sz)
+          id-data {:train-test {:train (take train-count ids)
+                                :test (drop train-count ids)}}]
+      (reset! ids-inst id-data)
+      (log/infof "divided by set: %s" (stats))
+      (persist-id-state id-data)
+      (stats))))
+
+(defn- divide-by-class-distribution-set
+  "Just like [[divide-by-uneven-distribution-set]] but each test and training set
+  has an even distribution by class label.
+
+  The **keys** parameter(s) are described in [[divide-by-set]]
+  and [[instances-by-class-label]]."
+  [train-ratio & opts]
+  (use-connection
+    (with-context [instance-context]
+      (let [opts (concat opts [:type 'ids])
+            {:keys [shuffle?] :or {shuffle? true}} (apply hash-map opts)]
+        (->> opts
+             (apply instances-by-class-label)
+             vals
+             (reduce (fn [{:keys [train test]} ids]
+                       (let [sz (count ids)
+                             train-count (* train-ratio sz)]
+                         {:train (concat train (take train-count ids))
+                          :test (concat test (drop train-count ids))}))
+                     {})
+             ((if shuffle?
+                (fn [{:keys [test train]}]
+                  {:test (shuffle test)
+                   :train (shuffle train)})
+                identity))
+             (array-map :train-test)
+             (reset! ids-inst)
+             persist-id-state))
+      (let [stats (stats)]
+        (log/infof "divided by set with even distribution by class: %s" stats)
+        stats))))
+
+(defn divide-by-set
+  "Divide the dataset into a test and training *buckets*.
+
+  * **train-ratio** this is the percentage of data in the train bucket, which
+  defaults to `0.5`
+
+  Keys
+  ----
+  * **:dist-type** one of the following symbols:
+      *even* each test/training set has an even distribution by class label
+      *uneven* each test/training set has an uneven distribution by class label
+  * **:shuffle?** if `true` then shuffle the set before partitioning, otherwise
+  just update the *demarcation* boundary
+  * **:max-instances** the maximum number of instances per class
+  * **:seed** if given, seed the random number generator, otherwise don't
+  return random documents"
+  ([]
+   (divide-by-set 0.5))
+  ([train-ratio & {:keys [dist-type shuffle? max-instances seed]
+                    :as opts
+                    :or {shuffle? true
+                         dist-type 'uneven}}]
+   (-> (case dist-type
+         even divide-by-class-distribution-set
+         uneven divide-by-uneven-distribution-set)
+       (apply (apply concat (list train-ratio) opts)))))
+
+(defn dataset-file []
+  (use-connection
+    (->> (format "%s-dataset.xls" index-name)
+         (res/resource-path :analysis-report))))
+
+(defn write-dataset
+  "Write the data set to a spreadsheet.  If the file name ends with a `.csv` a
+  CSV file is written, otherwise an Excel file is written.
+
+  Keys
+  ----
   * **:output-file** where to write the file and defaults to
   [[res/resource-path]] `:analysis-report`
   * **:single?** if `true` then create a single sheet, otherwise the training
   and testing *buckets* are split between sheets"
-  [& {:keys [output-file single?]}]
-  (use-connection
-    (let [output-file (or output-file
-                          (->> (format "%s.xls" index-name)
-                               (res/resource-path :analysis-report)))]
-      (letfn [(data-set [set-type fields col-names header?]
-                (->> (instances :set-type set-type)
-                     (map (fn [{:keys [class-label instance]}]
-                            (concat fields [class-label instance])))
-                     ((if header?
-                        #(cons (concat col-names ["Label" "Instance"]) %)
-                        identity))
-                     ((if header? ss/headerize identity))))]
-        (-> (excel/build-workbook
-             (excel/workbook-hssf)
-             (if single?
-               {"Train and Test" (concat (data-set :train ["train"] ["Train"] true)
-                                         (data-set :train ["test"] ["Test"] false))}
-               {"Train" (data-set :train nil nil true)
-                "Test" (data-set :test nil nil true)}))
-            (ss/autosize-columns)
-            (excel/save output-file))))))
-
-(defn- main
-  "In REPL testing"
-  [& actions]
-  (use-connection
-    (->> actions
-         (map (fn [action]
-                (case action
-                  -2 (set-default-connection)
-                  -1 (clear :wipe-persistent? true)
-                  0 (instances-load)
-                  4 (create-id-list)
-                  7 (id-info)
-                  8 (with-context [stats-context]
-                      (pprint (es/describe)))
-                  9 (set-population-use 0.7)
-                  12 (stats))))
-         doall)))
+  [& {:keys [output-file single? instance-fn columns-fn]
+      :or {instance-fn identity
+           columns-fn (constantly ["Instance"])}}]
+  (use-connection
+    (let [output-file (or output-file (dataset-file))
+          csv? (re-matches #".*\.csv$" (.toString output-file))]
+      (letfn [(data-set [set-type fields col-names header? headerize?]
+                (let [insts (instances :set-type set-type :include-ids? true)
+                      header-inst (first insts)]
+                  (->> insts
+                       (map (fn [{:keys [class-label id instance]}]
+                              (->> (instance-fn instance)
+                                   (concat fields [class-label id]))))
+                       ((if header?
+                          #(cons (concat col-names
+                                         ["Label" "Id"]
+                                         (columns-fn header-inst))
+                                 %)
+                          identity))
+                       ((if headerize? ss/headerize identity)))))]
+        (if csv?
+          (with-open [writer (io/writer output-file)]
+            (->> (concat (data-set :train ["train"] ["Set Type"] true false)
+                         (data-set :train ["test"] nil false false))
+                 (csv/write-csv writer)))
+          (-> (excel/build-workbook
+               (excel/workbook-hssf)
+               (if single?
+                 {"Train and Test"
+                  (concat (data-set :train ["train"] ["Set Type"] true true)
+                          (data-set :train ["test"] ["Test"] false false))}
+                 {"Train" (data-set :train nil nil true true)
+                  "Test" (data-set :test nil nil true true)}))
+              (ss/autosize-columns)
+              (excel/save output-file)))))))


Fix lint error :shirt:

Fix lint error :shirt:

--- src/metabase/core.clj
+++ src/metabase/core.clj
@@ -204,12 +204,12 @@

 ;;; ---------------------------------------- Special Commands ----------------------------------------

-(defn- ^:command migrate
+(defn ^:command migrate
   "Run database migrations. Valid options for DIRECTION are `up`, `force`, `down-one`, `print`, or `release-locks`."
   [direction]
   (db/migrate! @db/db-connection-details (keyword direction)))

-(defn- ^:command load-from-h2
+(defn ^:command load-from-h2
   "Transfer data from existing H2 database to the newly created MySQL or Postgres DB specified by env vars."
   ([]
    (load-from-h2 nil))
@@ -217,7 +217,7 @@
    (require 'metabase.cmd.load-from-h2)
    ((resolve 'metabase.cmd.load-from-h2/load-from-h2!) h2-connection-string)))

-(defn- ^:command profile
+(defn ^:command profile
   "Start Metabase the usual way and exit. Useful for profiling Metabase launch time."
   []
   ;; override env var that would normally make Jetty block forever
@@ -226,7 +226,7 @@
     (start-normally)
     (println (u/format-color 'green "start-normally took %s." (u/format-nanoseconds (- (System/nanoTime) start-ns))))))

-(defn- ^:command help
+(defn ^:command help
   "Show this help message listing valid Metabase commands."
   []
   (println "Valid commands are:")


fix path to example target-domains.txt file

--- src/ads_txt_crawler/core.clj
+++ src/ads_txt_crawler/core.clj
@@ -5,7 +5,7 @@

 ;; default
-(def targets-file "target-domains.txt")
+(def targets-file "./doc/target-domains.txt")

 (def cli-options


[ref] Start removing noir.validate dependence.

--- src/ombs/validate.clj
+++ src/ombs/validate.clj
@@ -56,6 +56,8 @@

 (defn errors? [] (vld/errors?))

+(defn add-error [tag text] (vld/set-error! tag text))
+
 (defmacro create-rule [tag [validator msg]]
   `(vld/rule ~@(list validator) [~tag ~msg] )
   )
@@ -72,42 +74,53 @@
      (not (vld/errors? ~tag)))
   )

-(defn add-error [tag text] (vld/set-error! tag text))
+(defn rule [[validator msg]]
+   (if-not validator
+     msg
+     nil))
+
+
+(cond-> {}
+  false (msg-insert :e "First")
+  false (msg-insert :e "Second")
+  true (msg-insert :f "Third")
+  false (msg-insert :d "ERoror")
+  true (msg-insert :f (message [:event :empty-name]))
+  )
+
+
+
+(defn msg-insert [coll tag msg]
+  (update coll tag #(vec (conj % msg)))
+  )

 (defn new-event? [eventname price date]
-  (create-validator :event
-                    [
-                     [ (vld/has-value? eventname)              (message [:event :empty-name])      ]
-                     [ (vld/greater-than? price 0)             (message [:event :zero-price])      ]
-                     [ (vld/has-value? date)                   (message [:event :empty-date])      ]
-                     [ (empty? (db/get-event eventname date))  (message [:event :duplicate-event]) ]
-                     ]))
+  (cond-> {}
+    (vld/has-value? eventname)              (msg-insert :event (message [:event :empty-name]))
+    (vld/greater-than? price 0)             (msg-insert :event (message [:event :zero-price]))
+    (vld/has-value? date)                   (msg-insert :event (message [:event :empty-date]))
+    (empty? (db/get-event eventname date))  (msg-insert :event (message [:event :duplicate-event]))))

 (defn new-user? [username pass1 pass2]
-  (create-validator :register
-                    [
-                     [(vld/has-value? username) (message [:user :empty-name])]
-                     [(>= (count pass1) 8) (message [:register :short-pass])]
-                     [(= pass1 pass2) (message [:register :notmatch-pass])]
-                     [(empty? (db/get-user username)) (message [:user :unexist]) ]
-                     ]))
+  (cond-> {}
+    (vld/has-value? username)       (msg-insert :login (message [:user :empty-name]))
+    (>= (count pass1) 8)            (msg-insert :login (message [:register :short-pass]))
+    (= pass1 pass2)                 (msg-insert :login (message [:register :notmatch-pass]))
+    (empty? (db/get-user username)) (msg-insert :login (message [:user :unexist]))
+    ))

 (defn login? [username password]
-  (create-validator :login
-                    [[ (vld/has-value? username) (message [:user :empty-name])]
-                     [ (vld/has-value? (db/get-user username)) (message [:user :invalid])]
-                     [ (= password (:password (db/get-user username))) (message [:login :invalid])]
-                     ])
-  )
+  (cond-> {}
+    (vld/has-value? username)                       (msg-insert :login (message [:user :empty-name]))
+    (vld/has-value? (db/get-user username))         (msg-insert :login (message [:user :invalid]))
+    (= password (:password (db/get-user username))) (msg-insert :login (message [:login :invalid]))
+    ))

 (defn participation? [eid]
-  (create-validator :participation
-                    [
-                     [(not= (db/get-status eid) (db/statuses :finished))
-                      (message [:event :finished])]
-                     [(not= "" (:name (db/get-event eid)))
-                      (message [:event :unexist])]
-                     ]))
+  (cond-> {}
+    (not= (db/get-status eid) (db/statuses :finished)) (msg-insert :participation (message [:event :finished]))
+    (not= "" (:name (db/get-event eid)))               (msg-insert :participation (message [:event :unexist]))
+                     ))

 (defn payment? [eid uid parts]
   "Check, if id's is correct. Used with (db/get-*id)"


Adapted prototype of extended luxenburger basis to new implementation

Signed-off-by: Daniel Borchmann <25857343a15bf1edafebc2912e82ecf775c585c1@mailbox.tu-dresden.de>

--- stuff/code/extended_luxenburger_basis.clj
+++ stuff/code/extended_luxenburger_basis.clj
@@ -61,7 +61,6 @@
   which have support greater or equal to minsupp."
   [context minsupp minconf]
   (let [closures (extended-iceberg-intent-set context minsupp)]
-    (println closures)
     (for [B_1 closures,
           B_2 closures,
           :when (and (proper-subset? B_1 B_2)
@@ -69,8 +68,7 @@
                             (and (proper-subset? B_1 C)
                                  (proper-subset? C B_2)))))
           :let [ar (make-association-rule context B_1 B_2)]
-          :when (or (zero? (support B_1 context))
-                    (>= (confidence ar) minconf))]
+          :when (>= (confidence ar) minconf)]
       ar)))

 ;;;


Remove confusing reference

Remove confusing reference

--- src/play_clj/core.clj
+++ src/play_clj/core.clj
@@ -563,7 +563,7 @@
     (run! my-other-screen :on-change-color :color :blue)"
   [screen-object fn-name & options]
   (assert (some? (:screen screen-object)) "The first argument in run! must be
-a defscreen object, such as main-screen.")
+a defscreen object.")
   (assert (keyword? fn-name) "The second argument in run! must be a keyword.")
   (assert (even? (count options)) "Unexpected argument count in run!.")
   (let [execute-fn! (-> screen-object :screen deref :execute-fn!)


Code organization and naming improvements.

--- src/pubhouse/core.clj
+++ src/pubhouse/core.clj
@@ -21,12 +21,6 @@
   [path]
   (-> path (relative-path) (strip-extensions) (str ".html")))

-(defn canonical-url
-  "Given some url, ensures that it is an absolute url. If url is a path to
-  some index.html, return a url to the parent directory."
-  [url]
-  (str "/" (-> url (clojure.string/replace "index.html" "") (strip-extensions))))
-
 (defn file-mapping
   [info root file]
   (let [path (.getPath file)]
@@ -43,11 +37,17 @@
          (filter source-file?)
          (map #(file-mapping info root %)))))

-(defn resource-key
+(defn site-nav
   "Convert a url relative to the root of the site into a sequence of its path parts
   ,omitting any extensions in the url."
   [url]
   (-> url (strip-extensions) (fs/split)))
+
+(defn canonical-url
+  "Given some url, ensures that it is an absolute url. If url is a path to
+  some index.html, return a url to the parent directory."
+  [url]
+  (str "/" (-> url (clojure.string/replace "index.html" "") (strip-extensions))))

 (defn mapping->site
   "Convert a sequence of [path url] pairs to a map structure representing
@@ -55,7 +55,7 @@
   [mapping]
   (reduce (fn [site [path inf]]
             (assoc-in site
-                      (resource-key (:url inf))
+                      (site-nav (:url inf))
                       (update-in inf [:url] canonical-url)))
           {}
           mapping))
@@ -79,7 +79,7 @@
                   writer (html-writer build-root url)]
         (f (assoc site
                   :current-page
-                  (get-in site (resource-key url)))
+                  (get-in site (site-nav url)))
            reader
            writer)))))


Add a docstring for synthetic-personal

Add a docstring for synthetic-personal

--- src/caesium/magicnonce/secretbox.clj
+++ src/caesium/magicnonce/secretbox.clj
@@ -72,6 +72,10 @@
   (secretbox-pfx msg (random-nonce!) key))

 (def ^:private synthetic-personal
+  "The BLAKE2b personal used for synthetic nonce generation.
+
+  This says sodium, not caesium, in a vain hope that this ciphersuite gets
+  picked up by other libsodium bindings."
   (.getBytes "sodium autononce"))

 (defn ^:private synthetic-nonce


avoid overwhelming postgres server: s/pmap/map/

--- src/babel/english/writer.clj
+++ src/babel/english/writer.clj
@@ -46,7 +46,8 @@
         debug (log/debug (str "using root:" root))
         debug (log/debug (str "using spec:" (strip-refs spec)))

-        use-map-fn pmap
+        ;;        use-map-fn pmap
+        use-map-fn map
         source-expressions (read-all spec
                                      source-language-short-name)]
     (count (use-map-fn (fn [source-expression]


Allow use control-groupify in control-group-form.

--- src/cadence/views/common.clj
+++ src/cadence/views/common.clj
@@ -141,6 +141,15 @@
         (format-errors errors))
       (:more params)]]))

+(defn control-groupify
+  "Allow the field to pass through unaltered unless it is a map then call
+  control-group."
+  [field]
+  (let [field-isa? (partial isa? (type field))]
+    ((cond
+       (field-isa? clojure.lang.MapEquivalence) control-group
+       :else identity) field)))
+
 (defn- as-css-id [s]
   (name (if (nil? s) "" s)))

@@ -151,7 +160,7 @@
 (defhtml control-group-form [id+class params items buttons]
   [(keyword (str "form" (as-css-id id+class))) params
    [:fieldset
-    (map control-group items)
+    (map control-groupify items)
     [:div.form-actions
      (map form-button buttons)]]])


Adds auth to nav.

Adds auth to nav.

--- src/cadence/views/common.clj
+++ src/cadence/views/common.clj
@@ -69,8 +69,9 @@
         [:ul.nav
          [:li.divider-vertical]
          (when (not (friend/anonymous?))
-           [:li
-            [:a {:href "/user/training"} "Training"]])
+           (html
+             [:li [:a {:href "/user/training"} "Training"]]
+             [:li [:a {:href "/user/auth"} "Auth"]]))
          [:li [:a {:href "/about"} "About"]]
          [:li [:a {:href "/docs/index.html"} "Documentation"]]
          [:li [:a {:href "https://github.com/RyanMcG/Cadence"} "Source"]]


Finished docs for conversion.clj

Finished docs for conversion.clj

--- src/clj/forma/conversion.clj
+++ src/clj/forma/conversion.clj
@@ -8,11 +8,11 @@

 ;; In developing the time period conversion functions, we noticed that
 ;; as long as the time period remained consistent from dataset to
-;; dataset, the ability to 0-index became irrelevant.  Our choice of
-;; January 1st, 2000 ensures that, regardless of the date on which the
-;; specific MODIS product became active, datasets at the same temporal
-;; resolution will match up. With that in mind, we chose January 1st
-;; 2000.
+;; dataset, the ability to 0-index each dataset became irrelevant.
+;; Our choice of January 1st, 2000 as a common reference date ensures
+;; that, regardless of the date on which the specific MODIS product
+;; became active, datasets at the same temporal resolution will match
+;; up.

 (def ref-date (date-time 2000))

@@ -22,7 +22,7 @@
 ;; first of each month, while the others mark off blocks of 16 or 8
 ;; julian days from the beginning of the year.
 ;;
-;;(It's important to note that time periods are NOT measured from the
+;; (It's important to note that time periods are NOT measured from the
 ;; activation date of a specific product. The first available NDVI
 ;; 16-day composite, for example, has a beginning date of February
 ;; 18th 2001, or Julian day 49, the 1st day of the 4th period as
@@ -32,17 +32,15 @@
 ;; further validation for our choice of January 1st, 2000 as a
 ;; consistent reference point.
 ;;
-;; Additionally it should be noted that the final dataset is never
+;; Additionally, it should be noted that the final dataset is never
 ;; full; the 23rd 16-day dataset of the year holds data for either 13
 ;; or 14 days, depending on leap year.)
 ;;
-;; TODO COMPLETE FOM HERE ON OUT
-;; TODO -- what's the point of this file? We allow someone to convert
-;; a date into either 16 day, 8 day, or monthly periods, as defined by
-;; MODIS.
-;; TODO -- should we change this so we can pass the function name
-;; directly? The point of this is that we need some way to deal with
-;; 1000m, 16 day data. Make a note in tracker to deal with this.
+;; So! To construct a date, the user supplies a number of date
+;; "pieces", ranging from year down to microsecond. To compare two
+;; times on julian day, we use the clj-time library to calculate the
+;; interval between a given date and January 1st of the year in which
+;; the date occurs.

 (defn in-days
   "Returns the number of days spanned by the given time interval."
@@ -58,6 +56,20 @@
   (in-days
    (interval (date-time (year a))
              a)))
+
+;; the julian function complements the other "date-piece"
+;; functions supplied by the clj-time library; day, month, year, and
+;; the others each allow for extraction of that particular element of
+;; a date. We define the delta function to allow us to take the
+;; difference
+;;
+;;    (delta julian a b)
+;;
+;; returns the difference between the julian day values of the two
+;; supplied dates. (Note that `(partial delta julian)` ignores the
+;; year value of each of the dates. That function returns the same
+;; value for a = Jan 1st, 2000, b = Feb 25th, 2000 and b = Feb 25th,
+;; 2002.

 (defn delta [f a b] (- (f b) (f a)))

@@ -78,9 +90,16 @@
     (+ (* years (per-year unit span))
        (quot units span))))

+;; The following partial functions take care of the issue with delta
+;; above, by calculating the span in periods across years.
+
 (def months (partial delta-periods month 1))
 (def sixteens (partial delta-periods julian 16))
 (def eights (partial delta-periods julian 8))
+
+;; We currently choose the period-func based on the resolution of the
+;; data. This will have to be amended, as some datasets at different
+;; temporal resolutions do indeed have the same spatial resolution.

 (def period-func
   {"1000" months


z-index

z-index

--- src/clj/just_married/css.clj
+++ src/clj/just_married/css.clj
@@ -114,7 +114,9 @@
                          :color (:gold COLOR-PALLETTE)
                          :margin-bottom "50px"
                          :position "sticky"
-                         :top "0"}
+                         :top "0"
+                         ;; to avoid making it go behind the map
+                         :z-index 100}

     ;; FIXME: should be less generic here
     [:a {:color (:white COLOR-PALLETTE)}]


More jfx tweaks

More jfx tweaks

--- src/clj_simple_chart/jfx.clj
+++ src/clj_simple_chart/jfx.clj
@@ -13,7 +13,8 @@
            (java.io File)
            (javafx.concurrent Worker$State)
            (javafx.event EventHandler)
-           (javafx.scene.input KeyCode)))
+           (javafx.scene.input KeyCode)
+           (javafx.beans.value ChangeListener)))

 (defonce latch (CountDownLatch. 1))
 (defonce zoom (atom 1.0))
@@ -24,6 +25,9 @@

 (defonce input-height (atom nil))
 (defonce input-width (atom nil))
+
+(defonce window-height (atom nil))
+(defonce window-width (atom nil))

 (defn update-zoom! [zoomvalue]
   (swap! zoom (fn [o] zoomvalue))
@@ -39,22 +43,36 @@

 (def zoom-delta 0.025)

-(def key-released-handler
-  (reify EventHandler
-    (handle [_ v]
-      (cond (and (.isControlDown v)
-                 (= (.getText v) "0"))
-            (update-zoom! 1.0)
+; input-width * zoom = new
+; zoom = new / input-width

-            (and (.isControlDown v)
-                 (= (.getCode v) KeyCode/MINUS))
-            (update-zoom! (- @zoom zoom-delta))
+(defn key-released-handler
+  [v]
+  (cond (and (.isControlDown v)
+             (= (.getText v) "0"))
+        (update-zoom! 1.0)

-            (and (.isControlDown v)
-                 (= (.getCode v) KeyCode/EQUALS))
-            (update-zoom! (+ @zoom zoom-delta))
+        (and (.isControlDown v)
+             (= (.getCode v) KeyCode/W))
+        (update-zoom! (/ @window-width @input-width))

-            :else (do (println "unhandled" v))))))
+        (and (.isControlDown v)
+             (= (.getCode v) KeyCode/MINUS))
+        (update-zoom! (- @zoom zoom-delta))
+
+        (and (.isControlDown v)
+             (= (.getCode v) KeyCode/EQUALS))
+        (update-zoom! (+ @zoom zoom-delta))
+
+        :else (do #_(println "unhandled" v))))
+
+(defn window-width-change-handler [new]
+  (when (number? new)
+    (swap! window-width (fn [o] new))))
+
+(defn window-height-change-handler [new]
+  (when (number? new)
+    (swap! window-height (fn [o] new))))

 (defn -start [this internal-stage]
   (.setTitle internal-stage "SVG Output")
@@ -67,7 +85,15 @@
     (.setAll children [internal-webview])
     (.setScene internal-stage internal-scene)
     (.setOnZoom internal-scene zoom-handler)
-    (.setOnKeyReleased internal-scene key-released-handler)
+    (.addListener (.widthProperty internal-scene) (reify ChangeListener
+                                                    (changed [_ val old new]
+                                                      (window-width-change-handler new))))
+    (.addListener (.heightProperty internal-scene) (reify ChangeListener
+                                                     (changed [_ val old new]
+                                                       (window-height-change-handler new))))
+    (.setOnKeyReleased internal-scene (reify EventHandler
+                                        (handle [_ v]
+                                          (key-released-handler v))))
     (.show internal-stage)
     (swap! engine (fn [x] internal-engine))
     (swap! stage (fn [x] internal-stage))


resume email

resume email

--- src/cljs/fractals/email.cljs
+++ src/cljs/fractals/email.cljs
@@ -12,8 +12,8 @@
                      :padding-left "15px"
                      :padding-right "15px"
                      }}
-      [:tbody
-       [:tr
+     [:tbody
+      [:tr
         [:td
          [:table
           [:tbody
@@ -164,9 +164,13 @@
              [:div {:style {:padding "7px"
                             :color :white
                             :background-color "#12808D"}} "Iphone Hybrid APP"]
+             [:div "React Native"]
+             [:div "RN Bridge"]
+             [:div "Flex Box"]
+             [:div "iOS Components"]
              [:div "Objective C"]
-             [:div "Apple Swift"]
-             [:div "React Native"]
+             ;; [:div "Apple Swift"]
+             [:div "Offline first"]
              [:div "Cocoa Touch"]
              [:div "iOS SDK"]
              [:div "Core Services"]
@@ -174,7 +178,7 @@
              [:div "Managing Memory"]
              [:div "JavaScriptCore"]
              [:div "C/C++"]
-             [:div "UI/UX"]]]
+             ]]
            ]
           ]]]
        [:td {:style  {:box-shadow "0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)"
@@ -516,7 +520,7 @@
   Implementing Redis Cluster solve this problem because data stays memory use disk for persistence
   but it loose the ability to SQL over database. Finally we came out optimal data base which only only
   leverage SQL query but also can query graph database and document query at the same time efficiently
-  use Redis Clusters. Read More » "]]]])
+  use Redis Clusters."]]]])

 (rum/defc middle-site-email [state]
   (let [fw "1.4rem"
@@ -618,9 +622,7 @@
                      :padding "0"
                      :height "100%"
                      :width "100%"
-                     :background-color "#ddd"}
-
-             }
+                     :background-color "#ddd"}}
      [:tbody
       [:tr
        [:td {:style {
@@ -641,6 +643,18 @@
                          :border-collapse :collapse
                          :width "600px"}}
          [:tbody
+          [:tr [:td {:style {:padding "10px"
+                             :text-align :left
+                             :background-color "rgb(41, 73, 130)"}}
+                [:div {:style {:font-size "1.1rem"
+                               :padding "4px"}}
+                 "Hello " [:img {:style {:height "12px"}
+                                 :src "http://strativ.se/wp-content/uploads/2016/04/logo_zerif_default.png"}]]
+                [:div {:style {:fond-size "1rem"
+                               :color "#f1f1f1"
+                               :padding-left "5px"
+                               :text-align :left}} "Please accent my Résumé for React Native Developer	– Iphone and Android."]]]
+
           [:tr
            [:td {:style {
                          :color "#878787"
@@ -653,5 +667,18 @@
                              :font-size "13px"}}
              [:tbody
               [:tr [:td {:style {:text-align "center"}}
-                    (email-body state)]]]]]]]]
-        ]]]]]])
+                    (email-body state)]]]]]]
+          [:tr
+           [:td {:style { :background-color "#F3F3F3"}}
+                [:table
+                 [:tbody
+                  [:tr [:td [:img {:style {:height "23px"
+                                           }
+                                   :src "https://pbs.twimg.com/profile_images/616309728688238592/pBeeJQDQ.png"}]]
+                   [:td [:a {:href "https://github.com/aryabhatiya"} "https://github.com/aryabhatiya"]]
+                   [:td [:img {:style {:height "23px"
+                                       :padding-right "3px"}
+                               :src "https://cdn1.iconfinder.com/data/icons/simple-icons/2048/heroku-2048-black.png"}]]
+                   [:td [:a {:href "https://pacific-earth-90914.herokuapp.com/#/"}
+                         "https://pacific-earth-90914.herokuapp.com/#/"]]
+                   ]]]]]]]]]]]]])


Extract function that runs around-alls

Extract function that runs around-alls

--- src/cljx/speclj/running.cljx
+++ src/cljx/speclj/running.cljx
@@ -107,6 +107,17 @@
   (let [results (results-for-context description reporters)]
     (do-child-contexts description results reporters)))

+(defn- with-around-alls [description run-characteristics-fn]
+  (let [around-alls (reduce (fn [composed around-all]
+                              (fn [f] ((.-body around-all)
+                                         (partial composed f))))
+
+                            (fn [f] (f))
+
+                            (reverse @(.-around-alls description)))]
+
+    (around-alls run-characteristics-fn)))
+
 (defn do-description [description reporters]
   (let [tag-sets (tag-sets-for description)]
     (when (some pass-tag-filter? tag-sets)
@@ -114,19 +125,14 @@
       (with-withs-bound description
         (fn []
           (eval-components @(.-before-alls description))
-          (let [around-alls (reduce (fn [composed around-all]
-                                      (fn [f] ((.-body around-all)
-                                                 (fn [] (composed f)))))

-                                    (fn [f] (f))
+          (try
+            (with-around-alls
+              description
+              (partial nested-results-for-context description reporters))

-                                    (reverse @(.-around-alls description)))]
-            (try
-              (around-alls (fn []
-                (nested-results-for-context description reporters)))
-
-              (finally
-                (reset-withs @(.-with-alls description))))))))))
+            (finally
+              (reset-withs @(.-with-alls description)))))))))

 (defn process-compile-error [runner e]
   (let [error-result (error-result e)]


Removed duplicate function.

Removed duplicate function.

--- src/cmr/sample_data/core.clj
+++ src/cmr/sample_data/core.clj
@@ -55,9 +55,9 @@

 (defn get-ges-disc-airx3std-opendap-service
   ([]
-   (get-ges-disc-airx3std-opendap-service const/default-handler-key))
+    (get-ges-disc-airx3std-opendap-service [:json :edn]))
   ([as-data]
-   (util/get-file
+    (util/get-file
      (str const/services-resource "/GES_DISC/AIRX3STD/OPeNDAP.umm-json")
      as-data)))

@@ -81,11 +81,3 @@
    (util/get-files
     (str const/variables-resource "/GES_DISC/AIRX3STD/CH4")
     as-data)))
-
-(defn get-ges-disc-airx3std-opendap-service
-  ([]
-    (get-ges-disc-airx3std-opendap-service [:json :edn]))
-  ([as-data]
-    (util/get-file
-     (str const/services-resource "/GES_DISC/AIRX3STD/OPeNDAP.json")
-     as-data)))


add min degree

add min degree

--- src/clj/ceres_analytics/measures.clj
+++ src/clj/ceres_analytics/measures.clj
@@ -111,6 +111,26 @@
        (map
         (fn [sub-cs]
           (map
-           (comp #(map degree %) #(mc/find-maps @db % {:ts {$lt t0}}))
+           (comp
+            #(apply max %)
+            #(map degree %)
+            #(mc/find-maps @db % {:ts {$lt t0}}))
            sub-cs))))
       (apply max (pmap #(degree (:_id %) t0 ) (mc/find-maps @db cs {:ts {$lt t0}}))))))
+
+(defn min-degree
+  "Compute minimum degree"
+  [entity t0]
+  (let [cs (dispatch-entity entity)]
+    (if (vector? cs)
+      (zipmap
+       (keys cs)
+       (map
+        (fn [sub-cs]
+          (map
+           (comp
+            #(apply min %)
+            #(map degree %)
+            #(mc/find-maps @db % {:ts {$lt t0}}))
+           sub-cs))))
+      (apply min (pmap #(degree (:_id %) t0 ) (mc/find-maps @db cs {:ts {$lt t0}}))))))


correcting images

correcting images

--- src/clj/zhluminus/routes/stories.clj
+++ src/clj/zhluminus/routes/stories.clj
@@ -66,11 +66,12 @@
           )
           ; body

-          (str/replace (subs page
+          (str/replace (str/replace (subs page
                     (+ (.indexOf page ">" (.indexOf page "div property=\"schema:text\"" (.indexOf page "node__content" 0) ))1)

                     (- (.indexOf page "<div class=\"extras-section\">" (.indexOf page "node__content" 0))1)
                     ) #"src=\"/sites" "src=\"https://zerohedge.com/sites")
+                      #"srcset=\"/sites" "src=\"https://zerohedge.com/sites")

         )


Øvelsesdata settes lokalt med input type=number og oppdateres til global state med en egen knapp.

--- src/cljs/movement/pages/session.cljs
+++ src/cljs/movement/pages/session.cljs
@@ -53,7 +53,7 @@
                                                   :error-handler (fn [r] nil)}))
     (or (= :next kw)
         (= :previous kw)) (let [slot-category (:slot-category movement)
-                                    new-movement (first (shuffle (kw movement)))]
+                                new-movement (first (shuffle (kw movement)))]
                             (GET "movement" {:params        {:name new-movement}
                                              :handler       (fn [new-movement]
                                                               (let [new-movement (assoc new-movement :slot-category slot-category)
@@ -149,7 +149,7 @@
   [{:keys [name image slot-category measurement previous next
            rep set performed-sets distance duration weight rest] :as m}
    part-number]
-  (let [parts  (session/get-in [:movement-session :parts])
+  (let [parts (session/get-in [:movement-session :parts])
         pos (positions #{m} (get parts part-number))
         expand (atom false)]
     (fn []
@@ -195,6 +195,39 @@
         (when @expand
           [:div
            [:div.pure-g
+            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
+             [:label "Repetisjoner"]
+             [:input {:style {:width 75}
+                      :id    "rep-input" :type "number" :defaultValue rep :min 0}]]
+            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
+             [:label "Avstand"]
+             [:input {:style {:width 75}
+                      :id    "distance-input" :type "number" :defaultValue distance :min 0}]]
+            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
+             [:label "Tid"]
+             [:input {:style {:width 75}
+                      :id    "duration-input" :type "number" :defaultValue duration :min 0}]]
+            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
+             [:label "Vekt"]
+             [:input {:style {:width 75}
+                      :id    "weight-input" :type "number" :defaultValue weight :min 0}]]
+            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
+             [:label "Hvile"]
+             [:input {:style {:width 75}
+                      :id    "rest-input" :type "number" :defaultValue rest :min 0}]]
+            [:a.pure-u-1-3.pure-button.pure-button-primary {:style    {:margin "5px 5px 5px 5px"}
+                                                            :on-click #(let [rep-input (-> (.getElementById js/document "rep-input") .-value int)
+                                                                             distance-input (-> (.getElementById js/document "distance-input") .-value int)
+                                                                             duration-input (-> (.getElementById js/document "duration-input") .-value int)
+                                                                             weight-input (-> (.getElementById js/document "weight-input") .-value int)
+                                                                             rest-input (-> (.getElementById js/document "rest-input") .-value int)
+                                                                             new-movement (assoc m :rep rep-input :distance distance-input :duration duration-input
+                                                                                :weight weight-input :rest rest-input)
+                                                                             new-part (assoc (get parts part-number) (int pos) new-movement)
+                                                                             ;; todo: strategi for å bestemme hvilke data som har presedens når flere verdier sammen ikke gir mening.
+                                                                             ]
+                                                                        (session/assoc-in! [:movement-session :parts part-number] new-part))} "Oppdater"]]
+           [:div.pure-g
             [:a.pure-u.pure-button {:style   {:margin "5px 5px 5px 5px"}
                                     :onClick #(remove-movement % m part-number) :onTouchEnd #(remove-movement % m part-number)
                                     :title   "Fjern øvelse"}
@@ -214,36 +247,7 @@
                                       :onTouchEnd #(replace-movement % {:kw :next :movement m :part-number part-number}) :title "Bytt med vanskeligere"}
                [:i.fa.fa-arrow-up {:style {:color "#99cc99" :opacity 0.8}}]
                "Bytt med vanskeligere"])]
-           [:div.pure-g
-            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
-             [:label "Repetisjoner"]
-             [:input {:style {:width 75}
-                      :type  "number" :value rep :min 0 :on-change (fn [v] (let [new-movement (assoc m :rep (int (-> v .-target .-value)))
-                                                                                new-part (assoc (get parts part-number) (int pos) new-movement)]
-                                                                             (session/assoc-in! [:movement-session :parts part-number] new-part)))}]]
-            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
-             [:label "Avstand"]
-             [:input {:style {:width 75}
-                      :type "number" :value distance :min 0 :on-change #(pr "changed!")}]]
-            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
-             [:label "Tid"]
-             [:input {:style {:width 75}
-                      :type "number" :value duration :min 0 :on-change #(pr "changed!")}]]
-            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
-             [:label "Vekt"]
-             [:input {:style {:width 75}
-                      :type "number" :value weight :min 0 :on-change #(pr "changed!")}]]
-            [:div.pure-u {:style {:margin "5px 5px 5px 5px"}}
-             [:label "Hvile"]
-             [:input {:style {:width 75}
-                      :type "number" :value rest :min 0 :on-change #(pr "changed!")}]]
-
-
-            #_[rep-component rep m part-number]
-              #_[distance-component distance m part-number]
-              #_[duration-component duration m part-number]
-              #_[weight-component weight m part-number]
-              #_[rest-component rest m part-number]]])]])))
+           ])]])))

 (defn add-movement-component []
   (let [show-search-input? (atom false)]
@@ -367,10 +371,10 @@
         hash-tags (vec (re-seq #"#[\w]+" (:comment session)))
         session (assoc session :parts new-parts :date-time date-time :tags hash-tags)]
     (POST "store-session"
-            {:params        {:session session
-                             :user-id (:user-id (session/get :user))}
-             :handler       (fn [] (reset! s true))
-             :error-handler (fn [r] (pr r))})))
+          {:params        {:session session
+                           :user-id (:user-id (session/get :user))}
+           :handler       (fn [] (reset! s true))
+           :error-handler (fn [r] (pr r))})))

 (defn finish-session-component []
   ;; Etter trykk på avslutt&lagre bør den oppdaterte feeden vises


badge pdf fix

badge pdf fix

--- src/clj/salava/badge/pdf.clj
+++ src/clj/salava/badge/pdf.clj
@@ -80,7 +80,7 @@
         user-data (ud/user-information ctx user-id)
         ul (if (blank? (:language user-data)) "en" (:language user-data))
         font-path  (first (mapcat #(get-in ctx [:config % :font] []) (get-plugins ctx)))
-        font  {:ttf-name (str site-url font-path) :encoding :unicode}
+        font  {:encoding :unicode :ttf-name (str site-url font-path)}
         stylesheet {:heading-name {:color [127 113 121]
                                    :family :times-roman
                                    :align :center}
@@ -93,7 +93,7 @@
                     :chunk {:size 11
                             :style :bold}}

-        pdf-settings  (if (empty? font-path) {:stylesheet stylesheet  :bottom-margin 0 :footer {:page-numbers true :align :right}} {:font font :stylesheet stylesheet  :bottom-margin 0 :footer {:page-numbers false :align :right} :register-system-fonts? true})
+        pdf-settings  (if (empty? font-path) {:stylesheet stylesheet  :bottom-margin 0 :footer {:page-numbers true :align :right}} {:font font :stylesheet stylesheet  :bottom-margin 0 :footer {:page-numbers false :align :right}}) ;:register-system-fonts? true})
         badge-template (pdf/template
                          (let [template #(cons [:paragraph]  [ (if (and (not (= "-" (:image_file %)))(ends-with? (:image_file %) "png"))
                                                                  [:image {:width 85 :height 85 :align :center} (str data-dir "/" (:image_file %))]


Timestamp parser fix

Timestamp parser fix

--- src/clj/salava/core/util.clj
+++ src/clj/salava/core/util.clj
@@ -149,5 +149,5 @@
   (cond
     (integer? s) s
     (str/blank? s) nil
-    (re-find #"^[0-9]+$" (str/trim s)) (Long. s)
-    :else (tc/to-long s)))
+    (re-find #"^[0-9]+$" s) (Long. s)
+    (re-find #"^\d{4}-\d\d-\d\d" s) (some-> (tc/to-long s) (quot 1000))))


removed unused code

removed unused code

--- src/clj/skidskytte/views.clj
+++ src/clj/skidskytte/views.clj
@@ -1,7 +1,6 @@
 (ns skidskytte.views
   (:require [hiccup.core :as h]
             [hiccup.page :as hp]))
-

 (def matomo "<!-- Matomo -->
   <script type=\"text/javascript\">
@@ -18,15 +17,6 @@
   })();
   </script>
   <!-- End Matomo Code -->")
-
-(defn style [href]
-  [:link {:rel :stylesheet :href href}])
-
-(defn styles [& urls]
-  (for [url urls] (style url)))
-
-(defn js-var [var value]
-  (str "var " (name var) "=" value ";"))

 (defn base [sport]
   (hp/html5


allow options for form fields

allow options for form fields

--- src/clj_money/web/shared.clj
+++ src/clj_money/web/shared.clj
@@ -181,7 +181,8 @@
   "Renders a HTML input field"
   ([model attribute options]
    [:div.form-group {:class (when (validation/has-error? model attribute)"has-error")}
-    [:label.control-label {:for attribute} (humanize attribute)]
+    (when-not (:suppress-label? options)
+      [:label.control-label {:for attribute} (humanize attribute)])
     [:input.form-control (merge options {:id attribute
                                          :name attribute
                                          :value (get model attribute)})]
@@ -199,14 +200,17 @@
    (input-field model attribute (merge options {:type :password}))))

 (defn select-field
-  [model attribute options]
-  [:div.form-group
-   [:label {:for attribute} (humanize attribute)]
-   [:select.form-control {:id attribute :name attribute}
-    (map #(vector :option {:value (:value %)
-                           :selected (if (= (attribute model)
-                                            (:value %))
-                                       true
-                                       nil)}
-                  (:caption %))
-         options)]])
+  ([model attribute option-items]
+   (select-field model attribute option-items {}))
+  ([model attribute option-items options]
+   [:div.form-group
+    (when-not (:suppress-label? options)
+      [:label {:for attribute} (humanize attribute)])
+    [:select.form-control {:id attribute :name attribute}
+     (map #(vector :option {:value (:value %)
+                            :selected (if (= (attribute model)
+                                             (:value %))
+                                        true
+                                        nil)}
+                   (:caption %))
+          option-items)]]))