(ns ^{:skip-wiki true} clojure.core.specs.alpha
  (:require [clojure.spec.alpha :as s]))

;;;; destructure

(s/def ::local-name (s/and simple-symbol? #(not= '& %)))

(s/def ::binding-form
  (s/or :sym ::local-name
        :seq ::seq-binding-form
        :map ::map-binding-form))

;; sequential destructuring

(s/def ::seq-binding-form
  (s/and vector?
         (s/cat :elems (s/* ::binding-form)
                :rest (s/? (s/cat :amp #{'&} :form ::binding-form))
                :as (s/? (s/cat :as #{:as} :sym ::local-name)))))

;; map destructuring

(s/def ::keys (s/coll-of ident? :kind vector?))
(s/def ::syms (s/coll-of symbol? :kind vector?))
(s/def ::strs (s/coll-of simple-symbol? :kind vector?))
(s/def ::or (s/map-of simple-symbol? any?))
(s/def ::as ::local-name)

(s/def ::map-special-binding
  (s/keys :opt-un [::as ::or ::keys ::syms ::strs]))

(s/def ::map-binding (s/tuple ::binding-form any?))

(s/def ::ns-keys
  (s/tuple
    (s/and qualified-keyword? #(-> % name #{"keys" "syms"}))
    (s/coll-of simple-symbol? :kind vector?)))

(s/def ::map-bindings
  (s/every (s/or :mb ::map-binding
                 :nsk ::ns-keys
                 :msb (s/tuple #{:as :or :keys :syms :strs} any?)) :into {}))

(s/def ::map-binding-form (s/merge ::map-bindings ::map-special-binding))

;; bindings

(s/def ::binding (s/cat :binding ::binding-form :init-expr any?))
(s/def ::bindings (s/and vector? (s/* ::binding)))

;; let, if-let, when-let

(s/fdef clojure.core/let
  :args (s/cat :bindings ::bindings
               :body (s/* any?)))

(s/fdef clojure.core/if-let
  :args (s/cat :bindings (s/and vector? ::binding)
               :then any?
               :else (s/? any?)))

(s/fdef clojure.core/when-let
  :args (s/cat :bindings (s/and vector? ::binding)
               :body (s/* any?)))

;; defn, defn-, fn

(s/def ::arg-list
  (s/and
    vector?
    (s/cat :args (s/* ::binding-form)
           :varargs (s/? (s/cat :amp #{'&} :form ::binding-form)))))

(s/def ::args+body
  (s/cat :args ::arg-list
         :body (s/alt :prepost+body (s/cat :prepost map?
                                           :body (s/+ any?))
                      :body (s/* any?))))

(s/def ::defn-args
  (s/cat :name simple-symbol?
         :docstring (s/? string?)
         :meta (s/? map?)
         :bs (s/alt :arity-1 ::args+body
                    :arity-n (s/cat :bodies (s/+ (s/spec ::args+body))
                                    :attr (s/? map?)))))

(s/fdef clojure.core/defn
  :args ::defn-args
  :ret any?)

(s/fdef clojure.core/defn-
  :args ::defn-args
  :ret any?)

(s/fdef clojure.core/fn
  :args (s/cat :name (s/? simple-symbol?)
               :bs (s/alt :arity-1 ::args+body
                          :arity-n (s/+ (s/spec ::args+body))))
  :ret any?)

;;;; ns

(s/def ::exclude (s/coll-of simple-symbol?))
(s/def ::only (s/coll-of simple-symbol?))
(s/def ::rename (s/map-of simple-symbol? simple-symbol?))
(s/def ::filters (s/keys* :opt-un [::exclude ::only ::rename]))

(s/def ::ns-refer-clojure
  (s/spec (s/cat :clause #{:refer-clojure}
                 :filters ::filters)))

(s/def ::refer (s/or :all #{:all}
                     :syms (s/coll-of simple-symbol?)))

(s/def ::prefix-list
  (s/spec
    (s/cat :prefix simple-symbol?
           :libspecs (s/+ ::libspec))))

(s/def ::libspec
  (s/alt :lib simple-symbol?
         :lib+opts (s/spec (s/cat :lib simple-symbol?
                                  :options (s/keys* :opt-un [::as ::refer])))))

(s/def ::ns-require
  (s/spec (s/cat :clause #{:require}
                 :body (s/+ (s/alt :libspec ::libspec
                                   :prefix-list ::prefix-list
                                   :flag #{:reload :reload-all :verbose})))))

(s/def ::package-list
  (s/spec
    (s/cat :package simple-symbol?
           :classes (s/* simple-symbol?))))

(s/def ::import-list
  (s/* (s/alt :class simple-symbol?
              :package-list ::package-list)))

(s/def ::ns-import
  (s/spec
    (s/cat :clause #{:import}
           :classes ::import-list)))

(s/def ::ns-refer
  (s/spec (s/cat :clause #{:refer}
                 :lib simple-symbol?
                 :filters ::filters)))

;; same as ::prefix-list, but with ::use-libspec instead
(s/def ::use-prefix-list
  (s/spec
    (s/cat :prefix simple-symbol?
           :libspecs (s/+ ::use-libspec))))

;; same as ::libspec, but also supports the ::filters options in the libspec
(s/def ::use-libspec
  (s/alt :lib simple-symbol?
         :lib+opts (s/spec (s/cat :lib simple-symbol?
                                  :options (s/keys* :opt-un [::as ::refer ::exclude ::only ::rename])))))

(s/def ::ns-use
  (s/spec (s/cat :clause #{:use}
                 :libs (s/+ (s/alt :libspec ::use-libspec
                                   :prefix-list ::use-prefix-list
                                   :flag #{:reload :reload-all :verbose})))))

(s/def ::ns-load
  (s/spec (s/cat :clause #{:load}
                 :libs (s/* string?))))

(s/def ::name simple-symbol?)
(s/def ::extends simple-symbol?)
(s/def ::implements (s/coll-of simple-symbol? :kind vector?))
(s/def ::init symbol?)
(s/def ::class-ident (s/or :class simple-symbol? :class-name string?))
(s/def ::signature (s/coll-of ::class-ident :kind vector?))
(s/def ::constructors (s/map-of ::signature ::signature))
(s/def ::post-init symbol?)
(s/def ::method (s/and vector?
                  (s/cat :name simple-symbol?
                         :param-types ::signature
                         :return-type simple-symbol?)))
(s/def ::methods (s/coll-of ::method :kind vector?))
(s/def ::main boolean?)
(s/def ::factory simple-symbol?)
(s/def ::state simple-symbol?)
(s/def ::get simple-symbol?)
(s/def ::set simple-symbol?)
(s/def ::expose (s/keys :opt-un [::get ::set]))
(s/def ::exposes (s/map-of simple-symbol? ::expose))
(s/def ::prefix string?)
(s/def ::impl-ns simple-symbol?)
(s/def ::load-impl-ns boolean?)

(s/def ::ns-gen-class
  (s/spec (s/cat :clause #{:gen-class}
                 :options (s/keys* :opt-un [::name ::extends ::implements
                                            ::init ::constructors ::post-init
                                            ::methods ::main ::factory ::state
                                            ::exposes ::prefix ::impl-ns ::load-impl-ns]))))

(s/def ::ns-clauses
  (s/* (s/alt :refer-clojure ::ns-refer-clojure
              :require ::ns-require
              :import ::ns-import
              :use ::ns-use
              :refer ::ns-refer
              :load ::ns-load
              :gen-class ::ns-gen-class)))

(s/def ::ns-form
  (s/cat :name simple-symbol?
         :docstring (s/? string?)
         :attr-map (s/? map?)
         :clauses ::ns-clauses))

(s/fdef clojure.core/ns
  :args ::ns-form)

(defmacro ^:private quotable
  "Returns a spec that accepts both the spec and a (quote ...) form of the spec"
  [spec]
  `(s/or :spec ~spec :quoted-spec (s/cat :quote #{'quote} :spec ~spec)))

(s/def ::quotable-import-list
  (s/* (s/alt :class (quotable simple-symbol?)
              :package-list (quotable ::package-list))))

(s/fdef clojure.core/import
  :args ::quotable-import-list)

(s/fdef clojure.core/refer-clojure
  :args (s/* (s/alt
               :exclude (s/cat :op (quotable #{:exclude}) :arg (quotable ::exclude))
               :only (s/cat :op (quotable #{:only}) :arg (quotable ::only))
               :rename (s/cat :op (quotable #{:rename}) :arg (quotable ::rename)))))

(ns ^{:skip-wiki true} clojure.core.specs.alpha
  (:require [clojure.spec.alpha :as s]))

;;;; destructure

(s/def ::local-name (s/and simple-symbol? #(not= '& %)))

(s/def ::binding-form
  (s/or :sym ::local-name
        :seq ::seq-binding-form
        :map ::map-binding-form))

;; sequential destructuring

(s/def ::seq-binding-form
  (s/and vector?
         (s/cat :elems (s/* ::binding-form)
                :rest (s/? (s/cat :amp #{'&} :form ::binding-form))
                :as (s/? (s/cat :as #{:as} :sym ::local-name)))))

;; map destructuring

(s/def ::keys (s/coll-of ident? :kind vector?))
(s/def ::syms (s/coll-of symbol? :kind vector?))
(s/def ::strs (s/coll-of simple-symbol? :kind vector?))
(s/def ::or (s/map-of simple-symbol? any?))
(s/def ::as ::local-name)

(s/def ::map-special-binding
  (s/keys :opt-un [::as ::or ::keys ::syms ::strs]))

(s/def ::map-binding (s/tuple ::binding-form any?))

(s/def ::ns-keys
  (s/tuple
    (s/and qualified-keyword? #(-> % name #{"keys" "syms"}))
    (s/coll-of simple-symbol? :kind vector?)))

(s/def ::map-bindings
  (s/every (s/or :mb ::map-binding
                 :nsk ::ns-keys
                 :msb (s/tuple #{:as :or :keys :syms :strs} any?)) :into {}))

(s/def ::map-binding-form (s/merge ::map-bindings ::map-special-binding))

;; bindings

(s/def ::binding (s/cat :binding ::binding-form :init-expr any?))
(s/def ::bindings (s/and vector? (s/* ::binding)))

;; let, if-let, when-let

(s/fdef clojure.core/let
  :args (s/cat :bindings ::bindings
               :body (s/* any?)))

(s/fdef clojure.core/if-let
  :args (s/cat :bindings (s/and vector? ::binding)
               :then any?
               :else (s/? any?)))

(s/fdef clojure.core/when-let
  :args (s/cat :bindings (s/and vector? ::binding)
               :body (s/* any?)))

;; defn, defn-, fn

(s/def ::arg-list
  (s/and
    vector?
    (s/cat :args (s/* ::binding-form)
           :varargs (s/? (s/cat :amp #{'&} :form ::binding-form)))))

(s/def ::args+body
  (s/cat :args ::arg-list
         :body (s/alt :prepost+body (s/cat :prepost map?
                                           :body (s/+ any?))
                      :body (s/* any?))))

(s/def ::defn-args
  (s/cat :name simple-symbol?
         :docstring (s/? string?)
         :meta (s/? map?)
         :bs (s/alt :arity-1 ::args+body
                    :arity-n (s/cat :bodies (s/+ (s/spec ::args+body))
                                    :attr (s/? map?)))))

(s/fdef clojure.core/defn
  :args ::defn-args
  :ret any?)

(s/fdef clojure.core/defn-
  :args ::defn-args
  :ret any?)

(s/fdef clojure.core/fn
  :args (s/cat :name (s/? simple-symbol?)
               :bs (s/alt :arity-1 ::args+body
                          :arity-n (s/+ (s/spec ::args+body))))
  :ret any?)

;;;; ns

(s/def ::exclude (s/coll-of simple-symbol?))
(s/def ::only (s/coll-of simple-symbol?))
(s/def ::rename (s/map-of simple-symbol? simple-symbol?))
(s/def ::filters (s/keys* :opt-un [::exclude ::only ::rename]))

(s/def ::ns-refer-clojure
  (s/spec (s/cat :clause #{:refer-clojure}
                 :filters ::filters)))

(s/def ::refer (s/or :all #{:all}
                     :syms (s/coll-of simple-symbol?)))

(s/def ::prefix-list
  (s/spec
    (s/cat :prefix simple-symbol?
           :libspecs (s/+ ::libspec))))

(s/def ::libspec
  (s/alt :lib simple-symbol?
         :lib+opts (s/spec (s/cat :lib simple-symbol?
                                  :options (s/keys* :opt-un [::as ::refer])))))

(s/def ::ns-require
  (s/spec (s/cat :clause #{:require}
                 :body (s/+ (s/alt :libspec ::libspec
                                   :prefix-list ::prefix-list
                                   :flag #{:reload :reload-all :verbose})))))

(s/def ::package-list
  (s/spec
    (s/cat :package simple-symbol?
           :classes (s/* simple-symbol?))))

(s/def ::import-list
  (s/* (s/alt :class simple-symbol?
              :package-list ::package-list)))

(s/def ::ns-import
  (s/spec
    (s/cat :clause #{:import}
           :classes ::import-list)))

(s/def ::ns-refer
  (s/spec (s/cat :clause #{:refer}
                 :lib simple-symbol?
                 :filters ::filters)))

;; same as ::prefix-list, but with ::use-libspec instead
(s/def ::use-prefix-list
  (s/spec
    (s/cat :prefix simple-symbol?
           :libspecs (s/+ ::use-libspec))))

;; same as ::libspec, but also supports the ::filters options in the libspec
(s/def ::use-libspec
  (s/alt :lib simple-symbol?
         :lib+opts (s/spec (s/cat :lib simple-symbol?
                                  :options (s/keys* :opt-un [::as ::refer ::exclude ::only ::rename])))))

(s/def ::ns-use
  (s/spec (s/cat :clause #{:use}
                 :libs (s/+ (s/alt :libspec ::use-libspec
                                   :prefix-list ::use-prefix-list
                                   :flag #{:reload :reload-all :verbose})))))

(s/def ::ns-load
  (s/spec (s/cat :clause #{:load}
                 :libs (s/* string?))))

(s/def ::name simple-symbol?)
(s/def ::extends simple-symbol?)
(s/def ::implements (s/coll-of simple-symbol? :kind vector?))
(s/def ::init symbol?)
(s/def ::class-ident (s/or :class simple-symbol? :class-name string?))
(s/def ::signature (s/coll-of ::class-ident :kind vector?))
(s/def ::constructors (s/map-of ::signature ::signature))
(s/def ::post-init symbol?)
(s/def ::method (s/and vector?
                  (s/cat :name simple-symbol?
                         :param-types ::signature
                         :return-type ::class-ident)))
(s/def ::methods (s/coll-of ::method :kind vector?))
(s/def ::main boolean?)
(s/def ::factory simple-symbol?)
(s/def ::state simple-symbol?)
(s/def ::get simple-symbol?)
(s/def ::set simple-symbol?)
(s/def ::expose (s/keys :opt-un [::get ::set]))
(s/def ::exposes (s/map-of simple-symbol? ::expose))
(s/def ::prefix string?)
(s/def ::impl-ns simple-symbol?)
(s/def ::load-impl-ns boolean?)

(s/def ::ns-gen-class
  (s/spec (s/cat :clause #{:gen-class}
                 :options (s/keys* :opt-un [::name ::extends ::implements
                                            ::init ::constructors ::post-init
                                            ::methods ::main ::factory ::state
                                            ::exposes ::prefix ::impl-ns ::load-impl-ns]))))

(s/def ::ns-clauses
  (s/* (s/alt :refer-clojure ::ns-refer-clojure
              :require ::ns-require
              :import ::ns-import
              :use ::ns-use
              :refer ::ns-refer
              :load ::ns-load
              :gen-class ::ns-gen-class)))

(s/def ::ns-form
  (s/cat :name simple-symbol?
         :docstring (s/? string?)
         :attr-map (s/? map?)
         :clauses ::ns-clauses))

(s/fdef clojure.core/ns
  :args ::ns-form)

(defmacro ^:private quotable
  "Returns a spec that accepts both the spec and a (quote ...) form of the spec"
  [spec]
  `(s/or :spec ~spec :quoted-spec (s/cat :quote #{'quote} :spec ~spec)))

(s/def ::quotable-import-list
  (s/* (s/alt :class (quotable simple-symbol?)
              :package-list (quotable ::package-list))))

(s/fdef clojure.core/import
  :args ::quotable-import-list)

(s/fdef clojure.core/refer-clojure
  :args (s/* (s/alt
               :exclude (s/cat :op (quotable #{:exclude}) :arg (quotable ::exclude))
               :only (s/cat :op (quotable #{:only}) :arg (quotable ::only))
               :rename (s/cat :op (quotable #{:rename}) :arg (quotable ::rename)))))

CLJ-2314: allow string array hinting in genclass return type


(ns clj-http.test.conn-mgr
  (:require [clj-http.conn-mgr :as conn-mgr]
            [clj-http.core :as core]
            [clj-http.test.core :refer [run-server]]
            [clojure.test :refer :all]
            [ring.adapter.jetty :as ring])
  (:import (java.security KeyStore)
           (org.apache.http.conn.ssl SSLSocketFactory)
           (org.apache.http.impl.conn BasicClientConnectionManager)))

(def client-ks "test-resources/client-keystore")
(def client-ks-pass "keykey")
(def secure-request {:request-method :get :uri "/"
                     :server-port 18084 :scheme :https
                     :keystore client-ks :keystore-pass client-ks-pass
                     :trust-store client-ks :trust-store-pass client-ks-pass
                     :server-name "localhost" :insecure? true})

(defn secure-handler [req]
  (if (nil? (:ssl-client-cert req))
    {:status 403}
    {:status 200}))

(deftest load-keystore
  (let [ks (conn-mgr/get-keystore "test-resources/keystore" nil "keykey")]
    (is (instance? KeyStore ks))
    (is (> (.size ks) 0))))

(deftest use-existing-keystore
  (let [ks (conn-mgr/get-keystore "test-resources/keystore" nil "keykey")
        ks (conn-mgr/get-keystore ks nil nil)]
    (is (instance? KeyStore ks))
    (is (> (.size ks) 0))))

(deftest load-keystore-with-nil-pass
  (let [ks (conn-mgr/get-keystore "test-resources/keystore" nil nil)]
    (is (instance? KeyStore ks))))

(deftest keystore-scheme-factory
  (let [sr (conn-mgr/get-keystore-scheme-registry
            {:keystore client-ks :keystore-pass client-ks-pass
             :trust-store client-ks :trust-store-pass client-ks-pass})
        socket-factory (.getSchemeSocketFactory (.get sr "https"))]
    (is (instance? SSLSocketFactory socket-factory))))

(deftest ^:integration ssl-client-cert-get
  (let [server (ring/run-jetty secure-handler
                               {:port 18083 :ssl-port 18084
                                :ssl? true
                                :join? false
                                :keystore "test-resources/keystore"
                                :key-password "keykey"
                                :client-auth :want})]
    (try
      (let [resp (core/request {:request-method :get :uri "/get"
                                :server-port 18084 :scheme :https
                                :insecure? true :server-name "localhost"})]
        (is (= 403 (:status resp))))
      (let [resp (core/request secure-request)]
        (is (= 200 (:status resp))))
      (finally
        (.stop server)))))

(deftest ^:integration t-closed-conn-mgr-for-as-stream
  (run-server)
  (let [shutdown? (atom false)
        cm (proxy [BasicClientConnectionManager] []
             (shutdown []
               (reset! shutdown? true)))]
    (try
      (core/request {:request-method :get :uri "/timeout"
                     :server-port 18080 :scheme :http
                     :server-name "localhost"
                     ;; timeouts forces an exception being thrown
                     :socket-timeout 1
                     :conn-timeout 1
                     :connection-manager cm
                     :as :stream})
      (is false "request should have thrown an exception")
      (catch Exception e))
    (is @shutdown? "Connection manager has been shut down")))

(ns clj-http.test.conn-mgr
  (:require [clj-http.conn-mgr :as conn-mgr]
            [clj-http.core :as core]
            [clj-http.test.core :refer [run-server]]
            [clojure.test :refer :all]
            [ring.adapter.jetty :as ring])
  (:import (java.security KeyStore)
           (org.apache.http.conn.ssl SSLSocketFactory)
           (org.apache.http.impl.conn BasicClientConnectionManager)))

(def client-ks "test-resources/client-keystore")
(def client-ks-pass "keykey")
(def secure-request {:request-method :get :uri "/"
                     :server-port 18084 :scheme :https
                     :keystore client-ks :keystore-pass client-ks-pass
                     :trust-store client-ks :trust-store-pass client-ks-pass
                     :server-name "localhost" :insecure? true})

(defn secure-handler [req]
  (if (nil? (:ssl-client-cert req))
    {:status 403}
    {:status 200}))

(deftest load-keystore
  (let [ks (conn-mgr/get-keystore "test-resources/keystore" nil "keykey")]
    (is (instance? KeyStore ks))
    (is (> (.size ks) 0))))

(deftest use-existing-keystore
  (let [ks (conn-mgr/get-keystore "test-resources/keystore" nil "keykey")
        ks (conn-mgr/get-keystore ks)]
    (is (instance? KeyStore ks))
    (is (> (.size ks) 0))))

(deftest load-keystore-with-nil-pass
  (let [ks (conn-mgr/get-keystore "test-resources/keystore" nil nil)]
    (is (instance? KeyStore ks))))

(deftest keystore-scheme-factory
  (let [sr (conn-mgr/get-keystore-scheme-registry
            {:keystore client-ks :keystore-pass client-ks-pass
             :trust-store client-ks :trust-store-pass client-ks-pass})
        socket-factory (.getSchemeSocketFactory (.get sr "https"))]
    (is (instance? SSLSocketFactory socket-factory))))

(deftest ^:integration ssl-client-cert-get
  (let [server (ring/run-jetty secure-handler
                               {:port 18083 :ssl-port 18084
                                :ssl? true
                                :join? false
                                :keystore "test-resources/keystore"
                                :key-password "keykey"
                                :client-auth :want})]
    (try
      (let [resp (core/request {:request-method :get :uri "/get"
                                :server-port 18084 :scheme :https
                                :insecure? true :server-name "localhost"})]
        (is (= 403 (:status resp))))
      (let [resp (core/request secure-request)]
        (is (= 200 (:status resp))))
      (finally
        (.stop server)))))

(deftest ^:integration t-closed-conn-mgr-for-as-stream
  (run-server)
  (let [shutdown? (atom false)
        cm (proxy [BasicClientConnectionManager] []
             (shutdown []
               (reset! shutdown? true)))]
    (try
      (core/request {:request-method :get :uri "/timeout"
                     :server-port 18080 :scheme :http
                     :server-name "localhost"
                     ;; timeouts forces an exception being thrown
                     :socket-timeout 1
                     :conn-timeout 1
                     :connection-manager cm
                     :as :stream})
      (is false "request should have thrown an exception")
      (catch Exception e))
    (is @shutdown? "Connection manager has been shut down")))

test no-arity get-keystore method

test no-arity get-keystore method


(ns muon-clojure.core-test
  (:use midje.sweet)
  (:use muon-clojure.client)
  (:require [clojure.test :refer :all]
            [muon-clojure.server :refer :all]
            [muon-clojure.common :as mcc]
            [com.stuartsierra.component :as component]
            [clojure.core.async :refer [to-chan <!!]])
  (:import (com.google.common.eventbus EventBus)))

(defrecord TestMSImpl []
  MicroserviceStream
  (stream-mappings [this]
    [{:endpoint "stream-test" :type :hot-cold
      :fn-process (fn [params]
                    (to-chan
                     [{:val 1} {:val 2} {:val 3} {:val 4} {:val 5}]))}])
  MicroserviceRequest
  (request-mappings [this]
    [{:endpoint "post-endpoint"
      :fn-process (fn [resource]
                    {:val (inc (:val resource))})}
     {:endpoint "get-endpoint"
      :fn-process (fn [resource] {:test :ok})}]))

(let [uuid (.toString (java.util.UUID/randomUUID))
      ms (component/start
          (micro-service {:rabbit-url "amqp://localhost" #_:local
                          :service-identifier uuid
                          :tags ["dummy" "test"]
                          :implementation (->TestMSImpl)}))]
  (let [c (muon-client "amqp://localhost" #_:local (str uuid "-client")
                       "dummy" "test" "client")]
    (let [get-val
          (with-muon c (request! (str "request://" uuid "/get-endpoint")
                                 {:test :ok}))
          _ (println "After get-val")
          post-val
          (with-muon c (request! (str "request://" uuid "/post-endpoint")
                                 {:val 1}))
          _ (println "After post-val")
          stream-channel
          (with-muon c (subscribe!
                         (str "stream://" uuid "/stream-test")))
          _ (println "After stream-channel")
          stream-channel-order
          (with-muon c (subscribe!
                         (str "stream://" uuid "/stream-test")))
          _ (println "After stream-channel-order")
          not-ordered (<!! (clojure.core.async/reduce
                             (fn [prev n] (concat prev `(~n)))
                             '() stream-channel-order))
          _ (println "After not-ordered")
          post-many-vals
          (with-muon c (doall
                         (map (fn [_]
                                (request! (str "request://" uuid "/post-endpoint")
                                            {:val 1}))
                              (range 0 5))))]
      (fact "Query works as expected"
            get-val => {:test "ok"})
      (fact "Post works as expected"
            post-val => {:val 2.0})
      (fact "First element retrieved from stream is the first element provided by the service"
            (<!! stream-channel) => {:val 1.0})
      (fact "Stream results come ordered"
            (= not-ordered (sort-by :val not-ordered)) => true)
      (fact "There are 5 elements"
            (count not-ordered) => 5)
      (fact "Posting many times in a row works as expected"
            post-many-vals => (take 5 (repeat {:val 2.0})))
      (println not-ordered)))
  (component/stop ms))

(ns muon-clojure.core-test
  (:use midje.sweet)
  (:use muon-clojure.client)
  (:require [clojure.test :refer :all]
            [muon-clojure.server :refer :all]
            [muon-clojure.common :as mcc]
            [com.stuartsierra.component :as component]
            [clojure.core.async :refer [to-chan <!!]])
  (:import (com.google.common.eventbus EventBus)))

(defrecord TestMSImpl []
  MicroserviceStream
  (stream-mappings [this]
    [{:endpoint "stream-test" :type :hot-cold
      :fn-process (fn [params]
                    (to-chan
                     [{:val 1} {:val 2} {:val 3} {:val 4} {:val 5}]))}])
  MicroserviceRequest
  (request-mappings [this]
    [{:endpoint "post-endpoint"
      :fn-process (fn [resource]
                    {:val (inc (:val resource))})}
     {:endpoint "get-endpoint"
      :fn-process (fn [resource] {:test :ok})}]))

(let [uuid (.toString (java.util.UUID/randomUUID))
      ms (component/start
          (micro-service {:rabbit-url #_"amqp://localhost" :local
                          :service-identifier uuid
                          :tags ["dummy" "test"]
                          :implementation (->TestMSImpl)}))]
  (let [c (muon-client #_"amqp://localhost" :local (str uuid "-client")
                       "dummy" "test" "client")]
    (let [get-val
          (with-muon c (request! (str "request://" uuid "/get-endpoint")
                                 {:test :ok}))
          _ (println "After get-val")
          post-val
          (with-muon c (request! (str "request://" uuid "/post-endpoint")
                                 {:val 1}))
          _ (println "After post-val")
          stream-channel
          (with-muon c (subscribe!
                         (str "stream://" uuid "/stream-test")))
          _ (println "After stream-channel")
          stream-channel-order
          (with-muon c (subscribe!
                         (str "stream://" uuid "/stream-test")))
          _ (println "After stream-channel-order")
          not-ordered (<!! (clojure.core.async/reduce
                             (fn [prev n] (concat prev `(~n)))
                             '() stream-channel-order))
          _ (println "After not-ordered")
          post-many-vals
          (with-muon c (doall
                         (map (fn [_]
                                (request! (str "request://" uuid "/post-endpoint")
                                            {:val 1}))
                              (range 0 5))))]
      (fact "Query works as expected"
            get-val => {:test "ok"})
      (fact "Post works as expected"
            post-val => {:val 2.0})
      (fact "First element retrieved from stream is the first element provided by the service"
            (<!! stream-channel) => {:val 1.0})
      (fact "Stream results come ordered"
            (= not-ordered (sort-by :val not-ordered)) => true)
      (fact "There are 5 elements"
            (count not-ordered) => 5)
      (fact "Posting many times in a row works as expected"
            post-many-vals => (take 5 (repeat {:val 2.0})))
      (println not-ordered)))
  (component/stop ms))

Make tests work in local mode

Make tests work in local mode


(ns onyx.metrics.send-test
  (:require [clojure.core.async :refer [chan >!! <!! close! sliding-buffer]]
            [clojure.test :refer [deftest is testing]]
            [onyx.plugin.core-async :refer [take-segments!]]
            [onyx.test-helper :refer [with-test-env add-test-env-peers!]]
            [riemann.client]
            [gniazdo.core]
            [taoensso.timbre :refer [info warn fatal]]
            [onyx.lifecycle.metrics.metrics]
            [onyx.lifecycle.metrics.timbre]
            [onyx.lifecycle.metrics.riemann]
            [onyx.lifecycle.metrics.websocket]
            [onyx.api]))

(def n-messages 100000)

(defn my-inc [{:keys [n] :as segment}]
  (assoc segment :n (inc n)))

(deftest metrics-test
  (doseq [sender [:onyx.lifecycle.metrics.websocket/websocket-sender
                  ;; cannot test timbre-sender as info is a macro?
                  ;:onyx.lifecycle.metrics.timbre/timbre-sender
                  :onyx.lifecycle.metrics.riemann/riemann-sender]]

    (def in-chan (chan (inc n-messages)))

    (def out-chan (chan (sliding-buffer (inc n-messages))))

    (defn inject-in-ch [event lifecycle]
      {:core.async/chan in-chan})

    (defn inject-out-ch [event lifecycle]
      {:core.async/chan out-chan})

    (def in-calls
      {:lifecycle/before-task-start inject-in-ch})

    (def out-calls
      {:lifecycle/before-task-start inject-out-ch})

    (let [events (atom [])]
      (with-redefs [riemann.client/tcp-client (fn [opts] nil)
                    riemann.client/send-event (fn [_ event]
                                                (swap! events conj event)
                                                (future :sent))
                    ;taoensso.timbre/info (fn [& vs]
                    ;                       (swap! events conj :print))
                    gniazdo.core/connect (fn [_])
                    gniazdo.core/send-msg (fn [_ v]
                                            (swap! events conj v))]
        (let [id (java.util.UUID/randomUUID)
              env-config {:zookeeper/address "127.0.0.1:2188"
                          :zookeeper/server? true
                          :zookeeper.server/port 2188
                          :onyx/id id}
              peer-config {:zookeeper/address "127.0.0.1:2188"
                           :onyx/id id
                           :onyx.peer/job-scheduler :onyx.job-scheduler/greedy
                           :onyx.messaging/impl :aeron
                           :onyx.messaging/allow-short-circuit? true
                           :onyx.messaging/peer-port 40200
                           :onyx.messaging/bind-addr "localhost"}]
          (with-test-env [test-env [3 env-config peer-config]]
            (let [batch-size 20
                  catalog [{:onyx/name :in
                            :onyx/plugin :onyx.plugin.core-async/input
                            :onyx/type :input
                            :onyx/medium :core.async
                            :onyx/batch-size batch-size
                            :onyx/max-peers 1
                            :onyx/doc "Reads segments from a core.async channel"}

                           {:onyx/name :inc
                            :onyx/fn ::my-inc
                            :onyx/type :function
                            :onyx/batch-size batch-size}

                           {:onyx/name :out
                            :onyx/plugin :onyx.plugin.core-async/output
                            :onyx/type :output
                            :onyx/medium :core.async
                            :onyx/batch-size batch-size
                            :onyx/max-peers 1
                            :onyx/doc "Writes segments to a core.async channel"}]
                  workflow [[:in :inc] [:inc :out]]
                  lifecycles [{:lifecycle/task :in
                               :lifecycle/calls ::in-calls}
                              {:lifecycle/task :in
                               :lifecycle/calls :onyx.plugin.core-async/reader-calls}

                              {:lifecycle/task :all
                               :lifecycle/calls :onyx.lifecycle.metrics.metrics/calls
                               :websocket/address "ws://127.0.0.1:3000/metrics"
                               :metrics/buffer-capacity 10000
                               :metrics/workflow-name "test-workflow"
                               :riemann/address "localhost"
                               :riemann/port 12201
                               :metrics/sender-fn sender
                               :lifecycle/doc "Instruments a task's metrics"}

                              {:lifecycle/task :out
                               :lifecycle/calls ::out-calls}
                              {:lifecycle/task :out
                               :lifecycle/calls :onyx.plugin.core-async/writer-calls}]

                  _ (doseq [n (range n-messages)]
                      (>!! in-chan {:n n}))
                  _ (>!! in-chan :done)
                  _ (close! in-chan)
                  start-time (System/currentTimeMillis)
                  _ (onyx.api/submit-job peer-config
                                         {:catalog catalog
                                          :workflow workflow
                                          :lifecycles lifecycles
                                          :task-scheduler :onyx.task-scheduler/balanced})
                  results (take-segments! out-chan)
                  end-time (System/currentTimeMillis)]
              (let [expected (set (map (fn [x] {:n (inc x)}) (range n-messages)))]
                (is (= expected (set (butlast results))))
                (is (= :done (last results)))
                (is (> (count @events) (* 3 ; number of tasks
                                          (/ (- end-time start-time) 1000)
                                          ;; 4 events every second + 8 events ever 10 seconds, round down a little
                                          ;; because of test brittleness
                                          4.1)))))))))))

(ns onyx.metrics.send-test
  (:require [clojure.core.async :refer [chan >!! <!! close! sliding-buffer]]
            [clojure.test :refer [deftest is testing]]
            [onyx.plugin.core-async :refer [take-segments!]]
            [onyx.test-helper :refer [with-test-env add-test-env-peers!]]
            [riemann.client]
            [gniazdo.core]
            [taoensso.timbre :refer [info warn fatal]]
            [onyx.lifecycle.metrics.metrics]
            [onyx.lifecycle.metrics.timbre]
            [onyx.lifecycle.metrics.riemann]
            [onyx.lifecycle.metrics.websocket]
            [onyx.api]))

(def n-messages 100000)

(defn my-inc [{:keys [n] :as segment}]
  (assoc segment :n (inc n)))

(deftest metrics-test
  (doseq [sender [:onyx.lifecycle.metrics.websocket/websocket-sender
                  ;; cannot test timbre-sender as info is a macro?
                  ;:onyx.lifecycle.metrics.timbre/timbre-sender
                  :onyx.lifecycle.metrics.riemann/riemann-sender]]

    (def in-chan (chan (inc n-messages)))

    (def out-chan (chan (sliding-buffer (inc n-messages))))

    (defn inject-in-ch [event lifecycle]
      {:core.async/chan in-chan})

    (defn inject-out-ch [event lifecycle]
      {:core.async/chan out-chan})

    (def in-calls
      {:lifecycle/before-task-start inject-in-ch})

    (def out-calls
      {:lifecycle/before-task-start inject-out-ch})

    (let [events (atom [])]
      (with-redefs [riemann.client/tcp-client (fn [opts] nil)
                    riemann.client/send-event (fn [_ event]
                                                (swap! events conj event)
                                                (future :sent))
                    ;taoensso.timbre/info (fn [& vs]
                    ;                       (swap! events conj :print))
                    gniazdo.core/connect (fn [_])
                    gniazdo.core/send-msg (fn [_ v]
                                            (swap! events conj v))]
        (let [id (java.util.UUID/randomUUID)
              env-config {:zookeeper/address "127.0.0.1:2188"
                          :zookeeper/server? true
                          :zookeeper.server/port 2188
                          :onyx/id id}
              peer-config {:zookeeper/address "127.0.0.1:2188"
                           :onyx/id id
                           :onyx.peer/job-scheduler :onyx.job-scheduler/greedy
                           :onyx.messaging/impl :aeron
                           :onyx.messaging/allow-short-circuit? true
                           :onyx.messaging/peer-port 40200
                           :onyx.messaging/bind-addr "localhost"}]
          (with-test-env [test-env [3 env-config peer-config]]
            (let [batch-size 20
                  catalog [{:onyx/name :in
                            :onyx/plugin :onyx.plugin.core-async/input
                            :onyx/type :input
                            :onyx/medium :core.async
                            :onyx/batch-size batch-size
                            :onyx/max-peers 1
                            :onyx/doc "Reads segments from a core.async channel"}

                           {:onyx/name :inc
                            :onyx/fn ::my-inc
                            :onyx/type :function
                            :onyx/batch-size batch-size}

                           {:onyx/name :out
                            :onyx/plugin :onyx.plugin.core-async/output
                            :onyx/type :output
                            :onyx/medium :core.async
                            :onyx/batch-size batch-size
                            :onyx/max-peers 1
                            :onyx/doc "Writes segments to a core.async channel"}]
                  workflow [[:in :inc] [:inc :out]]
                  lifecycles [{:lifecycle/task :in
                               :lifecycle/calls ::in-calls}
                              {:lifecycle/task :in
                               :lifecycle/calls :onyx.plugin.core-async/reader-calls}

                              {:lifecycle/task :all
                               :lifecycle/calls :onyx.lifecycle.metrics.metrics/calls
                               :websocket/address "ws://127.0.0.1:3000/metrics"
                               :metrics/buffer-capacity 10000
                               :metrics/workflow-name "test-workflow"
                               :riemann/address "localhost"
                               :riemann/port 12201
                               :metrics/sender-fn sender
                               :lifecycle/doc "Instruments a task's metrics"}

                              {:lifecycle/task :out
                               :lifecycle/calls ::out-calls}
                              {:lifecycle/task :out
                               :lifecycle/calls :onyx.plugin.core-async/writer-calls}]

                  _ (doseq [n (range n-messages)]
                      (>!! in-chan {:n n}))
                  _ (>!! in-chan :done)
                  _ (close! in-chan)
                  start-time (System/currentTimeMillis)
                  _ (onyx.api/submit-job peer-config
                                         {:catalog catalog
                                          :workflow workflow
                                          :lifecycles lifecycles
                                          :task-scheduler :onyx.task-scheduler/balanced})
                  results (take-segments! out-chan)
                  end-time (System/currentTimeMillis)]
              (let [expected (set (map (fn [x] {:n (inc x)}) (range n-messages)))]
                (is (= expected (set (butlast results))))
                (is (= :done (last results)))
                (is (> (count @events) (* 3 ; number of tasks
                                          (/ (- end-time start-time) 1000)
                                          ;; 4 events every second + 8 events ever 10 seconds, round down a little
                                          ;; because of test brittleness
                                          3.8)))))))))))

Reduce test brittleness

Reduce test brittleness


(ns uxbox.tests.test-users
  (:require [clojure.test :as t]
            [promesa.core :as p]
            [clj-http.client :as http]
            [catacumba.testing :refer (with-server)]
            [buddy.hashers :as hashers]
            [uxbox.persistence :as up]
            [uxbox.frontend.routes :as urt]
            [uxbox.services.users :as usu]
            [uxbox.services :as usv]
            [uxbox.tests.helpers :as th]))

(t/use-fixtures :each th/database-reset)

(t/deftest test-http-retrieve-profile
  (with-open [conn (up/get-conn)]
    (let [user (th/create-user conn 1)]
      ;; Retrieve
      (with-server {:handler (urt/app)}
        (let [uri (str th/+base-url+ "/api/profile/me")
              [status data] (th/http-get user uri)]
          (println "RESPONSE:" status data)
          (t/is (= 200 status))
          (t/is (= (:fullname data) "User 1"))
          (t/is (= (:username data) "user1"))
          (t/is (= (:metadata data) "1"))
          (t/is (= (:email data) "user1@uxbox.io"))
          (t/is (not (contains? data :password))))))))

;; Update
(t/deftest test-http-update-profile
  (with-open [conn (up/get-conn)]
    (let [user (th/create-user conn 1)]
      (with-server {:handler (urt/app)}
        (let [uri (str th/+base-url+ "/api/profile/me")
              data (assoc user
                          :fullname "Full Name"
                          :username "user222"
                          :metadata "222"
                          :email "user222@uxbox.io")
              [status data] (th/http-put user uri {:body data})]
          (println "RESPONSE:" status data)
          (t/is (= 200 status))
          (t/is (= (:fullname data) "Full Name"))
          (t/is (= (:username data) "user222"))
          (t/is (= (:metadata data) "222"))
          (t/is (= (:email data) "user222@uxbox.io"))
          (t/is (not (contains? data :password))))))))

(ns uxbox.tests.test-users
  (:require [clojure.test :as t]
            [clojure.java.io :as io]
            [promesa.core :as p]
            [clj-http.client :as http]
            [catacumba.testing :refer (with-server)]
            [buddy.hashers :as hashers]
            [uxbox.persistence :as up]
            [uxbox.frontend.routes :as urt]
            [uxbox.services.users :as usu]
            [uxbox.services :as usv]
            [uxbox.tests.helpers :as th]))

(t/use-fixtures :each th/database-reset)

(t/deftest test-http-retrieve-profile
  (with-open [conn (up/get-conn)]
    (let [user (th/create-user conn 1)]
      (with-server {:handler (urt/app)}
        (let [uri (str th/+base-url+ "/api/profile/me")
              [status data] (th/http-get user uri)]
          (println "RESPONSE:" status data)
          (t/is (= 200 status))
          (t/is (= (:fullname data) "User 1"))
          (t/is (= (:username data) "user1"))
          (t/is (= (:metadata data) "1"))
          (t/is (= (:email data) "user1@uxbox.io"))
          (t/is (not (contains? data :password))))))))

(t/deftest test-http-update-profile
  (with-open [conn (up/get-conn)]
    (let [user (th/create-user conn 1)]
      (with-server {:handler (urt/app)}
        (let [uri (str th/+base-url+ "/api/profile/me")
              data (assoc user
                          :fullname "Full Name"
                          :username "user222"
                          :metadata "222"
                          :email "user222@uxbox.io")
              [status data] (th/http-put user uri {:body data})]
          (println "RESPONSE:" status data)
          (t/is (= 200 status))
          (t/is (= (:fullname data) "Full Name"))
          (t/is (= (:username data) "user222"))
          (t/is (= (:metadata data) "222"))
          (t/is (= (:email data) "user222@uxbox.io"))
          (t/is (not (contains? data :password))))))))

(t/deftest test-http-update-profile-photo
  (with-open [conn (up/get-conn)]
    (let [user (th/create-user conn 1)]
      (with-server {:handler (urt/app)}
        (let [uri (str th/+base-url+ "/api/profile/me/photo")
              params [{:name "sample.jpg"
                       :part-name "file"
                       :content (io/input-stream
                                 (io/resource "uxbox/tests/_files/sample.jpg"))}]
              [status data] (th/http-multipart user uri params)]
          (println "RESPONSE:" status data)
          (t/is (= 204 status)))))))

Add tests.

Add tests.


(ns frontend.components.language-landing
  (:require [cljs.core.async :as async :refer [>! <! alts! chan sliding-buffer close!]]
            [clojure.string :as str]
            [frontend.async :refer [put!]]
            [frontend.components.common :as common]
            [frontend.components.plans :as plans-component]
            [frontend.components.shared :as shared]
            [frontend.state :as state]
            [frontend.stefon :as stefon]
            [frontend.utils.github :refer [auth-url]]
            [frontend.utils :as utils :include-macros true]
            [om.core :as om :include-macros true])
  (:require-macros [frontend.utils :refer [defrender html]]))

(defn arrow-class [selected-testimonial]
  (case selected-testimonial
    0 "arrowLeft"
    1 "arrowCenter"
    2 "arrowRight"
    "arrowLeft"
    ))

(def templates {"ruby" {:language "Ruby"
                        :headline "CircleCI makes Continous Integration and Deployment for Ruby projects a breeze."
                        :logo-path "/assets/img/outer/languages/ruby-logo.png"
                        :ss-1 "/assets/img/outer/languages/ruby-ss-1.png"
                        :ss-2 "/assets/img/outer/languages/ruby-ss-1.png"
                        :ss-3 "/assets/img/outer/languages/ruby-ss-1.png"
                        :features [{:feature "CircleCI uses RVM to provide support for a wide variety of Ruby versions and gems. It is also trivial to add any packages or frameworks that are not installed on our machines by default, allowing you to effortlessly customize your test enviroment.  CircleCI also supports Test::Unit, RSpec, Cucumber, Spinach, Jasmine, Konacha, and just about any other testing framework you use for your Ruby project."
                                    :title "Built For Ruby"
                                    :icon "/assets/img/outer/languages/gear-icon.png"}
                                   {:feature "Circle manages all your database requirements for your, such as running your rake commands for creating, loading, and migrating your database. We have pre-installed more than a dozen databases and queues, including PostgreSQL, MySQL, and MongoDB. You can also add custom database commands via your circle.yml."
                                    :title "Database Management"
                                    :icon "/assets/img/outer/languages/file-icon.png"}
                                   {:feature "For the majority of Ruby projects no configuration is required; you just run your builds on CircleCI and it works! CircleCI will automatically infer your test commands if you're using Test::Unit, RSpec, Cucumber, Spinach, Jasmine, or Konacha."
                                    :title "Inference That Just Works"
                                    :icon "/assets/img/outer/languages/book-icon.png"}]
                        :docs-link "/docs/language-ruby-on-rails"
                        :testimonials [{:text "Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Integer posuere erat a ante venenatis."
                                        :img "/assets/img/outer/stories/john.jpg"
                                        :author "Kevin Rose"
                                        :title "Entrepneur @Google"}
                                       {:text "Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Integer posuere erat a ante venenatis."
                                        :img "/assets/img/outer/stories/john.jpg"
                                        :author "Clark Kent"
                                        :title "superman"}
                                       {:text "Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Integer posuere erat a ante venenatis."
                                        :img "/assets/img/outer/stories/john.jpg"
                                        :author "Bruce Wayne"
                                        :title "Batman"}]}})

(defn language-landing [app owner]
  (reify
    om/IRender
    (render [_]
            (let [subpage (get-in app [:navigation-data :language])
                  template (get templates subpage)
                  selected-testimonial (get-in app state/language-testimonial-tab-path 0)
                  controls-ch (om/get-shared owner [:comms :controls])]
              (html
                [:div.languages.page
                 [:div.languages-head
                  [:img {:src (:logo-path template)}]
                  [:h1 (:headline template)]
                  [:div.languages-screenshots
                   [:img {:src "/assets/img/outer/languages/build-screenshot.png"}]]]
                 [:div.languages-body.remove-margin
                  [:div.languages-features
                   [:div.center-text
                    [:h3 "FEATURES"
                     ]
                    ]
                   (map-indexed
                     (fn [i feature] [feature (:features template)]
                       (if (odd? i)
                         [:div.feature
                          [:div.feature-image
                           [:img {:src (:icon feature)}]
                           ]
                          [:div.feature-copy
                           [:h4.feature-title (:title feature)
                            ]
                           [:p.feature-description (:feature feature)]
                           ]

                          ]
                         [:div.feature
                          [:div.feature-copy
                           [:h4.feature-title (:title feature)
                            ]
                           [:p.feature-description (:feature feature)]
                           ]
                          [:div.feature-image
                           [:img {:src (:icon feature)}]
                           ]
                          ]))
                     (:features template))

                   [:div.button
                    [:a {:href (:docs-link template)} "Read documentation on " (:language template)]]
                   ]
                  ]
                 [:div.languages-testimonials {:class (arrow-class selected-testimonial)}
                  [:div.languages-body.remove-margin
                   [:div.center-text
                    [:h3 "TESTIMONIALS"
                     ]
                    ]
                   [:div.testimonial-authors
                    (map-indexed (fn [i testimonial] [:img {:src (:img testimonial) :on-click #(put! controls-ch [:language-testimonial-tab-selected {:index i}])}])
                                 (:testimonials template))]
                   [:div.testimonial-box
                    [:div.testimonial
                     [:p.testimonial-text (get-in template [:testimonials selected-testimonial :text])]

                     [:div.testimonial-author "—" (get-in template [:testimonials selected-testimonial :author])]
                     [:div.testimonial-author-title (get-in template [:testimonials selected-testimonial :title])]]
                    ]
                   ]
                  ]

                 [:div.languages-cta
                  [:div.languages-body
                   [:h3
                    "How do I start using my " (:language template) " app with CircleCI?"]
                   [:div.languages-cta-steps
                    [:div.languages-cta-step
                     [:div.step-number "1"]
                     [:div
                      "Start by signing up "
                      [:br]
                      "using GitHub"]
                     ]
                    [:div.languages-cta-step
                     [:div.step-number "2"]
                     [:div
                      "Run one of your Ruby projects on Circle"]]
                    [:div.languages-cta-step
                     [:div.step-number "3"]
                     [:div
                      [:strong "That's it!"]
                      " Contact support if you run in to any issues. "]
                     ]]
                   [:div.cta-divider]
                   [:div.center-text
                    [:a.languages-cta-button
                     {:href (auth-url)
                      :on-click #(put! controls-ch [:track-external-link-clicked {:event "Auth GitHub"
                                                                                :properties {:source (:language template)}
                                                                                :path (auth-url)}])}
                     "Sign Up With GitHub"]
                    [:div.language-cta-trial "14-day free trial"]
                    ]
                   ]]

                 ])))))

(ns frontend.components.language-landing
  (:require [cljs.core.async :as async :refer [>! <! alts! chan sliding-buffer close!]]
            [clojure.string :as str]
            [frontend.async :refer [put!]]
            [frontend.components.common :as common]
            [frontend.components.plans :as plans-component]
            [frontend.components.shared :as shared]
            [frontend.state :as state]
            [frontend.stefon :as stefon]
            [frontend.utils.github :refer [auth-url]]
            [frontend.utils :as utils :include-macros true]
            [om.core :as om :include-macros true])
  (:require-macros [frontend.utils :refer [defrender html]]))

(defn arrow-class [selected-testimonial]
  (case selected-testimonial
    0 "arrowLeft"
    1 "arrowCenter"
    2 "arrowRight"
    "arrowLeft"
    ))

(def templates {"ruby" {:language "Ruby"
                        :headline "CircleCI makes Continous Integration and Deployment for Ruby projects a breeze."
                        :logo-path "/assets/img/outer/languages/ruby-logo.png"
                        :ss-1 "/assets/img/outer/languages/ruby-ss-1.png"
                        :ss-2 "/assets/img/outer/languages/ruby-ss-1.png"
                        :ss-3 "/assets/img/outer/languages/ruby-ss-1.png"
                        :features [{:feature "CircleCI uses RVM to provide support for a wide variety of Ruby versions and gems. It is also trivial to add any packages or frameworks that are not installed on our machines by default, allowing you to effortlessly customize your test enviroment.  CircleCI also supports Test::Unit, RSpec, Cucumber, Spinach, Jasmine, Konacha, and just about any other testing framework you use for your Ruby project."
                                    :title "Built For Ruby"
                                    :icon "/assets/img/outer/languages/gear-icon.png"}
                                   {:feature "Circle manages all your database requirements for your, such as running your rake commands for creating, loading, and migrating your database. We have pre-installed more than a dozen databases and queues, including PostgreSQL, MySQL, and MongoDB. You can also add custom database commands via your circle.yml."
                                    :title "Database Management"
                                    :icon "/assets/img/outer/languages/file-icon.png"}
                                   {:feature "For the majority of Ruby projects no configuration is required; you just run your builds on CircleCI and it works! CircleCI will automatically infer your test commands if you're using Test::Unit, RSpec, Cucumber, Spinach, Jasmine, or Konacha."
                                    :title "Inference That Just Works"
                                    :icon "/assets/img/outer/languages/book-icon.png"}]
                        :docs-link "/docs/language-ruby-on-rails"
                        :testimonials [{:text "Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Integer posuere erat a ante venenatis."
                                        :img "/assets/img/outer/stories/john.jpg"
                                        :author "Kevin Rose"
                                        :title "Entrepneur @Google"}
                                       {:text "Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Integer posuere erat a ante venenatis."
                                        :img "/assets/img/outer/stories/john.jpg"
                                        :author "Clark Kent"
                                        :title "superman"}
                                       {:text "Nullam id dolor id nibh ultricies vehicula ut id elit. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Integer posuere erat a ante venenatis."
                                        :img "/assets/img/outer/stories/john.jpg"
                                        :author "Bruce Wayne"
                                        :title "Batman"}]}})

(defn language-landing [app owner]
  (reify
    om/IRender
    (render [_]
            (let [subpage (get-in app [:navigation-data :language])
                  template (get templates subpage)
                  selected-testimonial (get-in app state/language-testimonial-tab-path 0)
                  controls-ch (om/get-shared owner [:comms :controls])]
              (html
                [:div.languages.page
                 [:div.languages-head
                  [:img {:src (:logo-path template)}]
                  [:h1 (:headline template)]
                  [:div.languages-screenshots]]
                 [:div.languages-body.remove-margin
                  [:div.languages-features
                   [:div.center-text
                    [:h3 "FEATURES"
                     ]
                    ]
                   (map-indexed
                     (fn [i feature] [feature (:features template)]
                       (if (odd? i)
                         [:div.feature
                          [:div.feature-image
                           [:img {:src (:icon feature)}]
                           ]
                          [:div.feature-copy
                           [:h4.feature-title (:title feature)
                            ]
                           [:p.feature-description (:feature feature)]
                           ]

                          ]
                         [:div.feature
                          [:div.feature-copy
                           [:h4.feature-title (:title feature)
                            ]
                           [:p.feature-description (:feature feature)]
                           ]
                          [:div.feature-image
                           [:img {:src (:icon feature)}]
                           ]
                          ]))
                     (:features template))

                   [:div.button
                    [:a {:href (:docs-link template)} "Read documentation on " (:language template)]]
                   ]
                  ]
                 [:div.languages-testimonials {:class (arrow-class selected-testimonial)}
                  [:div.languages-body.remove-margin
                   [:div.center-text
                    [:h3 "TESTIMONIALS"
                     ]
                    ]
                   [:div.testimonial-authors
                    (map-indexed (fn [i testimonial] [:img {:src (:img testimonial) :on-click #(put! controls-ch [:language-testimonial-tab-selected {:index i}])}])
                                 (:testimonials template))]
                   [:div.testimonial-box
                    [:div.testimonial
                     [:p.testimonial-text (get-in template [:testimonials selected-testimonial :text])]

                     [:div.testimonial-author "—" (get-in template [:testimonials selected-testimonial :author])]
                     [:div.testimonial-author-title (get-in template [:testimonials selected-testimonial :title])]]
                    ]
                   ]
                  ]

                 [:div.languages-cta
                  [:div.languages-body
                   [:h3
                    "How do I start using my " (:language template) " app with CircleCI?"]
                   [:div.languages-cta-steps
                    [:div.languages-cta-step
                     [:div.step-number "1"]
                     [:div
                      "Start by signing up "
                      [:br]
                      "using GitHub"]
                     ]
                    [:div.languages-cta-step
                     [:div.step-number "2"]
                     [:div
                      "Run one of your Ruby projects on Circle"]]
                    [:div.languages-cta-step
                     [:div.step-number "3"]
                     [:div
                      [:strong "That's it!"]
                      " Contact support if you run in to any issues. "]
                     ]]
                   [:div.cta-divider]
                   [:div.center-text
                    [:a.languages-cta-button
                     {:href (auth-url)
                      :on-click #(put! controls-ch [:track-external-link-clicked {:event "Auth GitHub"
                                                                                :properties {:source (:language template)}
                                                                                :path (auth-url)}])}
                     "Sign Up With GitHub"]
                    [:div.language-cta-trial "14-day free trial"]
                    ]
                   ]]

                 ])))))

make background image and fix styles

make background image and fix styles


(ns pc.views.blog
  (:require [hiccup.core :refer [html]]
            [clj-time.core :as time]
            [clj-time.format]
            [pc.profile :as profile]
            [pc.views.content]
            [pc.util.http :as http-util]))

(defonce requires (atom #{}))

(defn post-ns [slug]
  (symbol (str "pc.views.blog." slug)))

;; This is probably a bad idea, but it seems to work pretty well.
(defn maybe-require [slug]
  (let [ns (post-ns slug)]
    (when-not (contains? @requires ns)
      (require ns)
      (swap! requires conj ns))))

(defn post-fn [slug]
  (maybe-require slug)
  (ns-resolve (post-ns slug)
              (symbol slug)))

(defn post-url [slug]
  (str "/blog/" slug))

(defn display-in-overview? [{:keys [display-in-overview scheduled-time] :as slug-map}]
  (and display-in-overview
       (or (not scheduled-time)
           (time/after? (time/now) scheduled-time))))

(def slugs
  "Sorted array of slugs, assumes the post content can be found in the
   function returned by post-fn
   Add a :scheduled-time key to prevent the post from showing up in the
   overview until after the scheduled time. Posts will still be accessible
   to anyone with the direct URL. Time should be in the format Fri, 30 Jan 2015 01:12:00 -0800
   Be careful for daylight savings time!"
  [
   {:slug "clojure-is-a-product-design-tool"
    :display-in-overview true
    ;; 9am dst
    :scheduled-time (clj-time.format/parse "Thu, 12 Mar 2015 08:00:00 -0800")}
   {:slug "optimizing-om-apps"
    :display-in-overview true}
   {:slug "blue-ocean-made-of-ink"
    :display-in-overview true}
   {:slug "private-docs-early-access"
    :display-in-overview false}
   {:slug "product-hunt-wake-up-call"
    :display-in-overview true}
   {:slug "interactive-layers"
    :display-in-overview true}
   ;; scheduled time example
   ;; {:slug "scheduled-time-example"
   ;;  :display-in-overview true
   ;;  :scheduled-time (clj-time.format/parse "Fri, 30 Jan 2015 01:12:00 -0800")}
   ])

(defn post-exists? [slug]
  (not= -1 (.indexOf (map :slug slugs) slug)))

(def logomark
  [:i {:class "icon-logomark"}
   [:svg {:class "iconpile" :viewBox "0 0 100 100"}
    [:path {:class "fill-logomark" :d "M43,100H29.5V39H43V100z M94,33.8C90.9,22,83.3,12.2,72.8,6.1C62.2,0,50-1.6,38.2,1.6 C26.5,4.7,16.6,12.3,10.6,22.8C4.5,33.3,2.9,45.6,6,57.4l1.7,6.4l12.7-3.4l-1.7-6.4c-4.6-17.2,5.6-35,22.9-39.6 c8.3-2.2,17.1-1.1,24.6,3.2c7.5,4.3,12.8,11.3,15.1,19.7c4.6,17.2-5.6,35-22.9,39.6L52,78.5l3.4,12.7l6.4-1.7 C86.1,83.1,100.5,58,94,33.8z"}]]])

(def twitter
  [:i {:class "icon-twitter"}
   [:svg {:class "iconpile" :viewBox "0 0 100 100"}
    [:path {:class "fill-twitter" :d "M100,19c-3.7,1.6-7.6,2.7-11.8,3.2c4.2-2.5,7.5-6.6,9-11.4c-4,2.4-8.4,4.1-13,5c-3.7-4-9.1-6.5-15-6.5 c-11.3,0-20.5,9.2-20.5,20.5c0,1.6,0.2,3.2,0.5,4.7c-17.1-0.9-32.2-9-42.3-21.4c-1.8,3-2.8,6.6-2.8,10.3c0,7.1,3.6,13.4,9.1,17.1 c-3.4-0.1-6.5-1-9.3-2.6c0,0.1,0,0.2,0,0.3c0,9.9,7.1,18.2,16.5,20.1c-1.7,0.5-3.5,0.7-5.4,0.7c-1.3,0-2.6-0.1-3.9-0.4 c2.6,8.2,10.2,14.1,19.2,14.2c-7,5.5-15.9,8.8-25.5,8.8c-1.7,0-3.3-0.1-4.9-0.3c9.1,5.8,19.9,9.2,31.4,9.2 c37.7,0,58.4-31.3,58.4-58.4c0-0.9,0-1.8-0.1-2.7C93.8,26.7,97.2,23.1,100,19z"}]]])

(def authors
  [{:name "Danny"
    :url "https://twitter.com/dannykingme"}
   {:name "Daniel"
    :url "https://twitter.com/DanielWoelfel"}])

(defn author-link [author-name]
  (if-let [author (first (filter #(= author-name (:name %)) authors))]
    [:a.blogroll-post-author {:href (:url author)}
     (:name author)]
    author-name))

(defn blog-head []
  [:article
   [:div.blog-head
    [:a.blog-head-logo {:href "/blog"
                        :title "Precursor Blog"}
     logomark]]])

(defn overview []
  [:div.blogroll
   (blog-head)
   [:article
    (for [slug (->> slugs (filter display-in-overview?) (map :slug))
          :let [{:keys [title blurb author] :as content} ((post-fn slug))]]
      [:div.blogroll-post
       [:a.blogroll-post-title {:href (post-url slug)}
        [:h3 title]]
       [:p blurb]
       [:p (author-link author)]])]])

(defn single-post [slug]
  (let [post ((post-fn slug))]
    [:div.blogpost
     (blog-head)
     [:div.blogpost-title
      [:article
       [:h2 (:title post)]]]
     (:body post)]))

(defn render-page [slug]
  (html (pc.views.content/layout
         {}
         [:div.page-blog
          [:div.nav.nav-head ; keep up to date with outer/nav-head
           [:a.nav-link.nav-logo    {:href "/"        :title "Precursor"} "Precursor"]
           [:a.nav-link.nav-home    {:href "/home"    :title "Home"}      "Home"]
           [:a.nav-link.nav-pricing {:href "/pricing" :title "Pricing"}   "Pricing"]
           [:a.nav-link.nav-blog    {:href "/blog"    :title "Blog"}      "Blog"]
           [:a.nav-link.nav-app     {:href "/new"     :title "Launch"}    "App"]]
          (if (post-exists? slug)
            (single-post slug)
            (overview))
          [:div.nav.nav-foot ; keep up to date with outer/nav-foot
           [:a.nav-link.nav-logo    {:href "/"        :title "Precursor"} logomark]
           [:a.nav-link.nav-home    {:href "/home"    :title "Home"}      "Home"]
           [:a.nav-link.nav-pricing {:href "/pricing" :title "Pricing"}   "Pricing"]
           [:a.nav-link.nav-blog    {:href "/blog"    :title "Blog"}      "Blog"]
           [:a.nav-link.nav-app     {:href "/new"     :title "Launch"}    "App"]
           [:a.nav-link.nav-twitter {:href "https://twitter.com/PrecursorApp" :title "@PrecursorApp"} twitter]]])))

(ns pc.views.blog
  (:require [hiccup.core :refer [html]]
            [clj-time.core :as time]
            [clj-time.format]
            [pc.profile :as profile]
            [pc.views.content]
            [pc.util.http :as http-util]))

(defonce requires (atom #{}))

(defn post-ns [slug]
  (symbol (str "pc.views.blog." slug)))

;; This is probably a bad idea, but it seems to work pretty well.
(defn maybe-require [slug]
  (let [ns (post-ns slug)]
    (when-not (contains? @requires ns)
      (require ns)
      (swap! requires conj ns))))

(defn post-fn [slug]
  (maybe-require slug)
  (ns-resolve (post-ns slug)
              (symbol slug)))

(defn post-url [slug]
  (str "/blog/" slug))

(defn display-in-overview? [{:keys [display-in-overview scheduled-time] :as slug-map}]
  (and display-in-overview
       (or (not scheduled-time)
           (time/after? (time/now) scheduled-time))))

(def slugs
  "Sorted array of slugs, assumes the post content can be found in the
   function returned by post-fn
   Add a :scheduled-time key to prevent the post from showing up in the
   overview until after the scheduled time. Posts will still be accessible
   to anyone with the direct URL. Time should be in the format Fri, 30 Jan 2015 01:12:00 -0800
   Be careful for daylight savings time!"
  [
   {:slug "clojure-is-a-product-design-tool"
    :display-in-overview true
    ;; 9am dst
    :scheduled-time (clj-time.format/parse "Thu, 12 Mar 2015 08:00:00 -0800")}
   {:slug "optimizing-om-apps"
    :display-in-overview true}
   {:slug "blue-ocean-made-of-ink"
    :display-in-overview true}
   {:slug "private-docs-early-access"
    :display-in-overview false}
   {:slug "product-hunt-wake-up-call"
    :display-in-overview true}
   {:slug "interactive-layers"
    :display-in-overview true}
   ;; scheduled time example
   ;; {:slug "scheduled-time-example"
   ;;  :display-in-overview true
   ;;  :scheduled-time (clj-time.format/parse "Fri, 30 Jan 2015 01:12:00 -0800")}
   ])

(defn post-exists? [slug]
  (not= -1 (.indexOf (map :slug slugs) slug)))

(def logomark
  [:i {:class "icon-logomark"}
   [:svg {:class "iconpile" :viewBox "0 0 100 100"}
    [:path {:class "fill-logomark" :d "M43,100H29.5V39H43V100z M94,33.8C90.9,22,83.3,12.2,72.8,6.1C62.2,0,50-1.6,38.2,1.6 C26.5,4.7,16.6,12.3,10.6,22.8C4.5,33.3,2.9,45.6,6,57.4l1.7,6.4l12.7-3.4l-1.7-6.4c-4.6-17.2,5.6-35,22.9-39.6 c8.3-2.2,17.1-1.1,24.6,3.2c7.5,4.3,12.8,11.3,15.1,19.7c4.6,17.2-5.6,35-22.9,39.6L52,78.5l3.4,12.7l6.4-1.7 C86.1,83.1,100.5,58,94,33.8z"}]]])

(def twitter
  [:i {:class "icon-twitter"}
   [:svg {:class "iconpile" :viewBox "0 0 100 100"}
    [:path {:class "fill-twitter" :d "M100,19c-3.7,1.6-7.6,2.7-11.8,3.2c4.2-2.5,7.5-6.6,9-11.4c-4,2.4-8.4,4.1-13,5c-3.7-4-9.1-6.5-15-6.5 c-11.3,0-20.5,9.2-20.5,20.5c0,1.6,0.2,3.2,0.5,4.7c-17.1-0.9-32.2-9-42.3-21.4c-1.8,3-2.8,6.6-2.8,10.3c0,7.1,3.6,13.4,9.1,17.1 c-3.4-0.1-6.5-1-9.3-2.6c0,0.1,0,0.2,0,0.3c0,9.9,7.1,18.2,16.5,20.1c-1.7,0.5-3.5,0.7-5.4,0.7c-1.3,0-2.6-0.1-3.9-0.4 c2.6,8.2,10.2,14.1,19.2,14.2c-7,5.5-15.9,8.8-25.5,8.8c-1.7,0-3.3-0.1-4.9-0.3c9.1,5.8,19.9,9.2,31.4,9.2 c37.7,0,58.4-31.3,58.4-58.4c0-0.9,0-1.8-0.1-2.7C93.8,26.7,97.2,23.1,100,19z"}]]])

(def authors
  [{:name "Danny"
    :url "https://twitter.com/dannykingme"}
   {:name "Daniel"
    :url "https://twitter.com/DanielWoelfel"}])

(defn author-link [author-name]
  (if-let [author (first (filter #(= author-name (:name %)) authors))]
    [:a.blogroll-post-author {:href (:url author)}
     (:name author)]
    author-name))

(defn blog-head []
  [:article
   [:div.blog-head
    [:a.blog-head-logo {:href "/blog"
                        :title "Precursor Blog"}
     logomark]]])

(defn overview []
  [:div.blogroll
   (blog-head)
   [:article
    (for [slug (->> slugs (filter display-in-overview?) (map :slug))
          :let [{:keys [title blurb author] :as content} ((post-fn slug))]]
      [:div.blogroll-post
       [:a.blogroll-post-title {:href (post-url slug)}
        [:h3 title]]
       [:p blurb]
       [:p (author-link author)]])]])

(defn single-post [post]
  [:div.blogpost
   (blog-head)
   [:div.blogpost-title
    [:article
     [:h2 (:title post)]]]
   (:body post)])

(defn render-page [slug]
  (let [post (when (post-exists? slug)
               ((post-fn slug)))]
    (html (pc.views.content/layout
           {:meta-title (:title post)
            :meta-description (:blurb post)
            :meta-image (:image post)}
           [:div.page-blog
            [:div.nav.nav-head ; keep up to date with outer/nav-head
             [:a.nav-link.nav-logo    {:href "/"        :title "Precursor"} "Precursor"]
             [:a.nav-link.nav-home    {:href "/home"    :title "Home"}      "Home"]
             [:a.nav-link.nav-pricing {:href "/pricing" :title "Pricing"}   "Pricing"]
             [:a.nav-link.nav-blog    {:href "/blog"    :title "Blog"}      "Blog"]
             [:a.nav-link.nav-app     {:href "/new"     :title "Launch"}    "App"]]
            (if post
              (single-post post)
              (overview))
            [:div.nav.nav-foot ; keep up to date with outer/nav-foot
             [:a.nav-link.nav-logo    {:href "/"        :title "Precursor"} logomark]
             [:a.nav-link.nav-home    {:href "/home"    :title "Home"}      "Home"]
             [:a.nav-link.nav-pricing {:href "/pricing" :title "Pricing"}   "Pricing"]
             [:a.nav-link.nav-blog    {:href "/blog"    :title "Blog"}      "Blog"]
             [:a.nav-link.nav-app     {:href "/new"     :title "Launch"}    "App"]
             [:a.nav-link.nav-twitter {:href "https://twitter.com/PrecursorApp" :title "@PrecursorApp"} twitter]]]))))

use title, desc, and image from the post for the html graph data


(ns figwheel-sidecar.build-middleware.clj-reloading
  (:require
   [figwheel-sidecar.utils :as utils]
   [cljs.build.api :as bapi]
   [cljs.env :as env]
   [clojure.java.io :as io]))

;; live relading clj macros is a tough problem and we should think
;; about either backing off or doing it more correctly say with
;; tools.namespace

;; TODO refactor

;; TODO we should use tools.analyzer
(defn get-clj-ns [x]
  (-> x :source-file utils/get-ns-from-source-file-path))

(defn get-clj-namespaces [file-resources]
  (map get-clj-ns file-resources))

;; this gets cljs dependant ns for macro files
(defn macro-dependants [macro-file-resources]
  (let [namespaces (get-clj-namespaces macro-file-resources)]
    (bapi/cljs-dependents-for-macro-namespaces namespaces)))

(defn mark-known-dependants-for-recompile! [opts file-resources]
  (let [ns-syms (macro-dependants file-resources)]
    (doseq [ns-sym ns-syms]
      (bapi/mark-cljs-ns-for-recompile! ns-sym (:output-dir opts)))
    ns-syms))

(defn macro-file?
  [f] (.contains (slurp (:source-file f)) "(defmacro"))

(defn annotate-macro-file [f]
  (assoc f :macro-file? (macro-file? f)))

(defn get-files-to-reload [opts changed-clj-files]
  ;; :reload-non-macro-clj-files defaults to true
  (if ((fnil identity true) (:reload-non-macro-clj-files opts))
    changed-clj-files
    (filter :macro-file? changed-clj-files)))

(defn relevant-macro-files [clj-files-fn changed-clj-files]
  (let [non-macro-clj? (first (filter #(not (:macro-file? %)) changed-clj-files))]
    (filter :macro-file?
            (if non-macro-clj? (clj-files-fn)
                changed-clj-files))))

(defn clj-files-in-dirs [dirs]
  (let [all-files (mapcat file-seq (filter #(and (.exists %)
                                                 (.isDirectory %))
                                           (map io/file dirs)))]
    (map (fn [f] {:source-file f })
         (filter #(let [name (.getName ^java.io.File %)]
                    (and (or (.endsWith name ".clj")
                             (.endsWith name ".cljc"))
                         (not= \# (first name))
                         (not= \. (first name))))
                 all-files))))

(defn handle-clj-source-reloading [{:keys [source-paths build-options compiler-env] :as build-config} changed-clj-files]
  (let [build-options (or build-options (:compiler build-config))
        changed-clj-files (keep
                           (fn [f]
                             (when f
                               (let [f (io/file f)]
                                 (annotate-macro-file
                                  {:source-file f}))))
                           changed-clj-files)
        files-to-reload (get-files-to-reload build-options changed-clj-files)]
    (when (not-empty changed-clj-files)
      (doseq [clj-file files-to-reload]
        ;; this could be a problem if the file isn't in the require
        ;; chain
        ;; it will be loaded anyway
        (load-file (.getCanonicalPath (:source-file clj-file))))
      (let [rel-files (relevant-macro-files
                       (fn [] (map annotate-macro-file (clj-files-in-dirs source-paths)))
                       changed-clj-files)]
        (env/with-compiler-env compiler-env
          (mark-known-dependants-for-recompile! build-options rel-files))))))

(defn default-config [{:keys [reload-clj-files] :as figwheel-server}]
  (cond
    (false? reload-clj-files) false
    (or (true? reload-clj-files)
          (not (map? reload-clj-files)))
    {:cljc true :clj true}
    :else reload-clj-files))

(defn suffix-conditional [config]
  #(reduce-kv
    (fn [accum k v]
      (or accum
          (and v
               (.endsWith % (str "." (name k))))))
    false
    config))

(defn hook [build-fn]
  (fn [{:keys [figwheel-server build-config changed-files] :as build-state}]
    (let [reload-config (default-config figwheel-server)]
      (if-let [changed-clj-files (and
                                  reload-config
                                  (not-empty
                                   (filter (suffix-conditional reload-config)
                                           changed-files)))]
        (let [additional-changed-ns' (handle-clj-source-reloading build-config changed-clj-files)]
          (build-fn (update-in
                     build-state
                     [:additional-changed-ns]
                     concat
                     additional-changed-ns')))
        (build-fn build-state)))))

(ns figwheel-sidecar.build-middleware.clj-reloading
  (:require
   [figwheel-sidecar.utils :as utils]
   [cljs.build.api :as bapi]
   [cljs.env :as env]
   [clojure.java.io :as io]))

;; live relading clj macros is a tough problem and we should think
;; about either backing off or doing it more correctly say with
;; tools.namespace

;; TODO refactor

;; TODO we should use tools.analyzer
(defn get-clj-ns [x]
  (-> x :source-file utils/get-ns-from-source-file-path))

(defn get-clj-namespaces [file-resources]
  (map get-clj-ns file-resources))

;; this gets cljs dependant ns for macro files
(defn macro-dependants [macro-file-resources]
  (let [namespaces (get-clj-namespaces macro-file-resources)]
    (bapi/cljs-dependents-for-macro-namespaces namespaces)))

(defn mark-known-dependants-for-recompile! [opts file-resources]
  (let [ns-syms (macro-dependants file-resources)]
    (doseq [ns-sym ns-syms]
      (bapi/mark-cljs-ns-for-recompile! ns-sym (:output-dir opts)))
    ns-syms))

(defn macro-file?
  [f] (.contains (slurp (:source-file f)) "(defmacro"))

(defn annotate-macro-file [f]
  (assoc f :macro-file? (macro-file? f)))

(defn get-files-to-reload [opts changed-clj-files]
  ;; :reload-non-macro-clj-files defaults to true
  (if ((fnil identity true) (:reload-non-macro-clj-files opts))
    changed-clj-files
    (filter :macro-file? changed-clj-files)))

(defn clj-files-in-dirs [dirs]
  (let [all-files (mapcat file-seq (filter #(and (.exists %)
                                                 (.isDirectory %))
                                           (map io/file dirs)))]
    (map (fn [f] {:source-file f })
         (filter #(let [name (.getName ^java.io.File %)]
                    (and (or (.endsWith name ".clj")
                             (.endsWith name ".cljc"))
                         (not= \# (first name))
                         (not= \. (first name))))
                 all-files))))

(defn handle-clj-source-reloading [{:keys [source-paths build-options compiler-env] :as build-config} changed-clj-files]
  (let [build-options (or build-options (:compiler build-config))
        changed-clj-files (keep
                           (fn [f]
                             (when f
                               (let [f (io/file f)]
                                 (annotate-macro-file
                                  {:source-file f}))))
                           changed-clj-files)
        files-to-reload (get-files-to-reload build-options changed-clj-files)]
    (when (not-empty changed-clj-files)
      (doseq [clj-file files-to-reload]
        ;; this could be a problem if the file isn't in the require
        ;; chain
        ;; it will be loaded anyway
        ;; TODO send load time error notifications to server
        (load-file (.getCanonicalPath (:source-file clj-file))))
      (let [rel-files (filter :macro-file? changed-clj-files)]
        (env/with-compiler-env compiler-env
          (mark-known-dependants-for-recompile! build-options rel-files))))))

(defn default-config [{:keys [reload-clj-files] :as figwheel-server}]
  (cond
    (false? reload-clj-files) false
    (or (true? reload-clj-files)
          (not (map? reload-clj-files)))
    {:cljc true :clj true}
    :else reload-clj-files))

(defn suffix-conditional [config]
  #(reduce-kv
    (fn [accum k v]
      (or accum
          (and v
               (.endsWith % (str "." (name k))))))
    false
    config))

(defn hook [build-fn]
  (fn [{:keys [figwheel-server build-config changed-files] :as build-state}]
    (let [reload-config (default-config figwheel-server)]
      (if-let [changed-clj-files (and
                                  reload-config
                                  (not-empty
                                   (filter (suffix-conditional reload-config)
                                           changed-files)))]
        (let [additional-changed-ns' (handle-clj-source-reloading build-config changed-clj-files)]
          (build-fn (update-in
                     build-state
                     [:additional-changed-ns]
                     concat
                     additional-changed-ns')))
        (build-fn build-state)))))

tweak clj file reloading

tweak clj file reloading

fixes #423


(ns org.openforis.ceo.db.imagery
  (:require [clojure.data.json :as json]
            [org.openforis.ceo.database :refer [call-sql sql-primitive]]
            [org.openforis.ceo.db.institutions :refer [is-inst-admin-query]] ; FIXME this function has not yet been converted in institutions
            [org.openforis.ceo.utils.type-conversion :as tc]
            [org.openforis.ceo.views :refer [data-response]]))

(defn- clean-source [sourceConfig]
  (if (#{"GeoServer" "SecureWatch" "Planet"})
    (select-keys sourceConfig [:type :startDate :endDate :month :year])
    sourceConfig))

(defn- map-imagery [imagery admin?]
  (mapv (fn [{:keys [imagery_id institution_id visibility title attribution extent source_config]}]
          {:id           imagery_id ; FIXME, legacy variable name, update to imageryId
           :institution  institution_id ; FIXME, legacy variable name, update to institutionId
           :visibility   visibility
           :title        title
           :attribution  attribution
           :extent       extent
           :sourceConfig (if admin? source_config (clean-source source_config))})
        imagery))

(defn get-institution-imagery [{:keys [params]}]
  (let [institution-id (tc/str->int (:institutionId params))
        user-id        (tc/str->int (:userId params))]
    (data-response (map-imagery (call-sql "select_imagery_by_institution" institution-id user-id)
                                (is-inst-admin-query user-id institution-id)))))

(defn get-project-imagery [{:keys [params]}]
  (let [project-id (tc/str->int (:projectId params))
        user-id    (tc/str->int (:userId params))
        token-key  (:token-key params)] ; FIXME, what case are we using for the session?
    (data-response (map-imagery (call-sql "select_imagery_by_project" project-id user-id token-key)
                                false))))

(defn get-public-imagery [_]
  (data-response (map-imagery (call-sql "select_public_imagery")
                              false)))

; TODO investigate to what degree we need to be converting to and from JSON
(defn get-imagery-source-config [imagery-id]
  (data-response
   (sql-primitive (call-sql (str "SELECT source_config FROM imagery WHERE imagery_uid = "
                                 imagery-id)))))

(defn add-institution-imagery [{:keys [params]}]
  (let [institution-id       (tc/str->int (:institutionId params))
        imagery-title        (:imageryTitle params)
        imagery-attribution  (:imageryAttribution params)
        source-config        (json/read-str (:sourceConfig params))
        add-to-all-projects? (tc/str-bool (:addToAllProjects params) true)]
    (if (sql-primitive (call-sql "imagery_name_taken" institution-id imagery-title -1))
      (data-response "The title you have chosen is already taken.")
      (let [new-imagery-id (sql-primitive (call-sql "add_institution_imagery"
                                                    institution-id
                                                    "private"
                                                    imagery-title
                                                    imagery-attribution
                                                    nil
                                                    source-config))]
        (when add-to-all-projects?
          (call-sql "add_imagery_to_all_institution_projects" new-imagery-id))
        (data-response "")))))

;; TODO this should not be needed. Just build the source config on the front end and reuse add-institution-imagery
(defn add-geodash-imagery [{:keys [params]}]
  (let [institution-id      (tc/str->int (:institutionId params))
        imagery-title       (:imageryTitle params)
        imagery-attribution (:imageryAttribution params)
        gee-url             (:geeUrl params)
        gee-params          (json/read-str (:geeParam params))
        source-config       {:type      "GeeGateway"
                             :geeUrl    gee-url
                             :geeParams gee-params}]
    (if (sql-primitive (call-sql "imagery_name_taken" institution-id imagery-title -1))
      (data-response "The title you have chosen is already taken.")
      (do
        (call-sql "add_institution_imagery"
                  institution-id
                  "private"
                  imagery-title
                  imagery-attribution
                  nil
                  source-config)
        (data-response "")))))

(defn update-institution-imagery [{:keys [params]}]
  (let [imagery-id           (tc/str->int (:imageryId params))
        imagery-title        (:imageryTitle params)
        imagery-attribution  (:imageryAttribution params)
        source-config        (json/read-str (:sourceConfig params))
        add-to-all-projects? (tc/str-bool (:addToAllProjects params) true)
        institution-id       (sql-primitive (call-sql (str "SELECT instituion_rid FROM imagery WHERE imagery_uid = "
                                                           imagery-id)))]
    (if (call-sql "imagery_name_taken" institution-id imagery-title imagery-id)
      (data-response "The title you have chosen is already taken.")
      (do
        (call-sql "update_institution_imagery"
                  imagery-id
                  imagery-title
                  imagery-attribution
                  source-config)
        (when add-to-all-projects?
          (call-sql "add_imagery_to_all_institution_projects" imagery-id))
        (data-response "")))))

(defn archive-institution-imagery [{:keys [params]}]
  (call-sql "archive_imagery" (tc/str->int (:imageryId params)))
  (data-response ""))

(ns org.openforis.ceo.db.imagery
  (:require [clojure.data.json :as json]
            [org.openforis.ceo.database :refer [call-sql sql-primitive]]
            [org.openforis.ceo.db.institutions :refer [is-inst-admin-query]] ; FIXME this function has not yet been converted in institutions
            [org.openforis.ceo.utils.type-conversion :as tc]
            [org.openforis.ceo.views :refer [data-response]]))

(defn- clean-source [sourceConfig]
  (if (#{"GeoServer" "SecureWatch" "Planet"})
    (dissoc sourceConfig [:geoserverParams :accessToken])
    sourceConfig))

(defn- map-imagery [imagery admin?]
  (mapv (fn [{:keys [imagery_id institution_id visibility title attribution extent source_config]}]
          {:id           imagery_id ; FIXME, legacy variable name, update to imageryId
           :institution  institution_id ; FIXME, legacy variable name, update to institutionId
           :visibility   visibility
           :title        title
           :attribution  attribution
           :extent       extent
           :sourceConfig (if admin? source_config (clean-source source_config))})
        imagery))

(defn get-institution-imagery [{:keys [params]}]
  (let [institution-id (tc/str->int (:institutionId params))
        user-id        (tc/str->int (:userId params))]
    (data-response (map-imagery (call-sql "select_imagery_by_institution" institution-id user-id)
                                (is-inst-admin-query user-id institution-id)))))

(defn get-project-imagery [{:keys [params]}]
  (let [project-id (tc/str->int (:projectId params))
        user-id    (tc/str->int (:userId params))
        token-key  (:token-key params)] ; FIXME, what case are we using for the session?
    (data-response (map-imagery (call-sql "select_imagery_by_project" project-id user-id token-key)
                                false))))

(defn get-public-imagery [_]
  (data-response (map-imagery (call-sql "select_public_imagery")
                              false)))

; TODO investigate to what degree we need to be converting to and from JSON
(defn get-imagery-source-config [imagery-id]
  (data-response
   (sql-primitive (call-sql (str "SELECT source_config FROM imagery WHERE imagery_uid = "
                                 imagery-id)))))

(defn add-institution-imagery [{:keys [params]}]
  (let [institution-id       (tc/str->int (:institutionId params))
        imagery-title        (:imageryTitle params)
        imagery-attribution  (:imageryAttribution params)
        source-config        (json/read-str (:sourceConfig params))
        add-to-all-projects? (tc/str-bool (:addToAllProjects params) true)]
    (if (sql-primitive (call-sql "imagery_name_taken" institution-id imagery-title -1))
      (data-response "The title you have chosen is already taken.")
      (let [new-imagery-id (sql-primitive (call-sql "add_institution_imagery"
                                                    institution-id
                                                    "private"
                                                    imagery-title
                                                    imagery-attribution
                                                    nil
                                                    source-config))]
        (when add-to-all-projects?
          (call-sql "add_imagery_to_all_institution_projects" new-imagery-id))
        (data-response "")))))

;; TODO this should not be needed. Just build the source config on the front end and reuse add-institution-imagery
(defn add-geodash-imagery [{:keys [params]}]
  (let [institution-id      (tc/str->int (:institutionId params))
        imagery-title       (:imageryTitle params)
        imagery-attribution (:imageryAttribution params)
        gee-url             (:geeUrl params)
        gee-params          (json/read-str (:geeParam params))
        source-config       {:type      "GeeGateway"
                             :geeUrl    gee-url
                             :geeParams gee-params}]
    (if (sql-primitive (call-sql "imagery_name_taken" institution-id imagery-title -1))
      (data-response "The title you have chosen is already taken.")
      (do
        (call-sql "add_institution_imagery"
                  institution-id
                  "private"
                  imagery-title
                  imagery-attribution
                  nil
                  source-config)
        (data-response "")))))

(defn update-institution-imagery [{:keys [params]}]
  (let [imagery-id           (tc/str->int (:imageryId params))
        imagery-title        (:imageryTitle params)
        imagery-attribution  (:imageryAttribution params)
        source-config        (json/read-str (:sourceConfig params))
        add-to-all-projects? (tc/str-bool (:addToAllProjects params) true)
        institution-id       (sql-primitive (call-sql (str "SELECT instituion_rid FROM imagery WHERE imagery_uid = "
                                                           imagery-id)))]
    (if (call-sql "imagery_name_taken" institution-id imagery-title imagery-id)
      (data-response "The title you have chosen is already taken.")
      (do
        (call-sql "update_institution_imagery"
                  imagery-id
                  imagery-title
                  imagery-attribution
                  source-config)
        (when add-to-all-projects?
          (call-sql "add_imagery_to_all_institution_projects" imagery-id))
        (data-response "")))))

(defn archive-institution-imagery [{:keys [params]}]
  (call-sql "archive_imagery" (tc/str->int (:imageryId params)))
  (data-response ""))

use dissoc instead of select keys

use dissoc instead of select keys