shithub: watch

Download patch

ref: 5d4e55ea225f736f41d4e351033ba269e117c149
author: kvik <vp@kvik.link>
date: Fri May 17 17:31:26 EDT 2019

init

--- /dev/null
+++ b/mkfile
@@ -1,0 +1,14 @@
+</$objtype/mkfile
+
+TARG=watch
+OFILES=$TARG.$O
+BIN=$home/bin/$objtype
+MAN=/sys/man/1
+
+</sys/src/cmd/mkone
+
+install:V: man
+
+uninstall:V:
+	rm -f $BIN/$TARG
+	rm -f $MAN/$TARG
--- /dev/null
+++ b/watch.c
@@ -1,0 +1,204 @@
+#include <u.h>
+#include <libc.h>
+#include <regexp.h>
+
+typedef struct List List;
+struct List {
+	List *next;
+	union {
+		Dir;
+		Reprog *re;
+	};
+};
+
+char pwd[1024];
+int period = 1000;
+int noregroup = 0;
+List *expl;
+List *filel;
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [[-e expr] ...] [-t sec] [cmd]\n", argv0);
+	exits("usage");
+}
+
+void*
+emalloc(ulong sz)
+{
+	void *v = malloc(sz);
+	if(v == nil)
+		sysfatal("malloc: %r");
+	setmalloctag(v, getcallerpc(&sz));
+	memset(v, 0, sz);
+	return v;
+}
+char*
+estrdup(char *s)
+{
+	if((s = strdup(s)) == nil)
+		sysfatal("strdup: %r");
+	setmalloctag(s, getcallerpc(&s));
+	return s;
+}
+
+char*
+join(char **arg)
+{
+	int i;
+	char *s, *p;
+	
+	s = estrdup("");
+	for(i = 0; arg[i]; i++){
+		p = s;
+		if((s = smprint("%s %s", s, arg[i])) == nil)
+			sysfatal("smprint: %r");
+		free(p);
+	}
+	return s;
+}
+
+void
+eadd(char *exp)
+{
+	List *r;
+	
+	r = emalloc(sizeof(*r));
+	r->re = regcomp(exp);
+	r->next = expl;
+	expl = r;
+}
+
+void
+fadd(Dir *d)
+{
+	List *f;
+	
+	f = emalloc(sizeof(*f));
+	f->Dir = *d;
+	f->next = filel;
+	filel = f;
+}
+
+int
+tracked(char *name)
+{
+	List *r;
+
+	for(r = expl; r; r = r->next)
+		if(regexec(r->re, name, nil, 0))
+			return 1;
+	return 0;
+}
+
+int
+changed(Dir *d)
+{
+	List *f;
+
+	for(f = filel; f; f = f->next)
+		if(f->type == d->type)
+		if(f->dev == d->dev)
+		if(f->qid.path == d->qid.path)
+		if(f->qid.type == d->qid.type)
+			if(f->qid.vers == d->qid.vers)
+				return 0;
+			else{
+				f->Dir = *d;
+				return 1;
+			}
+	fadd(d);
+	return 1;
+}
+
+void
+watch(void)
+{
+	static int first = 1;
+	long fd, n;
+	Dir *d, *p;
+	
+	for(;;){
+		sleep(period);
+		if((fd = open(pwd, OREAD)) < 0)
+			sysfatal("open: %r");
+		if((n = dirreadall(fd, &d)) < 0)
+			sysfatal("dirreadall: %r");
+		close(fd);
+		for(p = d; n--; p++){
+			if(tracked(p->name)){
+				if(first){
+					fadd(p);
+					continue;
+				}
+				if(changed(p)){
+					free(d);
+					return;
+				}
+			}
+		}
+		first = 0;
+		free(d);
+	}
+}
+
+void
+regroup(void)
+{
+	int fd, n, noteid;
+	char buf[128];
+	
+	snprint(buf, sizeof buf, "/proc/%d/noteid", getppid());
+	if((fd = open(buf, OREAD)) < 0)
+		sysfatal("open: %r");
+	if((n = read(fd, buf, 128)) < 0)
+		sysfatal("read: %r");
+	close(fd);
+	buf[n] = 0;
+	noteid = strtol(buf, nil, 10);
+	snprint(buf, sizeof buf, "/proc/%d/noteid", getpid());
+	if((fd = open(buf, OWRITE)) < 0)
+		sysfatal("open: %r");
+	if(fprint(fd, "%d", noteid) < 0)
+		sysfatal("fprint: %r");
+	close(fd);
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *cmd;
+	Waitmsg *m;
+
+	cmd = "mk";
+	ARGBEGIN{
+	case 'e':
+		eadd(EARGF(usage())); break;
+	case 't':
+		period = 1000*strtol(EARGF(usage()), nil, 10); break;
+	case 'G':
+		noregroup = 1; break;
+	default: usage();
+	}ARGEND;
+	if(expl == nil)
+		eadd("\.[chsy]$");
+	if(argc > 0)
+		cmd = join(argv);
+	if(getwd(pwd, sizeof pwd) == nil)
+		sysfatal("getwd: %r");
+
+	if(noregroup == 0) regroup();
+	for(;;){
+		watch();
+		switch(fork()){
+		case -1: sysfatal("fork: %r");
+		case 0:
+			execl("/bin/rc", "rc", "-c", cmd, nil);
+			sysfatal("execl: %r");
+		}
+		if((m = wait()) && m->msg[0])
+			fprint(2, "watch: %s\n", m->msg);
+		free(m);
+	}
+}
--- /dev/null
+++ b/watch.man
@@ -1,0 +1,63 @@
+.TH WATCH 1
+.SH NAME
+watch \- run a command on file change
+.SH SYNOPSIS
+.B watch 
+[
+.B -t
+sec
+] [[
+.B -e
+pattern
+] ... ] [
+.B command
+]
+.SH DESCRIPTION
+.PP
+Run
+.IR command
+.RB ( mk
+by default) when a change to files
+in the current directory is detected.
+.PP
+The options are as follows:
+.TF "-e pattern"
+.TP
+.BI -e pattern
+Watch files matching a
+.IR pattern ;
+it may be given multiple times and
+defaults to
+.BR \e.[chsy]$ 
+.TP
+.BI -t sec
+Sets the polling period;
+one second by default.
+.TP
+.B -G
+Prevents regrouping to the parent
+process' note group. The default
+to regroup was chosen to make it
+easy to kill
+.I watch
+in common use by  interrupting the
+parent shell.
+.SH EXAMPLES
+.EX
+watch -e '\e.man$' 'mk install; window man -P prog' &
+.EE
+.SH SEE ALSO
+.SH SOURCE
+.B git://code.kvik.link/watch
+.SH BUGS
+.I Watch
+will not react on file removal.
+.PP
+.I Qid.vers
+is watched for changes, which is
+not maintained by every file server.
+.PP
+The polling period is actually
+.I sec
+plus the time it takes to run
+.IR command .