Permalink
Browse files

Initial commit. Adding files for first release.

  • Loading branch information...
0 parents commit c7c09e3dc11ac59876101407f5eb1c4e30116b41 @meowgorithm committed May 23, 2012
Showing with 418 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +12 −0 README.md
  3. +30 −0 bin/cakewalk
  4. +221 −0 lib/cakewalk.js
  5. +38 −0 lib/errors.js
  6. +89 −0 lib/walk.js
  7. +26 −0 package.json
@@ -0,0 +1,2 @@
+.DS_Store
+node_modules/*
@@ -0,0 +1,12 @@
+# Cakewalk
+
+Cakewalk walks a given directory looking for CoffeeScript, Stylus, and Jade files, watching them for changes and compiling them when they change.
+
+## Install
+ npm install -g cakewalk
+
+## Usage
+ cakewalk [/path/to/watch]
+
+## For more information
+ cakewalk -h
@@ -0,0 +1,30 @@
+#!/usr/bin/env node
+
+var optimist = require('optimist');
+var colors = require('colors');
+var Cakewalker = require('../lib/cakewalk.js').Cakewalker;
+
+var argv = optimist
+ .wrap(76)
+ .usage('Usage $0 [path]')
+ .alias('h', 'help')
+ .describe('h', 'Show this help message and exit')
+ .argv;
+
+if(argv.h) {
+ console.log(optimist.help());
+ process.exit();
+}
+
+directory = argv._[0] || '.';
+
+console.log('_.:*:._.:* Cakewalk *:._.:*:._'.rainbow);
+
+new Cakewalker(directory);
+
+process.on('SIGINT', function() {
+ console.log('\nExiting...');
+ return process.exit();
+});
+
+// vim: syntax=javascript
@@ -0,0 +1,221 @@
+var fs = require('fs');
+var coffee = require('coffee-script');
+var stylus = require('stylus');
+var jade = require('jade');
+var colors = require('colors');
+var walk = require('./walk.js').walk;
+var BuildError = require('./errors.js').BuildError;
+var CoffeeScriptError = require('./errors.js').CoffeeScriptError;
+var StylusError = require('./errors.js').StylusError;
+var JadeError = require('./errors.js').JadeError;
+
+
+/**
+ * Watch a file, passing its contents through a specified function and writing
+ * the result to disk.
+ */
+function Builder(source, destination, compileFunction) {
+ this.source = source;
+ this.destination = destination;
+ this.compileFunction = compileFunction;
+ this.build();
+ this.start();
+}
+
+Builder.prototype = {
+
+ /**
+ * Compile our target file.
+ */
+ build: function() {
+ console.log('Reading:'.grey, this.source);
+ var self = this;
+
+ fs.readFile(this.source, 'utf8', function(err, data) {
+
+ try {
+ var compiled = self.compileFunction(data);
+ } catch(err) {
+ err.printError();
+ }
+
+ if(compiled) {
+ fs.writeFile(self.destination, compiled, function(err) {
+ console.log('Writing:'.grey, self.destination);
+ });
+ }
+
+ });
+ },
+
+ /**
+ * Start watching file and build when it changes.
+ */
+ start: function() {
+ var self = this;
+ fs.watch(this.source, function(event, filename) {
+ if(event == 'change') {
+ self.build();
+ }
+ });
+ },
+
+ /**
+ * Stop watching (and building) file.
+ */
+ stop: function() {
+ fs.unwatchFile(this.source);
+ }
+
+};
+
+
+/**
+ * Compiles CoffeeScript into JavaScript.
+ */
+function CoffeeBuilder(source, destination) {
+
+ var destination = destination || replaceExtension(source, 'js');
+
+ var builder = new Builder(source, destination, function(input) {
+ try {
+ return coffee.compile(input);
+ } catch(err) {
+ throw new CoffeeScriptError(err.message, source);
+ }
+ });
+
+ this.start = builder.start;
+ this.stop = builder.stop;
+}
+
+
+/**
+ * Compiles Stylus into CSS.
+ */
+function StylusBuilder(source, destination) {
+
+ var destination = destination || replaceExtension(source, 'css');
+
+ var builder = new Builder(source, destination, function(input) {
+ var output;
+ stylus.render(input, {}, function(err, css) {
+ if(err) {
+ throw new StylusError(err.message, source);
+ }
+ output = css;
+ });
+ return output;
+ });
+
+ this.start = builder.start;
+ this.stop = builder.stop;
+}
+
+
+/**
+ * Compiles Jade into HTML.
+ */
+function JadeBuilder(source, destination) {
+
+ var destination = destination || replaceExtension(source, 'html');
+
+ var builder = new Builder(source, destination, function(input) {
+ try {
+ return jade.compile(input, { pretty: true })();
+ } catch(err) {
+ throw new JadeError(err.message, source);
+ }
+ });
+
+ this.start = builder.start;
+ this.stop = builder.stop;
+}
+
+
+/*
+ * Replace the extension on a filename.
+ */
+function replaceExtension(filename, newExtension) {
+ return filename.replace(/(?:\.\w+)$/i, '.' + newExtension);
+};
+
+
+/**
+ * Cakewalker
+ *
+ * Object that initiates the walking procedure.
+ *
+ * Usage:
+ * new Cakewalk('path/to/directory/to/watch');
+ */
+function Cakewalker(directory) {
+
+ this.builders = [];
+ this.directory = directory || '.';
+
+ this.start();
+
+}
+
+// Note: the last rule in the below pattern should ignore all files prefixed
+// with an underscore. Those files should be treated as includes and mixins and
+// thus need not be compiled separately.
+Cakewalker.IGNORE_PATTERN = new RegExp(/(?:\.git|\.svn|node_modules|\/_)/);
+
+Cakewalker.COFFEE_PATTERN = new RegExp(/\.coffee$/);
+Cakewalker.STYLUS_PATTERN = new RegExp(/\.styl$/);
+Cakewalker.JADE_PATTERN = new RegExp(/\.jade$/);
+
+Cakewalker.prototype = {
+
+ /**
+ * Walk a directory, find files to compile, and watch them for changes.
+ */
+ start: function() {
+
+ var self = this;
+
+ walk(this.directory, function(err, files) {
+
+ if(err) throw new Error(err);
+
+ for(var i=0; i<files.length; i++) {
+ var file = files[i];
+
+ if(!Cakewalker.IGNORE_PATTERN.test(file)) {
+ console.log('caught', file);
+
+ if(Cakewalker.COFFEE_PATTERN.test(file)) {
+ self.builders.push(new CoffeeBuilder(file));
+ } else if(Cakewalker.STYLUS_PATTERN.test(file)) {
+ self.builders.push(new StylusBuilder(file));
+ } else if(Cakewalker.JADE_PATTERN.test(file)) {
+ self.builders.push(new JadeBuilder(file));
+ }
+
+ }
+ }
+
+ if(self.builders.length === 0) {
+ console.error('No CoffeeScript, Stylus, or Jade files found. Exiting.');
+ process.exit();
+ }
+
+ });
+
+ },
+
+ /**
+ * Stop watching and building.
+ */
+ stop: function() {
+ for(var i = 0; i < builders.length; i++) {
+ builders[i].stop();
+ }
+ }
+
+}
+
+
+exports.Cakewalker = Cakewalker;
@@ -0,0 +1,38 @@
+var colors = require('colors');
+
+
+function BuildError() {}
+
+BuildError.prototype = {
+ printError: function() {
+ var leadin = this.type + ' error in ' + this.sourceFile + ': ';
+ console.log(leadin.red + '\n', this.message);
+ }
+}
+
+
+exports.CoffeeScriptError = function(message, sourceFile) {
+ this.message = message;
+ this.type = 'CoffeeScript';
+ this.sourceFile = sourceFile;
+}
+
+exports.CoffeeScriptError.prototype = new BuildError();
+
+
+exports.StylusError = function(message, sourceFile) {
+ this.message = message;
+ this.type = 'Stylus';
+ this.sourceFile = sourceFile;
+}
+
+exports.StylusError.prototype = new BuildError();
+
+
+exports.JadeError = function(message, sourceFile) {
+ this.message = message;
+ this.type = 'Jade';
+ this.sourceFile = sourceFile;
+}
+
+exports.JadeError.prototype = new BuildError();
@@ -0,0 +1,89 @@
+var fs = require('fs');
+var execFile = require('child_process').execFile;
+
+
+/**
+ * Walk a directory in a paralell loop.
+ * http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
+ *
+ * Usage:
+ * > walk('./path/to/directory', callbackFunction);
+ *
+ */
+function walk(dir, done) {
+ var results = [];
+ fs.readdir(dir, function(err, list) {
+ if (err) return done(err);
+ var pending = list.length;
+ if (!pending) return done(null, results);
+ list.forEach(function(file) {
+ file = dir + '/' + file;
+ fs.stat(file, function(err, stat) {
+ if (stat && stat.isDirectory()) {
+ walk(file, function(err, res) {
+ results = results.concat(res);
+ if (!--pending) done(null, results);
+ });
+ } else {
+ results.push(file);
+ if (!--pending) done(null, results);
+ }
+ });
+ });
+ });
+}
+
+
+/**
+ * Walk a directory in a serial loop.
+ * http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
+ *
+ * Usage:
+ * > walk('./path/to/directory', callbackFunction);
+ *
+ */
+function walkSerial(dir, done) {
+ var results = [];
+ fs.readdir(dir, function(err, list) {
+ if (err) return done(err);
+ var i = 0;
+ (function next() {
+ var file = list[i++];
+ if (!file) return done(null, results);
+ file = dir + '/' + file;
+ fs.stat(file, function(err, stat) {
+ if (stat && stat.isDirectory()) {
+ walk(file, function(err, res) {
+ results = results.concat(res);
+ next();
+ });
+ } else {
+ results.push(file);
+ next();
+ }
+ });
+ })();
+ });
+}
+
+
+/**
+ * Wrapper for the UNIX find function. Possibly faster than the walk()
+ * function above, particularly when multiple similar find commands are called
+ * due to the find tool's built-in caching.
+ * http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
+ *
+ * Usage:
+ * > find('./path/to/directory', callbackFunction);
+ *
+ */
+function find(path, callback) {
+ execFile('find', [path], function(err, stdout, stderr) {
+ var fileList = stdout.split('\n');
+ callback(err, fileList);
+ });
+}
+
+
+exports.walk = walk;
+exports.find = find;
Oops, something went wrong.

0 comments on commit c7c09e3

Please sign in to comment.