cmd/notify: rework notify command

This commit is contained in:
Rafal Jeczalik
2015-02-17 01:25:01 +01:00
parent 9768e2b11d
commit 69b1cd284f
3 changed files with 260 additions and 34 deletions
+38 -2
View File
@@ -1,6 +1,8 @@
notify [![GoDoc](https://godoc.org/github.com/rjeczalik/notify?status.svg)](https://godoc.org/github.com/rjeczalik/notify) [![Build Status](https://img.shields.io/travis/rjeczalik/notify/master.svg)](https://travis-ci.org/rjeczalik/notify "inotify") [![Build Status](https://img.shields.io/travis/rjeczalik/notify/fsevents.svg)](https://travis-ci.org/rjeczalik/notify "FSEvents") [![Build Status](https://img.shields.io/travis/rjeczalik/notify/kqueue.svg)](https://travis-ci.org/rjeczalik/notify "kqueue") [![Build status](https://img.shields.io/appveyor/ci/rjeczalik/notify-246.svg)](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [![Coverage Status](https://img.shields.io/coveralls/rjeczalik/notify/master.svg)](https://coveralls.io/r/rjeczalik/notify?branch=master)
[![Build Status](https://img.shields.io/travis/rjeczalik/notify/master.svg)](https://travis-ci.org/rjeczalik/notify "inotify") [![Build Status](https://img.shields.io/travis/rjeczalik/notify/fsevents.svg)](https://travis-ci.org/rjeczalik/notify "FSEvents") [![Build Status](https://img.shields.io/travis/rjeczalik/notify/kqueue.svg)](https://travis-ci.org/rjeczalik/notify "kqueue") [![Build status](https://img.shields.io/appveyor/ci/rjeczalik/notify-246.svg)](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [![Coverage Status](https://img.shields.io/coveralls/rjeczalik/notify/master.svg)](https://coveralls.io/r/rjeczalik/notify?branch=master)
======
### notify [![GoDoc](https://godoc.org/github.com/rjeczalik/notify?status.svg)](https://godoc.org/github.com/rjeczalik/notify)
Filesystem event notification library on steroids. (under active development)
*Installation*
@@ -9,6 +11,40 @@ Filesystem event notification library on steroids. (under active development)
~ $ go get -u github.com/rjeczalik/notify
```
*Documentation*
*Documentation*
[godoc.org/github.com/rjeczalik/notify](https://godoc.org/github.com/rjeczalik/notify)
### cmd/notify [![GoDoc](https://godoc.org/github.com/rjeczalik/notify?status.svg)](https://godoc.org/github.com/rjeczalik/notify)
Listens on filesystem changes and forwards received events to user-defined handlers.
*Installation*
```
~ $ go get -u github.com/rjeczalik/notify/cmd/notify
```
*Documentation*
[godoc.org/github.com/rjeczalik/notify/cmd/notify](https://godoc.org/github.com/rjeczalik/notify/cmd/notify)
*Usage*
```bash
~ $ notify -c 'echo "Hello from handler! (event={{.Event}}, path={{.Path}})"'
2015/02/17 01:17:40 received notify.Create: "/Users/rjeczalik/notify.tmp"
Hello from handler! (event=create, path=/Users/rjeczalik/notify.tmp)
2015/02/17 01:18:13 received notify.Write: "/Users/rjeczalik/notify.tmp"
Hello from handler! (event=write, path=/Users/rjeczalik/notify.tmp)
```
```bash
~ $ cat > handler <<EOF
> echo "Hello from handler! (event={{.Event}}, path={{.Path}})"
> EOF
~ $ notify -f handler
2015/02/17 01:22:26 received notify.Create: "/Users/rjeczalik/notify.tmp"
Hello from handler! (event=create, path=/Users/rjeczalik/notify.tmp)
2015/02/17 01:22:26 received notify.Remove: "/Users/rjeczalik/notify.tmp"
Hello from handler! (event=remove, path=/Users/rjeczalik/notify.tmp)
```
+220 -30
View File
@@ -2,41 +2,194 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// Command notify listens on filesystem changes and forwards received mapping to
// user-defined handlers.
//
// Usage
//
// usage: notify [-c command] [-f script file] [path]...
//
// The -c flag registers a command handler, which uses the syntax
// of package template. Notify passes struct to the template,
// splits produced string into command and args, and runs it using
// exec.Command(). Additionaly the path and event type values are
// accesible to the process via NOTIFY_PATH and NOTIFY_EVENT
// environment variables.
//
// The struct being passed to the template is:
//
// type Event struct {
// Path string
// Event string
// }
//
// Values for the Event field are:
//
// - create
// - remove
// - rename
// - write
//
// The -t flag registers a file handler, which works similary
// to the -c handler. The only difference the template is read from
// the given file instead of the command line.
//
// The path argument tells notify which director or directories to
// listen on. By default notify listens recursively in current working
// directory.
//
// If no handler is specified notify prints each event to os.Stdout.
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"runtime"
"strings"
"time"
"text/template"
"github.com/rjeczalik/notify"
)
const usage = "usage: notify path [EVENT...]"
const usage = `usage: notify [-c command] [-f script file] [path]...
const tformat = "2006-01-02 15:04:05.0000"
Listens on filesystem changes and forwards received mapping to
user-defined handlers.
var event = map[string]notify.Event{
"all": notify.All,
"create": notify.Create,
"delete": notify.Remove,
"write": notify.Write,
"move": notify.Rename,
The -c flag registers a command handler, which uses the syntax
of package template. Notify passes struct to the template,
splits produced string into command and args, and runs it using
exec.Command(). Additionaly the path and event type values are
accesible to the process via NOTIFY_PATH and NOTIFY_EVENT
environment variables.
The struct being passed to the template is:
type Event struct {
Path string
Event string
}
Values for the Event field are:
- create
- remove
- rename
- write
The -t flag registers a file handler, which works similary
to the -c handler. The only difference the template is read from
the given file instead of the command line.
The path argument tells notify which director or directories to
listen on. By default notify listens recursively in current working
directory.
If no handler is specified notify prints each event to os.Stdout.`
var (
file string
command string
paths = []string{"." + string(os.PathSeparator) + "..."}
env = newenv()
)
var mapping = map[notify.Event]string{
notify.Create: "create",
notify.Remove: "remove",
notify.Rename: "rename",
notify.Write: "write",
}
func parse(s []string) (e []notify.Event) {
if len(s) == 0 {
return []notify.Event{notify.All}
}
for _, s := range s {
event, ok := event[strings.ToLower(s)]
if !ok {
die("invalid event: " + s)
func newenv() func(Event) []string {
env := os.Environ()
for i, s := range env {
s = strings.ToLower(s)
if strings.Contains(s, "NOTIFY_PATH=") || strings.Contains(s, "NOTIFY_EVENT=") {
env[i], env = env[len(env)-1], env[:len(env)-1]
}
e = append(e, event)
}
return
env = append(env, "", "")
return func(e Event) []string {
s := make([]string, len(env))
copy(s, env)
s[len(s)-1] = "NOTIFY_EVENT=" + e.Event
s[len(s)-2] = "NOTIFY_PATH=" + e.Path
return s
}
}
// Handler TODO(rjeczalik)
type Handler struct {
tmpl *template.Template
env []string
}
// NewHandler TODO(rjeczalik)
func NewHandler(text string) (*Handler, error) {
tmpl, err := template.New("main.Handler").Parse(text)
if err != nil {
return nil, err
}
h := &Handler{
tmpl: tmpl,
env: env(Event{}),
}
return h, nil
}
// Run TODO(rjeczalik)
func (h *Handler) Run(e Event) error {
var buf bytes.Buffer
if err := h.tmpl.Execute(&buf, e); err != nil {
return err
}
s := buf.String()
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", s)
default:
cmd = exec.Command("/bin/sh", "-c", s)
}
h.env[len(h.env)-1] = "NOTIFY_EVENT=" + e.Event
h.env[len(h.env)-2] = "NOTIFY_PATH=" + e.Path
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = h.env
return cmd.Run()
}
// Daemon TODO(rjeczalik)
func (h *Handler) Daemon() chan<- Event {
c := make(chan Event)
go func() {
for e := range c {
if err := h.Run(e); err != nil {
log.Println("handler error:", err)
}
}
}()
return c
}
// Event TODO(rjeczalik)
type Event struct {
Path string
Event string
}
// NewEvent TODO(rjeczalik)
func NewEvent(ei notify.EventInfo) Event {
return Event{
Path: ei.Path(),
Event: mapping[ei.Event()],
}
}
func die(v interface{}) {
@@ -44,20 +197,57 @@ func die(v interface{}) {
os.Exit(1)
}
func main() {
if len(os.Args) == 1 {
die(usage)
func init() {
flag.CommandLine.Usage = func() {
fmt.Fprintln(os.Stderr, usage)
}
for _, path := range strings.Split(os.Args[1], string(os.PathListSeparator)) {
ch := make(chan notify.EventInfo, 10)
if err := notify.Watch(path, ch, parse(os.Args[2:])...); err != nil {
flag.StringVar(&file, "f", "", "script file to execute on received event")
flag.StringVar(&command, "c", "", "command to run on received event")
flag.Parse()
if flag.NArg() != 0 {
paths = flag.Args()
}
}
func main() {
var handlers []*Handler
if command != "" {
h, err := NewHandler(command)
if err != nil {
die(err)
}
go func(path string) {
for ei := range ch {
fmt.Printf("[%v] [%s] Event: %v\n", time.Now().Format(tformat), path, ei)
handlers = append(handlers, h)
}
if file != "" {
p, err := ioutil.ReadFile(file)
if err != nil {
die(err)
}
h, err := NewHandler(string(p))
if err != nil {
die(err)
}
handlers = append(handlers, h)
}
var run []chan<- Event
for _, h := range handlers {
run = append(run, h.Daemon())
}
c := make(chan notify.EventInfo, 1)
for _, path := range paths {
if err := notify.Watch(path, c, notify.All); err != nil {
die(err)
}
}
for ei := range c {
log.Println("received", ei)
e := NewEvent(ei)
for _, run := range run {
select {
case run <- e:
default:
log.Println("event dropped due to slow handler")
}
}(path)
}
}
select {}
}
+2 -2
View File
@@ -2,10 +2,10 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// BUG(rjeczalik): Currently notify does not automagically set a watch for newly
// BUG(rjeczalik): Notify does not automagically set a watch for newly
// created directory within recursively watched path for inotify and kqueue. (#5)
// BUG(rjeczalik): Currently notify does not gracefully handle rewatching directories,
// BUG(rjeczalik): Notify does not gracefully handle rewatching directories,
// that were deleted but their watchpoints were not cleaned by the user. (#69)
// BUG(ppknap): Linux(inotify) does not currently support watcher behavior masks