Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit c3e5a23c6f311515af4d9d3ac28677d5c9669f55 @tj committed
Showing with 329 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +17 −0 Makefile
  3. +121 −0 Readme.md
  4. +189 −0 src/watch.c
2 .gitignore
@@ -0,0 +1,2 @@
+*.o
+watch
17 Makefile
@@ -0,0 +1,17 @@
+
+CFLAGS = -std=c99
+PREFIX = /usr/local
+
+watch: src/watch.c
+ $(CC) $< $(CFLAGS) -o $@
+
+install: watch
+ install watch $(PREFIX)/bin/watch
+
+uninstall:
+ rm -f $(PREFIX)/bin/watch
+
+clean:
+ rm -f watch
+
+.PHONY: clean install uninstall
121 Readme.md
@@ -0,0 +1,121 @@
+
+# Watch
+
+ A tiny C program used to periodically execute a command.
+
+## Usage
+
+```
+
+Usage: watch [options] <cmd>
+
+Options:
+
+ -q, --quiet only output stderr
+ -i, --interval <n> interval in seconds or ms defaulting to 1
+ -V, --version output version number
+
+```
+
+## Installation
+
+```
+$ make install
+```
+
+## Milliseconds resolution
+
+ This version of `watch(1)` support millisecond resolution
+ with the `ms` suffix:
+
+```
+$ watch -i 300ms echo hey
+```
+
+whereas `300` would be seconds:
+
+```
+$ watch -i 300 echo hey
+```
+
+## Examples
+
+ Watch is pretty handy, here are a few use-cases:
+
+### Running tests
+
+ Ad-hoc mtime watchers are annoying to construct,
+ and have relatively no purpose when you can simply
+ execute your tests at a regular interval. For example
+ run `watch(1)` as a job, running tests each second (or a
+ second after the program exits):
+
+```
+$ watch make test &
+[1] 3794
+✔ bifs.components
+✔ bifs.dark
+✔ bifs.darken
+✔ bifs.image-size
+...
+```
+
+ Your tests will happily chug away, when you want to
+ stop watch simply foreground the job and ^C:
+
+```
+$ fg 1
+```
+
+### Auto-build CSS / JS etc
+
+ Need to build CSS or JavaScript dependencies? use a _Makefile_. With the large quantity of copy-cats (Rake,Jake,Sake,Cake...) people seem to be forgetting that Make is awesome, if you take a little bit of time to learn it you'll love it (or at least most of it).
+
+ Let's say we had some Jade templates, even some nested in sub-directories, we could list them in a _Makefile_ quite easily.
+
+ Below __JADE__ is a list constructed by the shell command `find templates -name "*.jade"`, which is usually a lot easier to manage than listing these files manually, which is also valid, and sometimes important of ordering is relevant. Following that we have __HTML__ which simply substitutes ".jade" with ".html", giving us our HTML targets.
+
+```make
+JADE = $(shell find templates -name "*.jade")
+HTML = $(JADE:.jade=.html)
+```
+
+ Our first target is `all`, becoming the default target for `make`. On the right-hand side of this we specify the dependencies, which in this case is a list of all of our HTML files, not yet built. Make will see this and execute the `%.html` targets, which allows use to use the `jade(1)` executable to translate the dependency on the right of `:`, to the target on the left.
+
+```make
+JADE = $(shell find templates -name "*.jade")
+HTML = $(JADE:.jade=.html)
+
+all: $(HTML)
+
+%.html: %.jade
+ jade < $< > $@
+```
+
+ Now we can build all of these files with a single command `make`:
+
+```
+$ make
+jade < templates/bar.jade > templates/bar.html
+jade < templates/baz/raz.jade > templates/baz/raz.html
+jade < templates/foo.jade > templates/foo.html
+```
+
+ We can also add a `clean` pseudo-target to remove the compiled files with `make clean`. Here it's listed to the right of `.PHONY:`, telling make that it does not expect a file named `./clean` on the fs, so it wont compare mtimes etc. Make is smart about re-executing these actions, if you `make` again you'll notice that since none of the dependencies have changed it'll simply tell you "make: Nothing to be done for `all'.", now doesn't this seem familiar? kinda like all these ad-hoc file watchers hey.
+
+```make
+JADE = $(shell find templates -name "*.jade")
+HTML = $(JADE:.jade=.html)
+
+all: $(HTML)
+
+%.html: %.jade
+ jade < $< > $@
+
+clean:
+ rm -f $(HTML)
+
+.PHONY: clean
+```
+
+ The one missing component is periodical action, which is where `watch(1)` or similar utilities come in, this functionality coupled with Make as a build system creates a powerful duo.
189 src/watch.c
@@ -0,0 +1,189 @@
+
+//
+// watch.c
+//
+// Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+/*
+ * Command version.
+ */
+
+#define VERSION "0.0.1"
+
+/*
+ * Default interval in milliseconds.
+ */
+
+#define DEFAULT_INTERVAL 1000
+
+/*
+ * Max command args.
+ */
+
+#define ARGS_MAX 128
+
+/*
+ * Quiet mode.
+ */
+
+static int quiet = 0;
+
+/*
+ * Output command usage.
+ */
+
+void
+usage() {
+ printf(
+ "\n"
+ " Usage: watch [options] <cmd>\n"
+ "\n"
+ " Options:\n"
+ "\n"
+ " -q, --quiet only output stderr\n"
+ " -i, --interval <n> interval in seconds or ms defaulting to 1\n"
+ " -V, --version output version number\n"
+ "\n"
+ );
+ exit(1);
+}
+
+/*
+ * Milliseconds string.
+ */
+
+int
+milliseconds(const char *str) {
+ int len = strlen(str);
+ return 'm' == str[len-2] && 's' == str[len-1];
+}
+
+/*
+ * Sleep in `ms`.
+ */
+
+void
+mssleep(int ms) {
+ usleep(ms * 1000);
+}
+
+/*
+ * Redirect stdout to `path`.
+ */
+
+void
+redirect_stdout(const char *path) {
+ int fd = open(path, O_WRONLY);
+ if (dup2(fd, 1) < 0) {
+ perror("dup2()");
+ exit(1);
+ }
+}
+
+/*
+ * Parse argv.
+ */
+
+int
+main(int argc, const char **argv){
+ if (1 == argc) usage();
+ int interval = DEFAULT_INTERVAL;
+
+ int len = 0;
+ char *args[ARGS_MAX] = {0};
+
+ for (int i = 1; i < argc; ++i) {
+ const char *arg = argv[i];
+
+ // -h, --help
+ if (!strcmp("-h", arg) || !strcmp("--help", arg)) {
+ usage();
+ }
+
+ // -q, --quiet
+ if (!strcmp("-q", arg) || !strcmp("--quiet", arg)) {
+ quiet = 1;
+ continue;
+ }
+
+ // -V, --version
+ if (!strcmp("-V", arg) || !strcmp("--version", arg)) {
+ printf("%s\n", VERSION);
+ exit(1);
+ }
+
+ // -i, --interval <n>
+ if (!strcmp("-i", arg) || !strcmp("--interval", arg)) {
+ if (argc-1 == i) {
+ fprintf(stderr, "\n --interval requires an argument\n\n");
+ exit(1);
+ }
+
+ // seconds or milliseconds
+ arg = argv[++i];
+ interval = milliseconds(arg)
+ ? atoi(arg)
+ : atoi(arg) * 1000;
+ continue;
+ }
+
+ // cmd args
+ if (len == ARGS_MAX) {
+ fprintf(stderr, "number of arguments exceeded %d\n", len);
+ exit(1);
+ }
+
+ args[len++] = (char *) arg;
+ }
+
+ // <cmd>
+ if (!len) {
+ fprintf(stderr, "\n <cmd> required\n\n");
+ exit(1);
+ }
+
+ // exec loop
+ loop: {
+ pid_t pid;
+ int status;
+ switch (pid = fork()) {
+ // error
+ case -1:
+ perror("fork()");
+ exit(1);
+ // child
+ case 0:
+ if (quiet) redirect_stdout("/dev/null");
+ execvp(args[0], args);
+ // parent
+ default:
+ if (waitpid(pid, &status, 0) < 0) {
+ perror("waitpid()");
+ exit(1);
+ }
+
+ // exit > 0
+ if (WEXITSTATUS(status)) {
+ fprintf(stderr, "\033[90mexit: %d\33[0m\n\n", WEXITSTATUS(status));
+ } else if (quiet) {
+ putchar('.');
+ fflush(stdout);
+ } else {
+ printf("\n");
+ }
+
+ mssleep(interval);
+ goto loop;
+ }
+ }
+
+ return 0;
+}

0 comments on commit c3e5a23

Please sign in to comment.
Something went wrong with that request. Please try again.