    :schedule {:commit
               {:job :build}}})
 
+(defn parse-spec-actions [spec]
+  (->> spec
+       (#(select-keys % [:setup :dependencies :compile :test :extra]))
+       (map-vals (fn [lines]
+                   (->> lines
+                        (re-seq #".*")
+                        (vec)
+                        (remove empty?)
+                        (map parse-action))))))
+
 (defn spec-commands [spec]
   (->> spec
        ((juxt :setup :dependencies :compile :test :extra))
        (mapcat (fn [line]
                  (str/split (or line "") #"\r\n")))
        (remove empty?)))
+
+(defn validate-action-map [cmd]
+  (validate! [(is-map?)
+              (require-predicate (fn [cmd]
+                                   (= 1 (count cmd))))
+              (fn [cmd]
+                ((allow-keys [:environment :pwd] (format "only :environment and :pwd are allowed in action %s" cmd)) (-> cmd (first) (val))))] cmd))
+
+(defn parse-action-map
+  "Converts user command string into a bash action."
+  [cmd]
+  (validate-action-map cmd)
+  (let [body (-> cmd (first) (key))
+        body (apply-if (keyword? body) name body)
+        env (-> cmd (first) (val) :environment)
+        pwd (-> cmd (first) (val) :pwd)]
+    (bash body :environment env :pwd pwd)))
+
+(defn parse-action [cmd]
+  (cond
+   (string? cmd) (bash cmd)
+   (map? cmd) (parse-action-map cmd)))
+
+(defn validate-job [job]
+  (validate! [(require-keys [:template])] job))
+
+(defn parse-actions [job]
+  (doall (map parse-action (-> job :commands))))
+
+(defn set-spec [actions]
+  (map #(action/set-source % :spec) actions))
+
+(defn load-actions
+  "Finds job in config, loads approprate template and parses actions"
+  [job]
+  (let [template-name (-> job :template)
+        actions (set-spec (parse-actions job))]
+    (template/apply-template template-name actions)))
+
+(defn load-job [config job-name]
+  (throw-if-not (keyword? job-name) "job name must be a keyword")
+  (let [job (-> config :jobs job-name)]
+    (throw-if-not job "could not find job named %s" job-name)
+    (validate-job job)
+    job))
+
+(defn parse-notify [notifies]
+  (for [n notifies]
+    (if (re-find #"^:" n)
+      (keyword (.substring n 1))
+      n)))
+
+(defn keypair-name
+  "Infer the name of the keypair given the path to the private key"
+  [priv-key-path]
+  (let [priv-key-path (fs/basename priv-key-path)
+        idx (.lastIndexOf priv-key-path ".")]
+    (if (> idx 0)
+      (.substring priv-key-path 0 idx)
+      priv-key-path)))
+
+(defn load-keys
+  "Slurp the file paths and returns a map. Url is the canonical url for the repo. "
+  [node url]
+  (when (-> node :private-key)
+    (try
+      (let [pub-key-path (-> node :public-key)
+            priv-key-path (-> node :private-key)
+            pub-key (slurp (fs/join (git/default-repo-path url) pub-key-path))
+            priv-key (slurp (fs/join (git/default-repo-path url) priv-key-path))]
+        {:private-key priv-key
+         :public-key pub-key
+         :keypair-name (ec2/ensure-keypair (keypair-name priv-key-path) pub-key)})
+      (catch Exception e
+        (println "except" e)
+        (error e "error loading keys")))))
+
+(defn load-default-node [config]
+  circle.backend.build.nodes.rails/default-rails-node)
+
+(defn load-specific-node
+  "Load a named node out of the config file"
+  [config node-name url]
+  (let [node (-> config :nodes node-name)
+        node (merge node (load-keys node url))]
+    (throw-if-not node "node named %s not found in config" node-name)
+    node))
+
+(defn load-node [config job url]
+  (let [node-name (-> job :node (keyword))]
+    (if node-name
+      (load-specific-node config node-name url)
+      (load-default-node config))))
+
+(defn translate-email-recipient
+  "Translates an email recipient like :committer to an email address"
+  [github-json to]
+  (condp = to
+    :owner (-?> github-json :repository :owner :email (vector))
+    :committer (->> (-> github-json :commits)
+                    (map (fn [c]
+                           (-> c :author :email))))))
+
+(defn get-build-email-recipients
+  "Takes a seq of email notifiers, like :committer. returns email literals"
+  [notifies github-json]
+  (->> notifies
+       (mapcat #(translate-email-recipient github-json %))
+       (set)))
+
+(defn infer-project-name [url]
+  (-> url
+      (clj-url.core/parse)
+      :path
+      (str/split #"/")
+      (last)
+      (str/replace #"\.git$" "")))
+
+(defn minimal-project [url]
+  {:name (infer-project-name url)})
+
+(defn minimal-config [url]
+  {})
+
 
 (defn get-config-from-db [url]
   (let [project (project/get-by-url url)]

     (get-config-from-db url)
     (get-config-from-yml url))))

(defn validate-action-map [cmd]
  (validate! [(is-map?)
              (require-predicate (fn [cmd]
                                   (= 1 (count cmd))))
              (fn [cmd]
                ((allow-keys [:environment :pwd] (format "only :environment and :pwd are allowed in action %s" cmd)) (-> cmd (first) (val))))] cmd))

(defn parse-action-map
  "Converts user command string into a bash action."
  [cmd]
  (validate-action-map cmd)
  (let [body (-> cmd (first) (key))
        body (apply-if (keyword? body) name body)
        env (-> cmd (first) (val) :environment)
        pwd (-> cmd (first) (val) :pwd)]
    (bash body :environment env :pwd pwd)))

(defn parse-action [cmd]
  (cond
   (string? cmd) (bash cmd)
   (map? cmd) (parse-action-map cmd)))

(defn validate-job [job]
  (validate! [(require-keys [:template])] job))

(defn parse-actions [job]
  (doall (map parse-action (-> job :commands))))

(defn set-spec [actions]
  (map #(action/set-source % :spec) actions))

(defn load-actions
  "Finds job in config, loads approprate template and parses actions"
  [job]
  (let [template-name (-> job :template)
        actions (set-spec (parse-actions job))]
    (template/apply-template template-name actions)))

(defn load-job [config job-name]
  (throw-if-not (keyword? job-name) "job name must be a keyword")
  (let [job (-> config :jobs job-name)]
    (throw-if-not job "could not find job named %s" job-name)
    (validate-job job)
    job))

(defn parse-notify [notifies]
  (for [n notifies]
    (if (re-find #"^:" n)
      (keyword (.substring n 1))
      n)))

(defn keypair-name
  "Infer the name of the keypair given the path to the private key"
  [priv-key-path]
  (let [priv-key-path (fs/basename priv-key-path)
        idx (.lastIndexOf priv-key-path ".")]
    (if (> idx 0)
      (.substring priv-key-path 0 idx)
      priv-key-path)))

(defn load-keys
  "Slurp the file paths and returns a map. Url is the canonical url for the repo. "
  [node url]
  (when (-> node :private-key)
    (try
      (let [pub-key-path (-> node :public-key)
            priv-key-path (-> node :private-key)
            pub-key (slurp (fs/join (git/default-repo-path url) pub-key-path))
            priv-key (slurp (fs/join (git/default-repo-path url) priv-key-path))]
        {:private-key priv-key
         :public-key pub-key
         :keypair-name (ec2/ensure-keypair (keypair-name priv-key-path) pub-key)})
      (catch Exception e
        (println "except" e)
        (error e "error loading keys")))))

(defn load-default-node [config]
  circle.backend.build.nodes.rails/default-rails-node)

(defn load-specific-node
  "Load a named node out of the config file"
  [config node-name url]
  (let [node (-> config :nodes node-name)
        node (merge node (load-keys node url))]
    (throw-if-not node "node named %s not found in config" node-name)
    node))

(defn load-node [config job url]
  (let [node-name (-> job :node (keyword))]
    (if node-name
      (load-specific-node config node-name url)
      (load-default-node config))))

(defn translate-email-recipient
  "Translates an email recipient like :committer to an email address"
  [github-json to]
  (condp = to
    :owner (-?> github-json :repository :owner :email (vector))
    :committer (->> (-> github-json :commits)
                    (map (fn [c]
                           (-> c :author :email))))))

(defn get-build-email-recipients
  "Takes a seq of email notifiers, like :committer. returns email literals"
  [notifies github-json]
  (->> notifies
       (mapcat #(translate-email-recipient github-json %))
       (set)))

(defn add-project-info
  "Adds a bunch of relevant information from the project to the build

     (add-project-info project)
     (build/build))))


(defn infer-build-from-url
  "Assumes url does not have a circle.yml. Examine the source tree, and return a build"
