   [opts & inputs]
   (let [requires (mapcat -requires inputs)
         required-cljs (cljs-dependencies opts requires)
-        required (dependencies opts (concat (mapcat -requires required-cljs) requires))]
-    (distinct
-     (concat (map #(-> (javascript-file (or (:url %) (io/resource (:file %)))
-                                        (:provides %)
-                                        (:requires %))
-                       (assoc :group (:group %))) required)
-             required-cljs
-             inputs))))
+        required (dependencies opts (set (concat (mapcat -requires required-cljs) requires)))]
+    (concat (map #(-> (javascript-file (or (:url %) (io/resource (:file %)))
+                                       (:provides %)
+                                       (:requires %))
+                      (assoc :group (:group %))) required)
+            required-cljs
+            inputs)))
 
 (comment
   ;; add dependencies to literal js

(defn javascript-file [^URL url provides requires]
  (JavaScriptFile. url (map name provides) (map name requires)))

(defprotocol Compilable
  (-compile [this opts] "Returns one or more IJavaScripts."))

;; Compile
;; =======

(defmacro with-core
  "Ensure that core.cljs has been loaded."
  [& body]
  `(do (when-not (:defs (get @comp/namespaces 'cljs.core))
         (comp/analyze-file "cljs/core.cljs"))
       ~@body))

(defn empty-env []
  {:ns (@comp/namespaces comp/*cljs-ns*) :context :statement :locals {}})

(defn compile-form-seq
  "Compile a sequence of forms to a JavaScript source string."
  [forms]
  (with-core
    (with-out-str
      (binding [comp/*cljs-ns* 'cljs.user]
        (doseq [form forms]
          (comp/emit (comp/analyze (empty-env) form)))))))

(defn output-directory [opts]
  (or (:output-dir opts) "out"))

(defn compile-file
  "Compile a single cljs file. If no output-file is specified, returns
  a string of compiled JavaScript. With an output-file option, the
  compiled JavaScript will written to this location and the function
  returns a JavaScriptFile. In either case the return value satisfies
  IJavaScript."
  [^File file {:keys [output-file] :as opts}]
  (if output-file
    (let [out-file (io/file (output-directory opts) output-file)
          ns-info (comp/compile-file file out-file)]
      (javascript-file (to-url (:file ns-info))
                       (:provides ns-info)
                       (:requires ns-info)))
    (compile-form-seq (comp/forms-seq file))))

(defn compile-dir
  "Recursively compile all cljs files under the given source
  directory. Return a list of JavaScriptFiles in dependency order."
  [^File src-dir opts]
  (let [out-dir (output-directory opts)
        compiled-info (comp/compile-root src-dir out-dir)]
    (map (fn [{:keys [file ns requires]}]
           (javascript-file (to-url file) [ns] requires))
         compiled-info)))

(defn mkdirs
  "Create all parent directories the the passed file."
  [f]
  (.mkdirs (.getParentFile (.getCanonicalFile f))))

(defn path-from-jarfile
  "Given the URL of a file within a jar, return the path of the file
  from the root of the jar."
  [^URL url]
  (last (string/split (.getFile url) #"\.jar!/")))

(defn jar-file-to-disk
  "Copy a file contained within a jar to disk. Return the created file."
  [url out-dir]
  (let [out-file (io/file out-dir (path-from-jarfile url)) 
        content (slurp (io/reader url))]
    (do (mkdirs out-file)
        (spit out-file content)
        out-file)))

(extend-protocol Compilable

  File
  (-compile [this opts]
    (if (.isDirectory this)
      (compile-dir this opts)
      (compile-file this opts)))

  URL
  (-compile [this opts]
    (case (.getProtocol this)
      "file" (-compile (io/file this) opts)
      "jar" (let [out-file (jar-file-to-disk this (output-directory opts))]
              (-compile out-file opts))))
  
  clojure.lang.PersistentList
  (-compile [this opts]
    (compile-form-seq [this]))
  
  String
  (-compile [this opts] (-compile (io/file this) opts))
  
  clojure.lang.PersistentVector
  (-compile [this opts] (compile-form-seq this))
  )

(comment
  ;; compile a file in memory
  (-compile "samples/hello/src/hello/core.cljs" {})
  ;; compile a file to disk - see file @ 'out/clojure/set.js'
  (-compile (io/resource "clojure/set.cljs") {:output-file "clojure/set.js"})
  ;; compile a project
  (-compile (io/file "samples/hello/src") {})
  ;; compile a project with a custom output directory
  (-compile (io/file "samples/hello/src") {:output-dir "my-output"})
  ;; compile a form
  (-compile '(defn plus-one [x] (inc x)) {})
  ;; compile a vector of forms
  (-compile '[(ns test.app (:require [goog.array :as array]))
              (defn plus-one [x] (inc x))]
            {})
  )

;; Dependencies
;; ============
;;
;; Find all dependencies from files on the classpath. Eliminates the
;; need for closurebuilder. cljs dependencies will be compiled as
;; needed.

(defn build-index
  "Index a list of dependencies by namespace and file name. There can

  [opts & sources]
  (let [disk-sources (map #(source-on-disk opts %) sources)]
    (let [goog-deps (io/file (output-directory opts) "goog/deps.js")]
      (do (comp/mkdirs goog-deps)
          (spit goog-deps (deps-file opts (filter #(= (:group %) :goog) disk-sources)))
          (output-deps-file opts (remove #(= (:group %) :goog) disk-sources))))))

