Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit b5f3748177ff921d603f53e5cfdeff6b1f7f5ebf 0 parents
@airhorns authored
1  .gitignore
@@ -0,0 +1 @@
+node_modules
15 Cakefile
@@ -0,0 +1,15 @@
+muffin = require './lib/muffin'
+
+option '-w', '--watch', 'continue to watch the files and rebuild them when they change'
+option '-c', '--commit', 'operate on the git index instead of the working tree'
+
+task 'build', 'compile muffin', (options) ->
+ muffin.run
+ files: './src/**/*'
+ options: options
+ map:
+ 'src/muffin.coffee' : (matches) -> muffin.compileScript(matches[0], 'lib/muffin.js', options)
+ console.log "Watching src..." if options.watch
+
+task 'test', 'test', (options) ->
+ muffin.writeFile('test.js', '123', {})
268 lib/muffin.js
@@ -0,0 +1,268 @@
+(function() {
+ var CoffeeScript, compileMap, compileScript, copyFile, doccoFile, exec, extend, fs, glob, handleFileError, inRebase, k, minifyScript, notify, ofs, orgExec, path, q, readFile, run, spawn, v, writeFile, _ref, _ref2;
+ CoffeeScript = require('coffee-script');
+ q = require('q');
+ fs = require('q-fs');
+ ofs = require('fs');
+ path = require('path');
+ glob = require('glob');
+ _ref = require('child_process'), spawn = _ref.spawn, exec = _ref.exec;
+ orgExec = exec;
+ extend = function(onto, other) {
+ var k, o, result, v, _i, _len, _ref2;
+ result = onto;
+ _ref2 = [this, other];
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ o = _ref2[_i];
+ for (k in o) {
+ v = o[k];
+ result[k] = v;
+ }
+ }
+ return result;
+ };
+ exec = function(command, options) {
+ var child, deferred;
+ if (options == null) {
+ options = {};
+ }
+ deferred = q.defer();
+ child = orgExec(command, options, function(error, stdout, stderr) {
+ if (error != null) {
+ return deferred.reject(error);
+ } else {
+ return deferred.resolve([stdout, stderr]);
+ }
+ });
+ return [child, deferred.promise];
+ };
+ inRebase = function() {
+ return path.existsSync('.git/rebase-apply');
+ };
+ readFile = function(file, options) {
+ var deferred;
+ if (options == null) {
+ options = {};
+ }
+ deferred = q.defer();
+ if (options.commit) {
+ exec("git show :" + file, function(err, stdout, stderr) {
+ if (err != null) {
+ return handleFileError(file, err, options);
+ } else {
+ return deffered.resolve(stdout);
+ }
+ });
+ } else {
+ fs.read(file).then(function(contents) {
+ return deferred.resolve(contents);
+ }, function(error) {
+ return handleFileError(file, err, options);
+ });
+ }
+ return deferred.promise;
+ };
+ writeFile = function(file, data, options) {
+ var child, mode, promise, _ref2;
+ if (options == null) {
+ options = {};
+ }
+ mode = options.mode || 644;
+ if (options.commit) {
+ _ref2 = exec("git hash-object --stdin -w"), child = _ref2[0], promise = _ref2[1];
+ child.stdin.write(data);
+ child.stdin.end();
+ promise.then(function(_arg) {
+ var sha, stderr, stdout, subchild, subpromise, _ref3;
+ stdout = _arg[0], stderr = _arg[1];
+ sha = stdout.substr(0, 40);
+ _ref3 = exec("git update-index --add --cacheinfo 100" + (mode.toString(8)) + " " + sha + " " + file), subchild = _ref3[0], subpromise = _ref3[1];
+ return subpromise;
+ });
+ return promise;
+ } else {
+ return fs.write(file, data.toString(), "w", "UTF-8").then(function(data) {
+ return fs.chmod(file, mode);
+ }, function(reason) {
+ if (reason.toString().match(/not writable/g)) {
+ return q.reject("" + file + " isn't writable, please check permissions!");
+ } else {
+ return q.reject(reason);
+ }
+ });
+ }
+ };
+ handleFileError = function(file, err, options) {
+ if (options == null) {
+ options = {};
+ }
+ return ref(options.notify !== false ? notify(file, err.message, true) : void 0);
+ };
+ compileScript = function(source, target, options) {
+ if (options == null) {
+ options = {};
+ }
+ return readFile(source, options).then(function(data) {
+ var js;
+ try {
+ js = CoffeeScript.compile(data, {
+ source: source,
+ bare: options != null ? options.bare : void 0
+ });
+ return writeFile(target, js, options).then(function() {
+ if (options.notify !== false) {
+ return notify(source, "Compiled " + source + " to " + target + " successfully");
+ }
+ });
+ } catch (err) {
+ return handleFileError(target, err, options);
+ }
+ });
+ };
+ notify = function(source, origMessage, error) {
+ var args, basename, child, m, message, promise, _ref2;
+ if (error == null) {
+ error = false;
+ }
+ if (error) {
+ basename = source.replace(/^.*[\/\\]/, '');
+ if (m = origMessage.match(/Parse error on line (\d+)/)) {
+ message = "Parse error in " + basename + "\non line " + m[1] + ".";
+ } else {
+ message = "Error in " + basename + ".";
+ }
+ args = ['growlnotify', '-n', 'Cake', '-p', '2', '-t', "\"Action failed\"", '-m', "\"" + message + "\""];
+ console.error(message);
+ console.error(origMessage);
+ } else {
+ args = ['growlnotify', '-n', 'Cake', '-p', '-1', '-t', "\"Action Succeeded\"", '-m', "\"" + source + "\""];
+ console.log(origMessage);
+ }
+ _ref2 = exec(args.join(' ')), child = _ref2[0], promise = _ref2[1];
+ return promise;
+ };
+ copyFile = function(source, target, options) {
+ if (options == null) {
+ options = {};
+ }
+ return readFile(source, options).then(function(contents) {
+ return writeFile(target, contents, options).then(function() {
+ return notify(source, "Moved " + source + " to " + target + " successfully");
+ });
+ });
+ };
+ doccoFile = function(source, options) {
+ var child, promise, _ref2;
+ if (options == null) {
+ options = {};
+ }
+ _ref2 = exec("docco " + source), child = _ref2[0], promise = _ref2[1];
+ return promise.then(function(_arg) {
+ var stderr, stdout;
+ stdout = _arg[0], stderr = _arg[1];
+ if (stdout.toString().length > 0) {
+ notify(source, stdout.toString());
+ }
+ if (stderr.toString().length > 0) {
+ return notify(source, stderr.toString(), true);
+ }
+ });
+ };
+ minifyScript = function(source, options) {
+ var parser, uglify, _ref2;
+ if (options == null) {
+ options = {};
+ }
+ _ref2 = require("uglify-js"), parser = _ref2.parser, uglify = _ref2.uglify;
+ return readFile(source, options).then(function(original) {
+ var ast, final, finalPath;
+ ast = parser.parse(original);
+ ast = uglify.ast_mangle(ast);
+ ast = uglify.ast_squeeze(ast);
+ final = uglify.gen_code(ast);
+ finalPath = source.split('.');
+ finalPath.pop();
+ finalPath.push('min.js');
+ return writeFile(finalPath.join('.'), final, options);
+ });
+ };
+ compileMap = function(map) {
+ var action, pattern, _results;
+ _results = [];
+ for (pattern in map) {
+ action = map[pattern];
+ _results.push({
+ pattern: new RegExp(pattern),
+ action: action
+ });
+ }
+ return _results;
+ };
+ run = function(args) {
+ var compiledMap, done;
+ if (!(args.files != null)) {
+ args.files = glob.globSync('./**/*');
+ } else if (typeof args.files === 'string') {
+ args.files = glob.globSync(args.files);
+ }
+ compiledMap = compileMap(args.map);
+ done = compiledMap.reduce(function(done, map) {
+ var file, i, matches, work, _ref2;
+ _ref2 = args.files;
+ for (i in _ref2) {
+ file = _ref2[i];
+ if (matches = map.pattern.exec(file)) {
+ delete args.files[i];
+ work = q.ref(map.action(matches));
+ if (args.options.watch) {
+ (function(map, matches) {
+ return ofs.watchFile(file, {
+ persistent: true,
+ interval: 250
+ }, function(curr, prev) {
+ if (curr.mtime.getTime() === prev.mtime.getTime()) {
+ return;
+ }
+ if (inRebase()) {
+ return;
+ }
+ work = q.ref(map.action(matches));
+ return q.when(work, function(result) {
+ if (args.after) {
+ return args.after();
+ }
+ });
+ });
+ })(map, matches);
+ }
+ done = q.when(done, function() {
+ return work;
+ });
+ }
+ }
+ return done;
+ }, void 0);
+ q.when(done, function() {
+ if (args.after) {
+ return args.after();
+ }
+ });
+ return done.end();
+ };
+ _ref2 = {
+ run: run,
+ copyFile: copyFile,
+ doccoFile: doccoFile,
+ notify: notify,
+ minifyScript: minifyScript,
+ readFile: readFile,
+ writeFile: writeFile,
+ compileScript: compileScript,
+ exec: exec,
+ extend: extend
+ };
+ for (k in _ref2) {
+ v = _ref2[k];
+ exports[k] = v;
+ }
+}).call(this);
29 package.json
@@ -0,0 +1,29 @@
+{
+ "author": "Harry Brundage <harry.brundage@jadedpixel.com> (http://shopify.com/)",
+ "name": "muffin",
+ "description": "Handy helpers for Cakefiles",
+ "version": "0.1.0",
+ "homepage": "http://github.com/hornairs/muffin",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/hornairs/muffin.git"
+ },
+ "main": "lib/muffin.js",
+ "scripts": {
+ "test": "cake test"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "dependencies": {
+ "q": "~>0.6.0",
+ "q-fs": "~>0.1.9",
+ "docco": "~0.3.0",
+ "glob": "~2.0",
+ "coffee-script": "~>1.1.1",
+ "uglify-js": "1.0.3"
+ },
+ "devDependencies": {
+ "qunit": "~0.1.5"
+ }
+}
169 src/muffin.coffee
@@ -0,0 +1,169 @@
+# Copyright Shopify, 2011
+CoffeeScript = require 'coffee-script'
+q = require 'q'
+fs = require 'q-fs'
+ofs = require 'fs'
+path = require 'path'
+glob = require 'glob'
+{spawn, exec} = require 'child_process'
+orgExec = exec
+
+extend = (onto, other) ->
+ result = onto
+ for o in [@,other]
+ for k,v of o
+ result[k] = v
+ result
+
+exec = (command, options = {}) ->
+ deferred = q.defer()
+ child = orgExec(command, options, (error, stdout, stderr) ->
+ if error?
+ deferred.reject(error)
+ else
+ deferred.resolve([stdout, stderr])
+ )
+
+ [child, deferred.promise]
+
+inRebase = ->
+ path.existsSync('.git/rebase-apply')
+
+readFile = (file, options = {}) ->
+ deferred = q.defer()
+
+ if options.commit
+ exec "git show :"+file, (err, stdout, stderr) ->
+ if err?
+ handleFileError file, err, options
+ else
+ deffered.resolve stdout
+ else
+ fs.read(file).then((contents) ->
+ deferred.resolve(contents)
+ , (error) ->
+ handleFileError(file, err, options)
+ )
+
+ deferred.promise
+
+writeFile = (file, data, options = {}) ->
+ mode = options.mode || 644
+
+ if options.commit
+ [child, promise] = exec "git hash-object --stdin -w"
+ child.stdin.write(data)
+ child.stdin.end()
+
+ promise.then ([stdout, stderr]) ->
+ sha = stdout.substr(0,40)
+ [subchild, subpromise] = exec "git update-index --add --cacheinfo 100#{mode.toString(8)} #{sha} #{file}"
+ return subpromise
+
+ return promise
+ else
+ fs.write(file, data.toString(), "w", "UTF-8").then (data) ->
+ return fs.chmod file, mode
+ , (reason) ->
+ if reason.toString().match(/not writable/g)
+ q.reject "#{file} isn't writable, please check permissions!"
+ else
+ q.reject(reason)
+
+
+handleFileError = (file, err, options = {}) ->
+ ref(notify file, err.message, true unless options.notify == false)
+
+# Following 2 functions are stolen from Jitter, https://github.com/TrevorBurnham/Jitter/blob/master/src/jitter.coffee
+# Compiles a script to a destination
+compileScript = (source, target, options = {}) ->
+ readFile(source, options).then (data) ->
+ try
+ js = CoffeeScript.compile data, {source, bare: options?.bare}
+ writeFile(target, js, options).then ->
+ notify source, "Compiled #{source} to #{target} successfully" unless options.notify == false
+ catch err
+ handleFileError target, err, options
+
+# Notifies the user of a success or error during compilation
+notify = (source, origMessage, error = false) ->
+ if error
+ basename = source.replace(/^.*[\/\\]/, '')
+ if m = origMessage.match /Parse error on line (\d+)/
+ message = "Parse error in #{basename}\non line #{m[1]}."
+ else
+ message = "Error in #{basename}."
+ args = ['growlnotify', '-n', 'Cake', '-p', '2', '-t', "\"Action failed\"", '-m', "\"#{message}\""]
+ console.error message
+ console.error origMessage
+ else
+ args = ['growlnotify', '-n', 'Cake', '-p', '-1', '-t', "\"Action Succeeded\"", '-m', "\"#{source}\""]
+ console.log origMessage
+ [child, promise] = exec args.join(' ')
+ promise
+
+copyFile = (source, target, options = {}) ->
+ readFile(source, options).then (contents) ->
+ writeFile(target, contents, options).then ->
+ notify source, "Moved #{source} to #{target} successfully"
+
+doccoFile = (source, options = {}) ->
+ [child, promise] = exec("docco #{source}")
+ return promise.then ([stdout, stderr]) ->
+ notify source, stdout.toString() if stdout.toString().length > 0
+ notify source, stderr.toString(), true if stderr.toString().length > 0
+
+minifyScript = (source, options = {}) ->
+ {parser, uglify} = require("uglify-js")
+
+ readFile(source, options).then (original) ->
+ ast = parser.parse(original) # parse code and get the initial AST
+ ast = uglify.ast_mangle(ast) # get a new AST with mangled names
+ ast = uglify.ast_squeeze(ast) # get an AST with compression optimizations
+ final = uglify.gen_code(ast) # compressed code here
+ finalPath = source.split('.')
+ finalPath.pop()
+ finalPath.push('min.js')
+ return writeFile(finalPath.join('.'), final, options)
+
+compileMap = (map) ->
+ for pattern, action of map
+ {pattern: new RegExp(pattern), action: action}
+
+run = (args) ->
+ # Grab the glob if not given
+ if !args.files?
+ args.files = glob.globSync './**/*'
+ else if typeof args.files is 'string'
+ args.files = glob.globSync args.files
+
+ compiledMap = compileMap args.map
+
+ done = compiledMap.reduce (done, map) ->
+ for i, file of args.files
+ if matches = map.pattern.exec(file)
+ delete args.files[i]
+ # Do the job and wrap it in a promise
+ work = q.ref map.action(matches)
+
+ # Watch the file if the option was given
+ if args.options.watch
+ do (map, matches) ->
+ ofs.watchFile file, persistent: true, interval: 250, (curr, prev) ->
+ return if curr.mtime.getTime() is prev.mtime.getTime()
+ return if inRebase()
+ work = q.ref map.action(matches)
+ q.when work, (result) ->
+ args.after() if args.after
+
+ # Return another promise which will resolve to the work promise
+ done = q.when(done, -> work)
+ done
+ , undefined
+
+ q.when done, () ->
+ args.after() if args.after
+ done.end()
+
+for k, v of {run, copyFile, doccoFile, notify, minifyScript, readFile, writeFile, compileScript, exec, extend}
+ exports[k] = v
Please sign in to comment.
Something went wrong with that request. Please try again.