134 lines
3.6 KiB
Clojure
134 lines
3.6 KiB
Clojure
(ns lemondronor.advisorycircular.util
|
|
(:require [cemerick.url :as c-url]
|
|
["fs" :as fs]
|
|
[goog.string :as gstring]
|
|
[kitchen-async.promise :as p]
|
|
[lemondronor.advisorycircular.logging :as logging]
|
|
["request-promise-native" :as request]
|
|
["js-yaml" :as yaml]))
|
|
|
|
(declare logger log-debug log-verbose log-info log-warn log-error)
|
|
(logging/deflog "util" logger)
|
|
|
|
(def fs-promises (.-promises fs))
|
|
|
|
(defn ^boolean ends-with?
|
|
[s substr]
|
|
(gstring/endsWith s substr))
|
|
|
|
;; Reads a file, returns a promise resolving to the file contents.
|
|
|
|
(defn read-file
|
|
([path]
|
|
(read-file path {}))
|
|
([path options]
|
|
(.readFile fs-promises path (clj->js options))))
|
|
|
|
|
|
;; Writes a file, returns a promise that resolves to no arguments on
|
|
;; success.
|
|
|
|
(defn write-file [path data options]
|
|
(.writeFile fs-promises path data (clj->js options)))
|
|
|
|
|
|
;; Reads a YAML config file. Returns a promise that resolves to the
|
|
;; parsed YAML.
|
|
|
|
(defn read-config [path]
|
|
(log-verbose "Reading config file %s" path)
|
|
(p/try
|
|
(p/let [data (read-file path {:encoding "utf-8"})]
|
|
(let [config (-> (yaml/safeLoad data)
|
|
(js->clj :keywordize-keys true))]
|
|
(or config {})))
|
|
(p/catch :default e
|
|
(throw (str "Error reading config file '" path "': " e)))))
|
|
|
|
|
|
;; Fetches a URL. Returns a promise that resolves to the body of the
|
|
;; response.
|
|
|
|
(defn http-get
|
|
([url]
|
|
(http-get url {}))
|
|
([url options]
|
|
(let [query (or (:query options) {})
|
|
options (dissoc options :query)
|
|
parsed-url (-> (c-url/url (str url))
|
|
(assoc :query query))
|
|
;; Work around c-url bug to re-append any stripped trailing slash.
|
|
;; https://github.com/cemerick/url/issues/24
|
|
url (if (ends-with? (str url) "/")
|
|
(str parsed-url "/")
|
|
parsed-url)]
|
|
(p/do
|
|
(log-verbose "Fetching %s" url)
|
|
(p/let [result (.get request
|
|
(clj->js (merge {:url (str url) :gzip true} options)))]
|
|
result)))))
|
|
|
|
|
|
;; From https://github.com/puppetlabs/clj-kitchensink/blob/cfea4a16e4d2e15a2d391131a163b4eeb60d872e/src/puppetlabs/kitchensink/core.clj#L311-L332
|
|
|
|
(defn deep-merge
|
|
"Deeply merges maps so that nested maps are combined rather than replaced.
|
|
For example:
|
|
(deep-merge {:foo {:bar :baz}} {:foo {:fuzz :buzz}})
|
|
;;=> {:foo {:bar :baz, :fuzz :buzz}}
|
|
;; contrast with clojure.core/merge
|
|
(merge {:foo {:bar :baz}} {:foo {:fuzz :buzz}})
|
|
;;=> {:foo {:fuzz :quzz}} ; note how last value for :foo wins"
|
|
[& vs]
|
|
(if (every? map? vs)
|
|
(apply merge-with deep-merge vs)
|
|
(last vs)))
|
|
|
|
|
|
;; From https://stackoverflow.com/a/21769626/122762
|
|
|
|
(defn nested-keys [m]
|
|
(if (map? m)
|
|
(vec
|
|
(mapcat (fn [[k v]]
|
|
(let [sub (nested-keys v)
|
|
nested (map #(into [k] %) (filter (comp not empty?) sub))]
|
|
(if (seq nested)
|
|
nested
|
|
[[k]])))
|
|
m))
|
|
[]))
|
|
|
|
|
|
(defn format-utc-ts [millis]
|
|
(let [date (js/Date. millis)]
|
|
(str (.getUTCFullYear date)
|
|
(.getUTCMonth date)
|
|
(.getUTCDate date)
|
|
"-"
|
|
(.getUTCHours date)
|
|
(.getUTCMinutes date)
|
|
(.getUTCSeconds date)
|
|
(.getUTCMilliseconds date))))
|
|
|
|
|
|
(defn feet-to-meters [f]
|
|
(* f 0.3048))
|
|
|
|
|
|
(defn timeout
|
|
([ms]
|
|
(timeout ms nil))
|
|
([ms v]
|
|
(p/promise [resolve]
|
|
(js/setTimeout #(resolve v) ms))))
|
|
|
|
|
|
(defn write-stream-to-file [stream path]
|
|
(p/promise
|
|
[resolve reject]
|
|
(let [out (fs/createWriteStream path)]
|
|
(.pipe stream out)
|
|
(.on out "finish" #(resolve nil))
|
|
(.on out "error" #(reject %)))))
|