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 ea9a37f5302c5338076f2b6919da71471b8513f8 0 parents
@sstephenson authored
14 Cakefile
@@ -0,0 +1,14 @@
+require.paths.push __dirname + '/lib'
+fs = require 'fs'
+
+task 'build', 'Build lib/ from src/', ->
+ CoffeeScript = require 'coffee-script'
+ source = fs.readFileSync __dirname + '/src/stitch.coffee'
+ output = CoffeeScript.compile source.toString()
+ fs.writeFileSync __dirname + '/lib/stitch.js', output
+
+task 'test', 'Run tests', ->
+ invoke 'build'
+ {testrunner} = require 'nodeunit'
+ process.chdir __dirname
+ testrunner.run ['test']
21 LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2010 Joshua Peek
+Copyright (c) 2010 Sam Stephenson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6 README.md
@@ -0,0 +1,6 @@
+ __ ___ ___ __
+ (_ | | | / |__|
+ __) | | | \__ | |
+
+Develop and test your JavaScript applications as CommonJS modules in
+Node.js. Then __Stitch__ them together to run in the browser.
260 lib/stitch.js
@@ -0,0 +1,260 @@
+(function() {
+ var _a, compileFile, compilerIsAvailableFor, defaultCompilers, expandPaths, extend, extname, forEachAsync, fs, gatherSource, gatherSources, gatherSourcesFromPath, getCompilersFrom, getFilesInTree, getRelativePath, join, merge, normalize, stripExtension, sys, walkTree;
+ var __hasProp = Object.prototype.hasOwnProperty, __slice = Array.prototype.slice;
+ fs = require('fs');
+ sys = require('sys');
+ _a = require('path');
+ extname = _a.extname;
+ join = _a.join;
+ normalize = _a.normalize;
+ defaultCompilers = {
+ js: function(source) {
+ return source;
+ }
+ };
+ try {
+ defaultCompilers.coffee = require('coffee-script').compile;
+ } catch (err) {
+
+ }
+ extend = function(destination, source) {
+ var _b, key, value;
+ _b = source;
+ for (key in _b) {
+ if (!__hasProp.call(_b, key)) continue;
+ value = _b[key];
+ destination[key] = value;
+ }
+ return destination;
+ };
+ merge = function() {
+ var _b, _c, _d, object, objects, result;
+ objects = __slice.call(arguments, 0);
+ result = {};
+ _c = objects;
+ for (_b = 0, _d = _c.length; _b < _d; _b++) {
+ object = _c[_b];
+ if (object) {
+ extend(result, object);
+ }
+ }
+ return result;
+ };
+ forEachAsync = function(elements, callback) {
+ var _b, _c, _d, _e, element, next, remainingCount;
+ remainingCount = elements.length;
+ next = function() {
+ remainingCount--;
+ return remainingCount <= 0 ? callback(false, null) : null;
+ };
+ _b = []; _d = elements;
+ for (_c = 0, _e = _d.length; _c < _e; _c++) {
+ element = _d[_c];
+ _b.push(callback(next, element));
+ }
+ return _b;
+ };
+ exports.walkTree = (walkTree = function(directory, callback) {
+ return fs.readdir(directory, function(err, files) {
+ if (err) {
+ return callback(err);
+ }
+ return forEachAsync(files, function(next, file) {
+ var filename;
+ if (next) {
+ filename = join(directory, file);
+ return fs.stat(filename, function(err, stats) {
+ if (stats.isDirectory()) {
+ return walkTree(filename, function(err, filename) {
+ return filename ? callback(err, filename) : next();
+ });
+ } else {
+ callback(err, filename);
+ return next();
+ }
+ });
+ } else {
+ return callback(err, null);
+ }
+ });
+ });
+ });
+ exports.getFilesInTree = (getFilesInTree = function(directory, callback) {
+ var files;
+ files = [];
+ return walkTree(directory, function(err, filename) {
+ if (err) {
+ return callback(err);
+ } else if (filename) {
+ return files.push(filename);
+ } else {
+ return callback(err, files);
+ }
+ });
+ });
+ getCompilersFrom = function(options) {
+ return merge(defaultCompilers, options.compilers);
+ };
+ compilerIsAvailableFor = function(filename, options) {
+ var _b, _c, _d, extension, name;
+ _c = Object.keys(getCompilersFrom(options));
+ for (_b = 0, _d = _c.length; _b < _d; _b++) {
+ name = _c[_b];
+ extension = extname(filename).slice(1);
+ if (name === extension) {
+ return true;
+ }
+ }
+ return false;
+ };
+ exports.compileFile = (compileFile = function(path, options, callback) {
+ var compilers, extension;
+ compilers = getCompilersFrom(options);
+ extension = extname(path).slice(1);
+ return fs.readFile(path, function(err, contents) {
+ var compile, source;
+ if (err) {
+ return callback(err);
+ } else {
+ source = contents.toString();
+ if (compile = compilers[extension]) {
+ try {
+ return callback(false, compile(source));
+ } catch (err) {
+ return callback(err);
+ }
+ } else {
+ return callback("no compiler for '." + (extension) + "' files");
+ }
+ }
+ });
+ });
+ exports.expandPaths = (expandPaths = function(sourcePaths, callback) {
+ var paths;
+ paths = [];
+ return forEachAsync(sourcePaths, function(next, sourcePath) {
+ return next ? fs.realpath(sourcePath, function(err, path) {
+ if (err) {
+ callback(err);
+ } else {
+ paths.push(normalize(path));
+ }
+ return next();
+ }) : callback(null, paths);
+ });
+ });
+ exports.getRelativePath = (getRelativePath = function(requirePaths, path, callback) {
+ path = normalize(path);
+ return expandPaths(requirePaths, function(err, expandedPaths) {
+ if (err) {
+ return callback(err);
+ }
+ return fs.realpath(path, function(err, path) {
+ var _b, _c, _d, base, expandedPath;
+ if (err) {
+ return callback(err);
+ }
+ _c = expandedPaths;
+ for (_b = 0, _d = _c.length; _b < _d; _b++) {
+ expandedPath = _c[_b];
+ base = expandedPath + "/";
+ if (path.indexOf(base) === 0) {
+ return callback(false, path.slice(base.length));
+ }
+ }
+ return callback("" + (path) + " isn't in the require path");
+ });
+ });
+ });
+ exports.stripExtension = (stripExtension = function(filename) {
+ var extension;
+ extension = extname(filename);
+ return filename.slice(0, -extension.length);
+ });
+ gatherSource = function(path, options, callback) {
+ return getRelativePath(options.requirePaths, path, function(err, relativePath) {
+ return err ? callback(err) : compileFile(path, options, function(err, source) {
+ return err ? callback(err) : callback(err, stripExtension(relativePath), {
+ filename: relativePath,
+ source: source
+ });
+ });
+ });
+ };
+ gatherSourcesFromPath = function(sourcePath, options, callback) {
+ return fs.stat(sourcePath, function(err, stat) {
+ var sources;
+ if (err) {
+ return callback(err);
+ }
+ sources = {};
+ return stat.isDirectory() ? getFilesInTree(sourcePath, function(err, paths) {
+ return err ? callback(err) : forEachAsync(paths, function(next, path) {
+ return next ? (compilerIsAvailableFor(path, options) ? gatherSource(path, options, function(err, key, value) {
+ if (err) {
+ callback(err);
+ } else {
+ sources[key] = value;
+ }
+ return next();
+ }) : next()) : callback(null, sources);
+ });
+ }) : gatherSource(sourcePath, options, function(err, key, value) {
+ if (err) {
+ callback(err);
+ } else {
+ sources[key] = value;
+ }
+ return callback(false, sources);
+ });
+ });
+ };
+ exports.gatherSources = (gatherSources = function(options, callback) {
+ var _b, sourcePaths, sources;
+ _b = options;
+ sourcePaths = _b.sourcePaths;
+ sources = {};
+ return forEachAsync(sourcePaths, function(next, sourcePath) {
+ return next ? gatherSourcesFromPath(sourcePath, options, function(err, pathSources) {
+ var _c, key, value;
+ if (err) {
+ callback(err);
+ } else {
+ _c = pathSources;
+ for (key in _c) {
+ if (!__hasProp.call(_c, key)) continue;
+ value = _c[key];
+ sources[key] = value;
+ }
+ }
+ return next();
+ }) : callback(null, sources);
+ });
+ });
+ exports.stitch = function(options, callback) {
+ options.identifier = (typeof options.identifier !== "undefined" && options.identifier !== null) ? options.identifier : 'require';
+ options.sourcePaths = (typeof options.sourcePaths !== "undefined" && options.sourcePaths !== null) ? options.sourcePaths : ['lib'];
+ options.requirePaths = (typeof options.requirePaths !== "undefined" && options.requirePaths !== null) ? options.requirePaths : ['lib'];
+ return gatherSources(options, function(err, sources) {
+ var _b, _c, filename, index, name, result, source;
+ if (err) {
+ return callback(err);
+ } else {
+ result = ("var " + (options.identifier) + " = (function(modules) {\n return function require(name) {\n var fn = modules[name], module;\n if (fn) {\n module = { id: name, exports: {} };\n fn(module.exports, require, module);\n return module.exports;\n } else {\n throw 'module \\'' + name + '\\' not found';\n }\n }\n})({");
+ index = 0;
+ _b = sources;
+ for (name in _b) {
+ if (!__hasProp.call(_b, name)) continue;
+ _c = _b[name];
+ filename = _c.filename;
+ source = _c.source;
+ result += (index++ === 0 ? "" : ", ");
+ result += sys.inspect(name);
+ result += (": function(exports, require, module) {" + (source) + "}");
+ }
+ result += "});\n";
+ return callback(false, result);
+ }
+ });
+ };
+})();
211 src/stitch.coffee
@@ -0,0 +1,211 @@
+fs = require 'fs'
+sys = require 'sys'
+{extname, join, normalize} = require 'path'
+
+defaultCompilers =
+ js: (source) -> source
+
+try
+ defaultCompilers.coffee = require('coffee-script').compile
+catch err
+
+extend = (destination, source) ->
+ for key, value of source
+ destination[key] = value
+ destination
+
+merge = (objects...) ->
+ result = {}
+ for object in objects
+ extend result, object if object
+ result
+
+forEachAsync = (elements, callback) ->
+ remainingCount = elements.length
+
+ next = () ->
+ remainingCount--
+ if remainingCount <= 0
+ callback false, null
+
+ for element in elements
+ callback next, element
+
+exports.walkTree = walkTree = (directory, callback) ->
+ fs.readdir directory, (err, files) ->
+ if err then return callback err
+
+ forEachAsync files, (next, file) ->
+ if next
+ filename = join directory, file
+ fs.stat filename, (err, stats) ->
+ if stats.isDirectory()
+ walkTree filename, (err, filename) ->
+ if filename
+ callback err, filename
+ else
+ next()
+ else
+ callback err, filename
+ next()
+ else
+ callback err, null
+
+exports.getFilesInTree = getFilesInTree = (directory, callback) ->
+ files = []
+ walkTree directory, (err, filename) ->
+ if err
+ callback err
+ else if filename
+ files.push filename
+ else
+ callback err, files
+
+getCompilersFrom = (options) ->
+ merge defaultCompilers, options.compilers
+
+compilerIsAvailableFor = (filename, options) ->
+ for name in Object.keys getCompilersFrom options
+ extension = extname(filename).slice(1)
+ return true if name is extension
+ false
+
+exports.compileFile = compileFile = (path, options, callback) ->
+ compilers = getCompilersFrom options
+ extension = extname(path).slice(1)
+
+ fs.readFile path, (err, contents) ->
+ if err
+ callback err
+ else
+ source = contents.toString()
+ if compile = compilers[extension]
+ try
+ callback false, compile source
+ catch err
+ callback err
+ else
+ callback "no compiler for '.#{extension}' files"
+
+exports.expandPaths = expandPaths = (sourcePaths, callback) ->
+ paths = []
+
+ forEachAsync sourcePaths, (next, sourcePath) ->
+ if next
+ fs.realpath sourcePath, (err, path) ->
+ if err
+ callback err
+ else
+ paths.push normalize path
+ next()
+ else
+ callback null, paths
+
+exports.getRelativePath = getRelativePath = (requirePaths, path, callback) ->
+ path = normalize path
+
+ expandPaths requirePaths, (err, expandedPaths) ->
+ return callback err if err
+
+ fs.realpath path, (err, path) ->
+ return callback err if err
+
+ for expandedPath in expandedPaths
+ base = expandedPath + "/"
+ if path.indexOf(base) is 0
+ return callback false, path.slice base.length
+
+ callback "#{path} isn't in the require path"
+
+exports.stripExtension = stripExtension = (filename) ->
+ extension = extname filename
+ filename.slice 0, -extension.length
+
+gatherSource = (path, options, callback) ->
+ getRelativePath options.requirePaths, path, (err, relativePath) ->
+ if err then callback err
+ else
+ compileFile path, options, (err, source) ->
+ if err then callback err
+ else
+ callback err, stripExtension(relativePath),
+ filename: relativePath
+ source: source
+
+gatherSourcesFromPath = (sourcePath, options, callback) ->
+ fs.stat sourcePath, (err, stat) ->
+ if err then return callback err
+
+ sources = {}
+
+ if stat.isDirectory()
+ getFilesInTree sourcePath, (err, paths) ->
+ if err then callback err
+ else
+ forEachAsync paths, (next, path) ->
+ if next
+ if compilerIsAvailableFor path, options
+ gatherSource path, options, (err, key, value) ->
+ if err then callback err
+ else sources[key] = value
+ next()
+ else
+ next()
+ else
+ callback null, sources
+ else
+ gatherSource sourcePath, options, (err, key, value) ->
+ if err then callback err
+ else sources[key] = value
+ callback false, sources
+
+exports.gatherSources = gatherSources = (options, callback) ->
+ {sourcePaths} = options
+ sources = {}
+
+ forEachAsync sourcePaths, (next, sourcePath) ->
+ if next
+ gatherSourcesFromPath sourcePath, options, (err, pathSources) ->
+ if err then callback err
+ else
+ for key, value of pathSources
+ sources[key] = value
+ next()
+ else
+ callback null, sources
+
+exports.stitch = (options, callback) ->
+ options.identifier ?= 'require'
+ options.sourcePaths ?= ['lib']
+ options.requirePaths ?= ['lib']
+
+ gatherSources options, (err, sources) ->
+ if err
+ callback err
+ else
+ result = """
+ var #{options.identifier} = (function(modules) {
+ return function require(name) {
+ var fn = modules[name], module;
+ if (fn) {
+ module = { id: name, exports: {} };
+ fn(module.exports, require, module);
+ return module.exports;
+ } else {
+ throw 'module \\'' + name + '\\' not found';
+ }
+ }
+ })({
+ """
+
+ index = 0
+ for name, {filename, source} of sources
+ result += if index++ is 0 then "" else ", "
+ result += sys.inspect name
+ result += ": function(exports, require, module) {#{source}}"
+
+ result += """
+ });\n
+ """
+
+ callback false, result
1  test/fixtures/alternate/hello.alert
@@ -0,0 +1 @@
+hello world
3  test/fixtures/alternate/nonsense.coffee
@@ -0,0 +1,3 @@
+# This is some nonsense that should throw a parse error
+
+class for when if void
3  test/fixtures/default/custom_exports.js
@@ -0,0 +1,3 @@
+module.exports = function() {
+ return "foo";
+}
1  test/fixtures/default/exported_property.coffee
@@ -0,0 +1 @@
+exports.foo = "bar"
1  test/fixtures/default/foo.coffee
@@ -0,0 +1 @@
+foo = "bar"
0  test/fixtures/default/foo.unknown
No changes.
1  test/fixtures/default/foo/bar.coffee
@@ -0,0 +1 @@
+bar = "baz"
1  test/fixtures/default/foo/bar/baz.js
@@ -0,0 +1 @@
+var baz = "biz";
1  test/fixtures/default/foo/baz.coffee
@@ -0,0 +1 @@
+baz = "biz"
154 test/test_stitch.coffee
@@ -0,0 +1,154 @@
+sys = require "sys"
+stitch = require "stitch"
+
+fixtureRoot = __dirname + "/fixtures"
+fixtures = fixtureRoot + "/default"
+altFixtures = fixtureRoot + "/alternate"
+fixtureCount = 7
+
+defaultOptions =
+ identifier: "testRequire"
+ sourcePaths: [fixtures]
+ requirePaths: [fixtures]
+
+alternateOptions =
+ sourcePaths: [altFixtures]
+ requirePaths: [altFixtures]
+
+exports.testWalkTree = (test) ->
+ test.expect fixtureCount
+
+ stitch.walkTree fixtures, (err, file) ->
+ if file
+ test.ok file
+ else
+ test.done()
+
+exports.testGetFilesInTree = (test) ->
+ test.expect 2
+
+ stitch.getFilesInTree fixtures, (err, files) ->
+ test.ok !err
+ test.same fixtureCount, files.length
+ test.done()
+
+exports.testGetFilesInTreeDoesNotExist = (test) ->
+ test.expect 1
+
+ stitch.getFilesInTree fixtures + "/missing", (err, files) ->
+ test.ok err
+ test.done()
+
+exports.testCompileFile = (test) ->
+ test.expect 2
+
+ stitch.compileFile __filename, defaultOptions, (err, source) ->
+ test.ok !err
+ test.ok source.match(/\(function\(\) \{/)
+ test.done()
+
+exports.testCompileFileDoesNotExist = (test) ->
+ test.expect 1
+
+ stitch.compileFile "nosuchthing.coffee", defaultOptions, (err, source) ->
+ test.ok err
+ test.done()
+
+exports.testCompileFileWithSyntaxError = (test) ->
+ test.expect 1
+
+ stitch.compileFile altFixtures + "/nonsense.coffee", alternateOptions, (err, source) ->
+ test.ok err.toString().match(/SyntaxError/)
+ test.done()
+
+exports.testCompileFileWithCustomCompiler = (test) ->
+ test.expect 1
+
+ options = Object.create alternateOptions
+ options.compilers =
+ alert: (source) -> "alert(#{sys.inspect source});"
+
+ stitch.compileFile altFixtures + "/hello.alert", options, (err, source) ->
+ test.same "alert('hello world\\n');", source
+ test.done()
+
+exports.testCompileFileWithUnknownExtension = (test) ->
+ test.expect 1
+
+ stitch.compileFile altFixtures + "/hello.alert", alternateOptions, (err, source) ->
+ test.ok err.toString().match(/no compiler/)
+ test.done()
+
+exports.testExpandPaths = (test) ->
+ test.expect 3
+
+ stitch.expandPaths [__dirname + "/../test/fixtures/default"], (err, expandedPaths) ->
+ test.ok !err
+ test.ok expandedPaths
+ test.same [fixtures], expandedPaths
+ test.done()
+
+exports.testGetRelativePath = (test) ->
+ test.expect 2
+
+ stitch.getRelativePath [fixtures], fixtures + "/foo/bar.coffee", (err, path) ->
+ test.ok !err
+ test.same 'foo/bar.coffee', path
+ test.done()
+
+exports.testStripExtension = (test) ->
+ test.expect 1
+
+ filename = stitch.stripExtension "foo.coffee"
+ test.same "foo", filename
+
+ test.done()
+
+exports.testGatherSources = (test) ->
+ test.expect 3
+
+ stitch.gatherSources defaultOptions, (err, sources) ->
+ test.ok !err
+ test.same "foo.coffee", sources.foo.filename
+ test.ok sources.foo.source
+ test.done()
+
+exports.testGatherSourcesCanIncludeIndividualFile = (test) ->
+ test.expect 4
+
+ options = Object.create defaultOptions
+ options.sourcePaths = [fixtures + "/foo.coffee"]
+
+ stitch.gatherSources options, (err, sources) ->
+ test.ok !err
+ test.same 1, Object.keys(sources).length
+ test.same "foo.coffee", sources.foo.filename
+ test.ok sources.foo.source
+ test.done()
+
+exports.testStitchGeneratesValidJavaScript = (test) ->
+ test.expect 2
+
+ stitch.stitch defaultOptions, (err, sources) ->
+ test.ok !err
+ eval sources
+ test.ok typeof testRequire is "function"
+ test.done()
+
+exports.testStitchModuleWithCustomExports = (test) ->
+ test.expect 2
+
+ stitch.stitch defaultOptions, (err, sources) ->
+ eval sources
+ result = testRequire("custom_exports")
+ test.ok typeof result is "function"
+ test.same "foo", result()
+ test.done()
+
+exports.testStitchModuleWithExportedProperty = (test) ->
+ test.expect 1
+
+ stitch.stitch defaultOptions, (err, sources) ->
+ eval sources
+ test.same "bar", testRequire("exported_property").foo
+ test.done()
Please sign in to comment.
Something went wrong with that request. Please try again.