diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e04714b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+/target
+/classes
+/checkouts
+pom.xml
+pom.xml.asc
+*.jar
+*.class
+/.lein-*
+/.nrepl-port
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d6c408d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,33 @@
+# lein-watch
+
+A Leiningen plugin to watch directories and run tasks automatically.
+
+## Usage
+
+Put `[lein-watch "0.1.0-SNAPSHOT"]` into the `:plugins` vector of your project.clj and
+add :watch configuration to your project.clj.
+
+Example configuration (run compile task when .clj files changes) :
+
+ (defproject sample-project
+ ...
+ :watch {
+ :rate 500 ;; check file every 500ms (use 'watchtower' intanally)
+ :watches {
+ :compile {
+ :watch-dirs ["src"]
+ :file-patterns [#"\.clj"]
+ :tasks ["compile"]}}}
+ ...)
+
+and just run watch task
+
+ $ lein watch
+
+See [sample-project](http://github.com/runoshun/lein-watch/sample-project) for more complex usage.
+
+## License
+
+Distributed under the MIT License.
+
+Copyright © 2014 runoshun
diff --git a/project.clj b/project.clj
new file mode 100644
index 0000000..df8aa04
--- /dev/null
+++ b/project.clj
@@ -0,0 +1,7 @@
+(defproject lein-watch "0.0.1"
+ :description "A Leiningen plugin to watch directories and run tasks automatically."
+ :url "http://github.com/runoshun/lein-watch"
+ :license {:name "MIT"
+ :url "http://opensource.org/licenses/MIT"}
+ :dependencies [[watchtower "0.1.1"]]
+ :eval-in-leiningen true)
diff --git a/sample-project/.gitignore b/sample-project/.gitignore
new file mode 100644
index 0000000..e04714b
--- /dev/null
+++ b/sample-project/.gitignore
@@ -0,0 +1,9 @@
+/target
+/classes
+/checkouts
+pom.xml
+pom.xml.asc
+*.jar
+*.class
+/.lein-*
+/.nrepl-port
diff --git a/sample-project/README.md b/sample-project/README.md
new file mode 100644
index 0000000..06fea3c
--- /dev/null
+++ b/sample-project/README.md
@@ -0,0 +1,9 @@
+# sample-project for lein-watch plugin
+
+See `project.clj`
+
+## License
+
+Distributed under the MIT License.
+
+Copyright © 2014 FIXME
diff --git a/sample-project/doc/intro.md b/sample-project/doc/intro.md
new file mode 100644
index 0000000..138fbda
--- /dev/null
+++ b/sample-project/doc/intro.md
@@ -0,0 +1,3 @@
+# Introduction to test-watch
+
+TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/)
diff --git a/sample-project/project.clj b/sample-project/project.clj
new file mode 100644
index 0000000..f2d0bb1
--- /dev/null
+++ b/sample-project/project.clj
@@ -0,0 +1,57 @@
+(defproject watch-sample "0.1.0-SNAPSHOT"
+ :license {:name "Eclipse Public License"
+ :url "http://www.eclipse.org/legal/epl-v10.html"}
+
+ ;; use 'lein-watch' plugin
+ :plugins [[lein-watch "0.0.1"]
+ [lein-garden "0.1.8"]]
+ :dependencies [[org.clojure/clojure "1.5.1"]]
+
+ ;; profiles used in watch tasks
+ :profiles {:garden {:source-paths ["src-garden"]
+ :dependencies [[garden "1.1.5"]]}
+ :hiccup {:source-paths ["src-hiccup"]
+ :dependencies [[hiccup "1.0.5"]]}}
+
+ :garden {
+ :builds [{:id "screen"
+ :stylesheet test-watch.css/screen
+ :compiler {:output-to "resources/public/screen.css"
+ :pretty-print true}}]}
+
+ ;; configuration for 'lein-watch'
+ :watch {
+ ;; polling rate in 'ms' (it's directory passed to 'watchtower')
+ :rate 300
+
+ ;; watcher definition
+ :watchers {
+ ;; run 'lein garden once' when *.clj file under the 'src-garden' changed.
+ :garden {;; :watch-dirs (required) : vector of string
+ ;; Put directories that you want watch.
+ :watch-dirs ["src-garden"]
+
+ ;; :file-patterns (optional) : vector of java.util.regex.Pattern
+ ;; If file name that changed is matched this patterns, tasks are executed.
+ ;; otherwise not executed.
+ ;; default : [#".*"].
+ :file-patterns [#"\.clj"]
+
+ ;; :tasks (required) : vector of (string|symbol)
+ ;; Put tasks that you want executed when file changed.
+ ;; If a value is string, it is evaluated as a leiningen task.
+ ;; If a value is symbol, it is called as a function in project context and
+ ;; is passed changed file as argument.
+ :tasks ["garden once"]
+
+ ;; profiles (optional) : vector of keyword
+ ;; profile names used when executing tasks.
+ ;; default : []
+ :profiles [:garden]}
+ ;; call 'test-watch.hiccup/generate' (defined in 'src-hiccup/test_watch/hiccup.clj')
+ ;; when *.clj file under the 'src-hiccup' changed.
+ :hiccup {:watch-dirs ["src-hiccup"]
+ :profiles [:hiccup]
+ :file-patterns [#"\.clj"]
+ :tasks [test-watch.hiccup/generate]}}})
+
diff --git a/sample-project/resources/public/index.html b/sample-project/resources/public/index.html
new file mode 100644
index 0000000..eff07d8
--- /dev/null
+++ b/sample-project/resources/public/index.html
@@ -0,0 +1 @@
+
Hello world
\ No newline at end of file
diff --git a/sample-project/resources/public/screen.css b/sample-project/resources/public/screen.css
new file mode 100644
index 0000000..00fb820
--- /dev/null
+++ b/sample-project/resources/public/screen.css
@@ -0,0 +1,5 @@
+body {
+ line-height: 1.5;
+ font-size: 20px;
+ font-family: sans-serif;
+}
\ No newline at end of file
diff --git a/sample-project/src-garden/test_watch/css.clj b/sample-project/src-garden/test_watch/css.clj
new file mode 100644
index 0000000..6501207
--- /dev/null
+++ b/sample-project/src-garden/test_watch/css.clj
@@ -0,0 +1,9 @@
+(ns test-watch.css
+ (:require [garden.def :refer [defstylesheet defstyles]]
+ [garden.units :refer [px]]))
+
+(defstyles screen
+ [:body
+ {:font-family "sans-serif"
+ :font-size (px 20)
+ :line-height 1.5}])
diff --git a/sample-project/src-hiccup/test_watch/hiccup.clj b/sample-project/src-hiccup/test_watch/hiccup.clj
new file mode 100644
index 0000000..0d90af0
--- /dev/null
+++ b/sample-project/src-hiccup/test_watch/hiccup.clj
@@ -0,0 +1,9 @@
+(ns test-watch.hiccup
+ (:require [hiccup.core :refer [html]]))
+
+(defn generate [& _]
+ (spit "resources/public/index.html"
+ (html [:html
+ [:head [:link {:rel "stylesheet" :type "text/css" :href "screen.css"}]]
+ [:body [:div [:span "Hello world"]]]])))
+
diff --git a/sample-project/src/test_watch/core.clj b/sample-project/src/test_watch/core.clj
new file mode 100644
index 0000000..01bc78b
--- /dev/null
+++ b/sample-project/src/test_watch/core.clj
@@ -0,0 +1,6 @@
+(ns test-watch.core)
+
+(defn foo
+ "I don't do a whole lot."
+ [x]
+ (println x "Hello, World!"))
diff --git a/sample-project/test/test_watch/core_test.clj b/sample-project/test/test_watch/core_test.clj
new file mode 100644
index 0000000..de70f6e
--- /dev/null
+++ b/sample-project/test/test_watch/core_test.clj
@@ -0,0 +1,7 @@
+(ns test-watch.core-test
+ (:require [clojure.test :refer :all]
+ [test-watch.core :refer :all]))
+
+(deftest a-test
+ (testing "FIXME, I fail."
+ (is (= 0 1))))
diff --git a/src/leiningen/watch.clj b/src/leiningen/watch.clj
new file mode 100644
index 0000000..5809a41
--- /dev/null
+++ b/src/leiningen/watch.clj
@@ -0,0 +1,111 @@
+(ns leiningen.watch
+ (:require [clojure.string :as string]
+ [clojure.java.io :as io]
+ [watchtower.core :as wt]
+ [leiningen.core.eval :as lein-eval]
+ [leiningen.core.main :as lein-main]
+ [leiningen.core.project :as lein-project]))
+
+(def ^:private default-watcher-options
+ {:file-patterns [#".*"]
+ :profiles []})
+
+(def ^:private default-global-settings
+ {:rate 300})
+
+(defn- non-nils [coll] (remove nil? coll))
+
+(defn- ensure-regex [pat]
+ (if (instance? java.util.regex.Pattern pat)
+ pat
+ (re-pattern pat)))
+
+(defn- match? [patterns file]
+ (boolean (first (non-nils (map #(re-find % (str file))
+ (map ensure-regex patterns))))))
+
+(defn- child? [parent child]
+ (not (.isAbsolute (.relativize (.toURI (io/file parent))
+ (.toURI (io/file child))))))
+
+(defn- make-route [dir patterns group]
+ (fn [file]
+ (if (and (child? dir file) (match? patterns file))
+ group
+ nil)))
+
+(defn- make-router [watchers]
+ (fn [file]
+ (let [routes (mapcat (fn [group]
+ (let [options (group watchers)
+ patterns (-> options :file-patterns)
+ dirs (-> options :watch-dirs)]
+ (map (fn [dir] (make-route dir patterns group)) dirs)))
+ (keys watchers))]
+ (first (non-nils (map #(% file) routes))))))
+
+(defn- separate-ns [sym]
+ (let [syms (string/split (str sym) #"/")]
+ (if (= 2 (count syms))
+ (symbol (first syms))
+ nil)))
+
+(defn- run-tasks [project watcher file]
+ (let [project (lein-project/merge-profiles project (:profiles watcher))
+ tasks (:tasks watcher)]
+ (println (str "[lein-watch] file changed : " file))
+ (println (str "[lein-watch] run-tasks : " tasks))
+ (doseq [task tasks]
+ (cond
+ (string? task) (lein-main/resolve-and-apply
+ project
+ (string/split (string/replace-first task #"%f" (str file)) #"\s+"))
+ (symbol task) (let [ns (separate-ns task)
+ file-str (.getAbsolutePath file)
+ form (if ns
+ `(do (require '~ns) (~task ~file-str))
+ `(~task ~file-str))]
+ (lein-eval/eval-in-project project form))))))
+
+(defn- ensure-slash [dir]
+ (if-not (.endsWith dir "/")
+ (str dir "/")
+ dir))
+
+(defn watch
+ "Watch directories and run tasks when file changed."
+ [project & args]
+ (let [settings (:watch project)
+ settings (merge default-global-settings settings)
+ watchers (:watchers settings)
+ watchers (reduce (fn [m [k v]] (assoc m k (merge default-watcher-options v))) {} watchers)
+ router (make-router watchers)
+ dirs (map ensure-slash (mapcat :watch-dirs (vals watchers)))
+ process-event (fn [files]
+ (doseq [file files]
+ (run-tasks project (watchers (router file)) file)))]
+ (if watchers
+ (deref
+ (wt/watcher dirs
+ (wt/rate (:rate settings))
+ (wt/on-change process-event)))
+ (println "no watcher found."))))
+
+(comment
+(do
+ (def project
+ {:watch
+ {:watchers
+ {:garden {:watch-dirs ["src-garden"]
+ :file-patterns [#".*"]
+ :tasks ["garden once"]}
+ :hiccup {:watch-dirs ["src-hiccup"]
+ :file-patterns [#"\.txt"]
+ :tasks ["hiccpu once"]}}}})
+
+ (def router (make-router (-> project :watch :watchers)))
+ (assert (= (router (io/file "src-garden/foo")) :garden))
+ (assert (= (router "src-hiccup/bar") nil))
+ (assert (= (router "src-hiccup/bar.txt") :hiccup))
+
+))