Skip to content
This repository has been archived by the owner on Dec 3, 2023. It is now read-only.

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
runoshun committed Mar 21, 2014
0 parents commit 4a58710
Show file tree
Hide file tree
Showing 14 changed files with 275 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions sample-project/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
9 changes: 9 additions & 0 deletions sample-project/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# sample-project for lein-watch plugin

See `project.clj`

## License

Distributed under the MIT License.

Copyright © 2014 FIXME
3 changes: 3 additions & 0 deletions sample-project/doc/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Introduction to test-watch

TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/)
57 changes: 57 additions & 0 deletions sample-project/project.clj
Original file line number Diff line number Diff line change
@@ -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]}}})

1 change: 1 addition & 0 deletions sample-project/resources/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<html><head><link href="screen.css" rel="stylesheet" type="text/css" /></head><body><div><span>Hello world</span></div></body></html>
5 changes: 5 additions & 0 deletions sample-project/resources/public/screen.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
body {
line-height: 1.5;
font-size: 20px;
font-family: sans-serif;
}
9 changes: 9 additions & 0 deletions sample-project/src-garden/test_watch/css.clj
Original file line number Diff line number Diff line change
@@ -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}])
9 changes: 9 additions & 0 deletions sample-project/src-hiccup/test_watch/hiccup.clj
Original file line number Diff line number Diff line change
@@ -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"]]]])))

6 changes: 6 additions & 0 deletions sample-project/src/test_watch/core.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(ns test-watch.core)

(defn foo
"I don't do a whole lot."
[x]
(println x "Hello, World!"))
7 changes: 7 additions & 0 deletions sample-project/test/test_watch/core_test.clj
Original file line number Diff line number Diff line change
@@ -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))))
111 changes: 111 additions & 0 deletions src/leiningen/watch.clj
Original file line number Diff line number Diff line change
@@ -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))

))

0 comments on commit 4a58710

Please sign in to comment.