diff --git a/README.markdown b/README.markdown index 70bd8fa..583865b 100644 --- a/README.markdown +++ b/README.markdown @@ -1,16 +1,16 @@ CommonJScript ============= -CommonJScript is an implementation of the [CommonJS API](http://commonjs.org/) for Classic ASP and Windows Script Host. +CommonJScript is an implementation of the [CommonJS API](http://commonjs.org/) for Classic ASP. The [Modules/1.0](http://commonjs.org/specs/modules/1.0/) specification is currently implemented. Various others are planned for the future. -A few rudimentary modules are included in the *lib* directory. - Usage ----- -*require.js* can be included in a ASP page or WScript job to provide the `require` function. +*require.js* can be included in a ASP page or to provide the `require` function. + +Some other basic modules (system, engine, file) are also included in that file. Tests ----- @@ -20,12 +20,12 @@ The [CommonJS modules test suite](http://github.com/commonjs/commonjs/tree/maste git submodule init git submodule update -Run `cscript test.wsf` from the *test* directory for Windows Script Host (CLI), or load *test.asp* on ASP. +Load *test/index.asp* on ASP to run the tests. `debug=1` can be passed in the query string to turn `require.debug` on. Acknowledgements ---------------- -The module loading code is mostly taken from [Narwhal](http://narwhaljs.org). +Most of the code is taken directly from [Narwhal](http://narwhaljs.org) and [narwhal-lib](http://github.com/kriskowal/narwhal-lib/), so thanks to everyone working on those. License ------- diff --git a/lib/browser/xhr.js b/lib/browser/xhr.js deleted file mode 100644 index 9574c8e..0000000 --- a/lib/browser/xhr.js +++ /dev/null @@ -1,6 +0,0 @@ -// commonjscript XMLHttpRequest client -// @see http://wiki.commonjs.org/wiki/HTTP_Client/B - -exports.XMLHttpRequest = function () { - return new ActiveXObject("MSXML2.ServerXMLHTTP"); -}; diff --git a/lib/system.js b/lib/system.js deleted file mode 100644 index e461a91..0000000 --- a/lib/system.js +++ /dev/null @@ -1,26 +0,0 @@ -// commonjscript system module - -/*global Response, WScript */ - -var platform; - -exports.engine = "jscript"; - -// The platform is either wscript (cli) or asp -if (typeof WScript === "object") { - platform = "wscript"; -} else if (typeof Response === "object" && - typeof Response.write !== "undefined") { - platform = "asp"; -} else { - platform = "unknown"; -} - -// print function -exports.print = function () { - var out = Array.prototype.slice.call(arguments).join(" "); - if (platform === "wscript") { WScript.echo(out); } - else if (platform === "asp") { Response.write(out + "
"); } -}; - -exports.stdio = { print: exports.print }; diff --git a/package.json b/package.json index c39920b..974599b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "commonjscript", - "description": "An implementation of the CommonJS API for Classic ASP and Windows Script Host", - "version": "1.0.0", + "description": "An implementation of the CommonJS API for Classic ASP", + "version": "0.1.0", "maintainers": [{ "name": "Nathan L Smith", "email": "nlloyds@gmail.com", @@ -12,16 +12,21 @@ "url": "http://www.opensource.org/licenses/mit-license.php" }], "bugs": "http://github.com/smith/commonjscript/issues", - "keywords": ["engine", "jscript", "windows", "asp", "wsh", "modules"], + "keywords": ["engine", "jscript", "windows", "asp", "modules"], "repositories": [{ "type": "git", "url": "git://github.com/smith/commonjscript.git" }], - "contributors": [{ - "name": "Nathan L Smith", - "email": "nlloyds@gmail.com", - "web": "http://nlsmith.com/" - }], + "contributors": [ + { + "name": "Nathan L Smith", + "email": "nlloyds@gmail.com", + "web": "http://nlsmith.com/" + }, + { "name": "Kris Kowal" }, + { "name": "Tom Robinson" }, + { "name": "Christoph Dorn" } + ], "os": ["windows"], "engine": ["jscript"], "implements": ["CommonJS/Modules/1.0"] diff --git a/require.js b/require.js index c9df5b9..80a1820 100644 --- a/require.js +++ b/require.js @@ -1,126 +1,1089 @@ -/** - * @fileOverview An implentation of JavaScript modules for use in an ASP or - * WScript environment. This implementation is based on the one used in - * Narhwal - * - * @see ServerJS/Modules/SecurableModules - * @see Narwhal - * @author Nathan L Smith - * @date February 21, 2009 - */ +// CommonJScript for ASP -/*global ActiveXObject, Response, Server, WScript, exports, require */ +/*global require, exports, ActiveXObject, Response, Server */ /*jslint evil:true */ (function () { +// Base setup +// ============================================================================= +var modules = {}, paths = [], print, global; + // Don't do anything if require is already there if (typeof require === "function") { return; } -/** Global exports object */ -if (typeof exports === "undefined") { exports = {}; } +// ASP Only +if (typeof Server === "undefined" || typeof Response === "undefined") { + throw Error("This script only runs in ASP."); +} + +// Stand-in for require +require = function (id) { return modules[id]; }; // global + +// JScript does not support assigning properties explicitly on the global +// object or a reference to it. Some included modules try to modify this, so +// use an empty object in its place. +global = {}; + +// JScript's split method is not ECMA compliant. Replace it. +// ============================================================================= + +/* Cross-Browser Split 1.0.1 +(c) Steven Levithan ; MIT License +An ECMA-compliant, uniform cross-browser split method */ -/** A print function for use in logging */ -function print() { - var out = Array.prototype.slice.call(arguments).join(" "); - if (typeof WScript === "object") { - WScript.echo(out); - } else if (Response && typeof Response.write !== "undefined") { - Response.write(out + "
"); +var cbSplit; + +// avoid running twice, which would break `cbSplit._nativeSplit`'s reference to the native `split` +if (!cbSplit) { + +cbSplit = function (str, separator, limit) { + // if `separator` is not a regex, use the native `split` + if (Object.prototype.toString.call(separator) !== "[object RegExp]") { + return cbSplit._nativeSplit.call(str, separator, limit); } -} -//////////////////////////////////////////////////////////////////////////////// -// Internal functions for file manipulation -//////////////////////////////////////////////////////////////////////////////// + var output = [], + lastLastIndex = 0, + flags = (separator.ignoreCase ? "i" : "") + + (separator.multiline ? "m" : "") + + (separator.sticky ? "y" : ""), + separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues by working with a copy + separator2, match, lastIndex, lastLength; + + str = str + ""; // type conversion + if (!cbSplit._compliantExecNpcg) { + separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt + } + + /* behavior for `limit`: if it's... + - `undefined`: no limit. + - `NaN` or zero: return an empty array. + - a positive number: use `Math.floor(limit)`. + - a negative number: no limit. + - other: type-convert, then use the above rules. */ + if (limit === undefined || +limit < 0) { + limit = Infinity; + } else { + limit = Math.floor(+limit); + if (!limit) { + return []; + } + } + + while (match = separator.exec(str)) { + lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser + + if (lastIndex > lastLastIndex) { + output.push(str.slice(lastLastIndex, match.index)); + + // fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups + if (!cbSplit._compliantExecNpcg && match.length > 1) { + match[0].replace(separator2, function () { + for (var i = 1; i < arguments.length - 2; i++) { + if (arguments[i] === undefined) { + match[i] = undefined; + } + } + }); + } + + if (match.length > 1 && match.index < str.length) { + Array.prototype.push.apply(output, match.slice(1)); + } + + lastLength = match[0].length; + lastLastIndex = lastIndex; + + if (output.length >= limit) { + break; + } + } + + if (separator.lastIndex === match.index) { + separator.lastIndex++; // avoid an infinite loop + } + } + + if (lastLastIndex === str.length) { + if (lastLength || !separator.test("")) { + output.push(""); + } + } else { + output.push(str.slice(lastLastIndex)); + } + + return output.length > limit ? output.slice(0, limit) : output; +}; + +cbSplit._compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group +cbSplit._nativeSplit = String.prototype.split; + +} // end `if (!cbSplit)` + +// for convenience... +String.prototype.split = function (separator, limit) { + return cbSplit(this, separator, limit); +}; + +// Engine +// ============================================================================= +(function (exports) { + +exports.engine = "jscript"; +exports.debug = require.debug; + +// We're treating paths on ASP like Unix paths, so misreport the os. +// Hey, IE's been calling itself Mozilla all these years, so this is nothing. +exports.os = "asp"; + +exports.Module = function (text, path, line) { + // Return a function that takes an object, which returns another function + // that takes the inject properties + return function (inject) { + inject = inject || {}; + var names = [], values = [], result, ee; + + for (var name in inject) { + if (Object.prototype.hasOwnProperty.call(inject, name)) { + names.push(name); + values.push(inject[name]); + } + } + + // JScript's eval is not ES3 compliant, so the function needs to be + // assigned to a variable. + // @see http://www.bigresource.com/ASP-JScript-eval-bug-6nZST3Bk.html + eval("result = function (" + names.join(", ") + ") { " + text + "};"); + + try { + return result.apply(null, values); + } catch (e) { + // rethrow error with path + ee = Error(e.number, e.description + " (in " + path + ")"); + ee.name = e.name; + ee.message = e.message; + throw ee; + } + }; +}; + +})(modules.engine = {}); +modules["narwhal/engine"] = modules.engine; + +// System +// ============================================================================= +(function (exports) { + +exports.platform = "asp"; + +exports.stdout = { + print: function () { + Response.write(Array.prototype.slice.call(arguments).join(" ") + + "
"); + } +}; + +// system.stdio.print is not defined in any specs, but the Modules/1.0 test +// suite uses it +exports.stdio = { print: exports.stdout.print }; + +exports.print = exports.stdout.print; + +// Convert a JScript/VBScript collection to a plain ol object +// XXX: Not standard +exports.collectionToObject = function (c) { + var o = {}, // new object + e = new Enumerator(c), + cnt = 0, // count + item; // item + + // Iterate through collection + while (!e.atEnd()) { + item = e.item(); + // null items have no count + try { cnt = c(item).count; } + catch (er) { cnt = -1; } + + if (cnt > 1) { // multiple items + o[item] = []; + for (var i = 1; i < cnt + 1; i += 1) { + o[item].push(String(c(item)(i))); + } + } else if (cnt === 0) { o[item] = undefined; } + else { o[item] = c(item); } + + e.moveNext(); + } + + for (i in o) { + // Stringify everything but null, undefined, and array + if (Object.prototype.hasOwnProperty.call(o, i) && o[i] !== null && + typeof o[i] !== "undefined" && typeof o[i].length === "undefined") { + o[i] = String(o[i]); + } + } + + return o; +}; + +exports.env = exports.collectionToObject(Request.ServerVariables); + +})(modules.system = {}); + +print = require("system").print; // Add print function here + +// FS +// +// From narwhal-lib/lib/narwhal/fs-boot.js +// ============================================================================= +(function (exports) { +// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License +// -- tlrobinson Tom Robinson TODO /** - * An implentation of readFile, which opens a file from the filesystem and - * returns its contents as a string - * @private - */ -function readFile(fileName) { - var contents, - fileSystem = new ActiveXObject("Scripting.FileSystemObject"), - mapPath = function mapPath(path) { - var isASP = typeof Server === "object" && - typeof Server.mapPath !== "undefined"; - return isASP ? Server.mapPath(path) : path; - }; - fileName = mapPath(fileName); - - if (fileSystem.fileExists(fileName)) { - try { // JScript will throw an error if the file is empty - contents = fileSystem.openTextFile(fileName).readAll(); - } catch (e) { contents = ""; } - } else { throw new Error("File " + fileName + " does not exist"); } - return contents; -} + * Pure JavaScript implementations of file system path + * manipulation. + * + * This module depends on the non CommonJS "engine" module, + * particularly for an "os" property that has the words + * "windows" or "winnt" to distinguish Windows from Unix + * file systems. + */ + +// NOTE: this file may be used is the engine bootstrapping +// process, so any "requires" must be accounted for in +// narwhal.js + +/*whatsupdoc*/ +/*markup markdown*/ + +var ENGINE = require("engine"); + +/** + * @name ROOT + * * `/` on Unix + * * `\` on Windows + */ -function dirname(path) { - var raw = String(path), - match = raw.match(/^(.*)\/[^\/]+\/?$/); - if (match && match[1]) { return match[1]; } - else if (raw.charAt(0) == "/") { return "/"; } - else { return "."; } +/** + * @name SEPARATOR + * * `/` on Unix + * * `\` on Windows + */ + +/** + * @name ALT_SEPARATOR + * * undefined on Unix + * * `/` on Windows + */ + +if (/\bwind(nt|ows)\b/i.test(ENGINE.os)) { + exports.ROOT = "\\"; + exports.SEPARATOR = "\\"; + exports.ALT_SEPARATOR = "/"; +} else { + exports.ROOT = "/"; + exports.SEPARATOR = "/"; + exports.ALT_SEPARATOR = undefined; } -function canonicalize(path) { - return path.replace(/[^\/]+\/\.\.\//g, "").replace(/([^\.])\.\//g, "$1"). - replace(/^\.\//g, "").replace(/\/\/+/g, "/"); +// we need to make sure the separator regex is always in sync with the separators. +// this caches the generated regex and rebuild if either separator changes. +var separatorCached, altSeparatorCached, separatorReCached; +/** + * @function + */ +exports.SEPARATORS_RE = function () { + if ( + separatorCached !== exports.SEPARATOR || + altSeparatorCached !== exports.ALT_SEPARATOR + ) { + separatorCached = exports.SEPARATOR; + altSeparatorCached = exports.ALT_SEPARATOR; + separatorReCached = new RegExp("[" + + (separatorCached || '').replace(/[-[\]{}()*+?.\\^$|,#\s]/g, "\\$&") + + (altSeparatorCached || '').replace(/[-[\]{}()*+?.\\^$|,#\s]/g, "\\$&") + + "]", "g"); + } + return separatorReCached; } -//////////////////////////////////////////////////////////////////////////////// +/** + * separates a path into components. If the path is + * absolute, the first path component is the root of the + * file system, indicated by an empty string on Unix, and a + * drive letter followed by a colon on Windows. + * @returns {Array * String} + */ +exports.split = function (path) { + var parts; + try { + parts = String(path).split(exports.SEPARATORS_RE()); + } catch (exception) { + throw new Error("Cannot split " + (typeof path) + ', "' + path + '"'); + } + // this special case helps isAbsolute + // distinguish an empty path from an absolute path + // "" -> [] NOT [""] + if (parts.length == 1 && parts[0] == "") + return []; + // "a" -> ["a"] + // "/a" -> ["", "a"] + return parts; +}; + +/** + * Takes file system paths as variadic arguments and treats + * each as a file or directory path and returns the path + * arrived by traversing into the those paths. All + * arguments except for the last must be paths to + * directories for the result to be meaningful. + * @returns {String} path + */ +exports.join = function () { + if (arguments.length === 1 && typeof arguments[0] === "object") + return exports.normal.apply(exports, arguments[0]); + return exports.normal.apply(exports, arguments); +}; + +/** + * Takes file system paths as variadic arguments and treats + * each path as a location, in the URL sense, resolving each + * new location based on the previous. For example, if the + * first argument is the absolute path of a JSON file, and + * the second argument is a path mentioned in that JSON + * file, `resolve` returns the absolute path of the + * mentioned file. + * @returns {String} path + */ +exports.resolve = function () { + var root = ""; + var parents = []; + var children = []; + var leaf = ""; + for (var i = 0; i < arguments.length; i++) { + var path = String(arguments[i]); + if (path == "") + continue; + var parts = path.split(exports.SEPARATORS_RE()); + if (exports.isAbsolute(path)) { + root = parts.shift() + exports.SEPARATOR; + parents = []; + children = []; + } + leaf = parts.pop(); + if (leaf == "." || leaf == "..") { + parts.push(leaf); + leaf = ""; + } + for (var j = 0; j < parts.length; j++) { + var part = parts[j]; + if (part == "." || part == '') { + } else if (part == "..") { + if (children.length) { + children.pop(); + } else { + if (root) { + } else { + parents.push(".."); + } + } + } else { + children.push(part); + } + }; + } + path = parents.concat(children).join(exports.SEPARATOR); + if (path) leaf = exports.SEPARATOR + leaf; + return root + path + leaf; +}; + +/** + * Takes paths as any number of arguments and reduces them + * into a single path in normal form, removing all "." path + * components, and reducing ".." path components by removing + * the previous path component if possible. + * @returns {String} path + */ +exports.normal = function () { + var root = ""; + var parents = []; + var children = []; + for (var i = 0, ii = arguments.length; i < ii; i++) { + var path = String(arguments[i]); + // empty paths have no affect + if (path === "") + continue; + var parts = path.split(exports.SEPARATORS_RE()); + if (exports.isAbsolute(path)) { + root = parts.shift() + exports.SEPARATOR; + parents = []; + children = []; + } + for (var j = 0, jj = parts.length; j < jj; j++) { + var part = parts[j]; + if (part == "." || part == '') { + } else if (part == "..") { + if (children.length) { + children.pop(); + } else { + if (root) { + } else { + parents.push(".."); + } + } + } else { + children.push(part); + } + } + } + path = parents.concat(children).join(exports.SEPARATOR); + return root + path; +}; + +/*** + * @returns {Boolean} whether the given path begins at the + * root of the file system or a drive letter. + */ +exports.isAbsolute = function (path) { + // for absolute paths on any operating system, + // the first path component always determines + // whether it is relative or absolute. On Unix, + // it is empty, so ['', 'foo'].join('/') == '/foo', + // '/foo'.split('/') == ['', 'foo']. + var parts = exports.split(path); + // split('') == []. '' is not absolute. + // split('/') == ['', ''] is absolute. + // split(?) == [''] does not occur. + if (parts.length == 0) + return false; + return exports.isRoot(parts[0]); +}; -function _require(name, parentPath, loadOnce) { - var result, pwd, extensions, paths, path, searchDirectory, ext; - if (name.charAt(0) === "/") { - result = _attemptLoad(name, name, loadOnce); - if (result) { return result; } +/** + * @returns {Boolean} whether the given path does not begin + * at the root of the file system or a drive letter. + */ +exports.isRelative = function (path) { + return !exports.isAbsolute(path); +}; + +/** + * @returns {Boolean} whether the given path component + * corresponds to the root of the file system or a drive + * letter, as applicable. + */ +exports.isRoot = function (first) { + if (/\bwind(nt|ows)\b/i.test(ENGINE.os)) { + return /:$/.test(first); } else { - pwd = dirname(parentPath); - extensions = (/\.\w+$/).test(name) ? [""] : require.extensions; - paths = ["."].concat(require.paths); + return first == ""; + } +}; + +/** + * @returns {String} the Unix root path or corresponding + * Windows drive for a given path. + */ +exports.root = function (path) { + if (!exports.isAbsolute(path)) + path = require("./fs").absolute(path); + var parts = exports.split(path); + return exports.join(parts[0], ''); +}; + +/** + * @returns {String} the parent directory of the given path. + */ +exports.directory = function (path) { + var parts = exports.split(path); + // XXX needs to be sensitive to the root for + // Windows compatibility + parts.pop(); + return parts.join(exports.SEPARATOR) || "."; +}; + +/** + * @returns {String} the last component of a path, without + * the given extension if the extension is provided and + * matches the given file. + * @param {String} path + * @param {String} extention an optional extention to detect + * and remove if it exists. + */ +exports.base = function (path, extension) { + var base = path.split(exports.SEPARATORS_RE()).pop(); + if (extension) + base = base.replace( + new RegExp(RegExp.escape(extension) + '$'), + '' + ); + return base; +}; + +/** + * @returns {String} the extension (e.g., `txt`) of the file + * at the given path. + */ +exports.extension = function (path) { + path = exports.base(path); + path = path.replace(/^\.*/, ''); + var index = path.lastIndexOf("."); + return index <= 0 ? "" : path.substring(index); +}; +})(modules["narwhal/fs"] = {}); + +// FS - platform specific +// ============================================================================= +(function (exports) { + +var fso = new ActiveXObject("Scripting.FileSystemObject"), + stream = new ActiveXObject("ADODB.Stream"); + +// Wrap some calls in Server.mapPath on ASP +function m(path) { return Server.mapPath(path); } + +exports.isFile = function (path) { + return fso.fileExists(m(path)); +}; + +exports.read = function (path, options) { + var charset = (options || {}).charset || "utf-8", + adTypeText = 2, text = ""; + + stream.open(); + stream.charset = charset; + stream.type = adTypeText; + stream.loadFromFile(m(path)); + text = stream.readText(); + stream.close(); + + return text; +}; +})(modules.fs = modules["narwhal/fs"]); + +// Loader +// +// from narwhal-lib/lib/narwhal/loader.js +// ============================================================================= +(function (exports) { +// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License +// -- cadorn Christoph Dorn + +// NOTE: when this file is being loaded as part of the +// Narwhal bootstrapping process, all of the "requires" that +// occur here have to be manually accounted for (who loads +// the loader?) + +var ENGINE = require("narwhal/engine"); +// HACK: the stars prevent the file module from being sent to browser +// clients with the regexen we're using. we need a real solution +// for this. +var FS = require(/**/"narwhal/fs"/**/); + +// this gets swapped out with a full fledged-read before +// we're done using it +var read = FS.read; + +exports.Loader = function (options) { + var loader = {}; + var factories = options.factories || {}; + var paths = options.paths; + var extensions = options.extensions || ["", ".js"]; + var timestamps = {}; + var debug = options.debug; + + loader.resolve = exports.resolve; + + loader.resolvePkg = function(id, baseId, pkg, basePkg) { + return exports.resolvePkg(loader, id, baseId, pkg, basePkg); + }; + + loader.find = function (topId) { + // if it's absolute only search the "root" directory. + // FS.join() must collapse multiple "/" into a single "/" + var searchPaths = FS.isAbsolute(topId) ? [""] : paths; + for (var j = 0; j < extensions.length; j++) { - ext = extensions[j]; - for (var i = 0; i < paths.length; i++) { - searchDirectory = (paths[i] === ".") ? pwd : paths[i]; - path = searchDirectory + "/" + name + ext; - result = _attemptLoad(name, path, loadOnce); - if (result) { return result; } + var extension = extensions[j]; + for (var i = 0; i < searchPaths.length; i++) { + var path = FS.join(searchPaths[i], topId + extension); + if (FS.isFile(path)) + return path; + } + } + throw new Error("require error: couldn't find \"" + topId + '"'); + }; + + loader.fetch = function (topId, path) { + if (!path) + path = loader.find(topId); + if (typeof FS.lastModified === "function") + timestamps[path] = FS.lastModified(path); + if (debug) + print('loader: fetching ' + topId); + var text = read(path, { + 'charset': 'utf-8' + }); + // we leave the endline so the error line numbers align + text = text.replace(/^#[^\n]+\n/, "\n"); + return text; + }; + + loader.Module = function (text, topId, path) { + if (ENGINE.Module) { + if (!path) + path = loader.find(topId); + var factory = ENGINE.Module(text, path, 1); + factory.path = path; + return factory; + } else { + return function (inject) { + var keys = [], values = []; + for (var key in inject) { + if (Object.prototype.hasOwnProperty.call(inject, key)) { + keys.push(key); + values.push(inject[key]); + } + } + return Function.apply(null, keys).apply(this, values); + }; + } + }; + + loader.load = function (topId, path) { + if (!Object.prototype.hasOwnProperty.call(factories, topId)) { + loader.reload(topId, path); + } else if (typeof FS.lastModified === "function") { + var path = loader.find(topId); + if (loader.hasChanged(topId, path)) + loader.reload(topId, path); + } + return factories[topId]; + }; + + loader.reload = function (topId, path) { + factories[topId] = loader.Module(loader.fetch(topId, path), topId, path); + }; + + loader.isLoaded = function (topId) { + return Object.prototype.hasOwnProperty.call(factories, topId); + }; + + loader.hasChanged = function (topId, path) { + if (!path) + path = loader.resolve(topId); + return ( + !Object.prototype.hasOwnProperty.call(timestamps, path) || + FS.lastModified(path) > timestamps[path] + ); + }; + + loader.paths = paths; + loader.extensions = extensions; + + return loader; +}; + +exports.resolve = function (id, baseId) { + id = String(id); + if (id.charAt(0) == ".") { + id = FS.directory(baseId) + "/" + id; + } + // module ids need to use forward slashes, despite what the OS might say + return FS.normal(id).replace(/\\/g, '/'); +}; + +exports.resolvePkg = function(loader, id, baseId, pkg, basePkg) { + if(!loader.usingCatalog) { + // no usingCatalog - fall back to default + return [exports.resolve(id, baseId), null]; + } + if(pkg) { + // locate id in pkg + if(basePkg && loader.usingCatalog[basePkg]) { + // see if pkg is an alias + var packages = loader.usingCatalog[basePkg].packages; + if(packages[pkg]) { + if(loader.usingCatalog[packages[pkg]]) { + var path = loader.usingCatalog[packages[pkg]].libPath; + return [exports.resolve("./" + id, path + "/"), packages[pkg]]; + } else { + throw "Package '"+packages[pkg]+"' aliased with '"+pkg+"' in '"+basePkg+"' not found"; + } + } + } + // see if pkg is a top-level ID + if(loader.usingCatalog[pkg]) { + var path = loader.usingCatalog[pkg].libPath; + return [exports.resolve("./" + id, path + "/"), pkg]; + } else { + throw "Package '" + pkg + "' not aliased in '"+basePkg+"' nor a top-level ID"; + } + } else { + // if id is relative we want a module relative to basePkg if it exists + if(id.charAt(0) == "." && basePkg) { + // if baseId is absolute we use it as a base and ignore basePkg + if (FS.isAbsolute(baseId)) { + path = FS.Path(baseId); + } else if (loader.usingCatalog[basePkg]) { + path = loader.usingCatalog[basePkg].libPath.join(baseId); + } else { + throw "basePkg '" + basePkg + "' not known"; } + + // try and locate the path - at this stage it should be found + return [exports.resolve(id, path.valueOf()), basePkg]; + + } else { + // id is not relative - resolve against system modules + return [exports.resolve(id, baseId), undefined]; } } - throw new Error("couldn't find " + name); -} +}; +})(modules.loader = {}); + +// Sandbox +// +// from narwhal-lib/lib/narwhal/sandbox.js +// ============================================================================= +(function (exports) { +// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License -function _requireFactory(path, loadOnce) { - return function(name) { - return _require(name, path, loadOnce || false); +// NOTE: this file is used is the bootstrapping process, +// so any "requires" must be accounted for in narwhal.js + +var SYSTEM = require("system"); +var ENGINE = require("engine"); + +exports.Sandbox = function (options) { + options = options || {}; + var loader = options.loader; + var exportsMemo = options.modules || {}; + var moduleMemo = {}; + var debug = options.debug !== undefined ? !!options.debug : !!ENGINE.debug; + var debugDepth = 0; + var main; + var setDisplayName = (ENGINE.engine == "jsc"); + + // managed print free variable in the sandbox forwards + // to system.print in the sandbox + var subprint = options.print || function () { + var subsystem = sandbox("system"); + return subsystem.print.apply(subsystem, arguments); }; -} -function _attemptLoad(name, path, loadOnce) { - path = canonicalize(path); - var module, moduleCode; - - // see if the module is already loaded - if (require.loaded[path] && loadOnce) { return require.loaded[path]; } - try { moduleCode = readFile(path); } catch (e) {} - if (typeof moduleCode !== "undefined") { - require.loaded[path] = {}; - module = new Function("require", "exports", moduleCode); - module(_requireFactory(path, true), require.loaded[path]); - return require.loaded[path]; - } - return false; -} + var sandbox = function (id, baseId, pkg, basePkg, options) { + + if (!options) + options = {}; + + if (sandbox.debug) + SYSTEM.print("REQUIRE: id["+id+"] baseId["+baseId+"] pkg["+pkg+"] basePkg["+basePkg+"]"); + + if (loader.resolvePkg) { + var resolveInfo = loader.resolvePkg(id, baseId, pkg, basePkg); + id = resolveInfo[0]; + pkg = resolveInfo[1]; + } else { + id = loader.resolve(id, baseId); + } + + if (sandbox.debug) + SYSTEM.print("USING: id["+id+"] pkg["+pkg+"]"); + + /* populate memo with module instance */ + if (!Object.prototype.hasOwnProperty.call(exportsMemo, id) || options.force || options.once) { + + if (sandbox.debug) + SYSTEM.print(new Array(++debugDepth + 1).join("\\") + " " + id, 'module'); + + var globals = {}; + if (sandbox.debug) { + // record globals + for (var name in global) + globals[name] = true; + } + + var exports; + if (options.once) { + exports = {}; + } else { + if (!Object.prototype.hasOwnProperty.call(exportsMemo, id) || options.reload) + exports = exportsMemo[id] = {}; + exports = exportsMemo[id]; + } -/** The global require function */ -require = function (name) { return _require(name, ".", true); }; + if (options.reload) + loader.reload(id); + + var factory; + try { + factory = loader.load(id); + } finally { + // poor man's catch and rethrow (Rhino sets file/line to where the exception is thrown, not created) + if (!factory) { + delete exportsMemo[id]; + if (sandbox.debug) + debugDepth--; + } + } + + var require = Require(id, pkg); + var load = Load(id, pkg); + var module + = moduleMemo[id] + = moduleMemo[id] || Module(id, factory.path); + if (pkg) { + module["package"] = pkg; + module.using = ( + pkg && loader.usingCatalog && loader.usingCatalog[pkg] ? + loader.usingCatalog[pkg]["packages"] : + {} + ); + } + + var scope = { + "require": require, + "exports": exports, + "module": module, + "print": subprint + }; + + // require.once provides a scope of extra stuff to inject + if (options.scope) { + for (var name in options.scope) { + if (Object.prototype.hasOwnProperty.call(options.scope, name)) { + scope[name] = options.scope[name]; + } + } + } + + var completed; + try { + factory(scope); + completed = true; + } finally { + if (!completed) { + delete exportsMemo[id]; + delete moduleMemo[id]; + } + } + + if (sandbox.debug) { + // check for new globals + for (var name in global) + if (!globals[name]) + SYSTEM.print("NEW GLOBAL: " + name); + } + + if (sandbox.debug) + SYSTEM.print(new Array(debugDepth-- + 1).join("/") + " " + id, 'module'); + + // set fn.displayName on exported functions for better debugging + if (setDisplayName) { + var displayID = id.replace(/[^\w]/g, "_").toUpperCase(); + for (var name in exports) { + if (typeof exports[name] === "function" && !exports[name].displayName && + Object.prototype.hasOwnProperty.call(exports, name)) { + exports[name].displayName = displayID+"."+name; + } + } + } + + } else { + if (sandbox.debug > 1) + SYSTEM.print(new Array(debugDepth + 1).join("|") + " " + id, 'module'); + exports = exportsMemo[id]; + if (moduleMemo[id]) { + moduleMemo[id].setExports = function () { + throw new Error("Cannot set exports after a module has been required by another module."); + }; + } + } + + /* support curryId for modules in which it is requested */ + var imports = {}; + var importsUsed = false; + var curryUsed = false; + for (var name in exports) { + curryUsed = ( + typeof exports[name] == "function" && + // if it is Java class this will throw an exception, which is terribly annoying during debugging + Object.prototype.toString.call(exports[name]) !== "[object JavaClass]" && + exports[name].xNarwhalCurry + ); + + if (curryUsed) { + importsUsed = true; + imports[name] = (function (block, module) { + return function () { + return block.apply( + this, + [module].concat(Array.prototype.slice.call(arguments)) + ); + }; + })(exports[name], moduleMemo[baseId]); + } else { + imports[name] = exports[name]; + } + } + + if (!importsUsed) + imports = exports; + + return imports; + + }; + + /* + sandbox.async = function (id, baseId, pkg, basePkg, options) { + }; + */ + + sandbox.load = function (id, baseId, pkg, basePkg) { + if (loader.resolvePkg) { + var resolveInfo = loader.resolvePkg(id, baseId, pkg, basePkg); + id = resolveInfo[0]; + pkg = resolveInfo[1]; + } else { + id = loader.resolve(id, baseId); + } + return loader.load(id); + }; + + sandbox.once = function (id, baseId, pkg, basePkg, scope) { + return sandbox(id, baseId, pkg, basePkg, {"scope": scope}); + }; + + /* + sandbox.load.async = function (id, baseId, pkg, basePkg, options) { + }; + */ + + sandbox.force = function (id) { + /* baseId, pkgId, basePkg , options */ + return sandbox(id, undefined, undefined, undefined, {"force": true}); + }; + + sandbox.main = function (id, path) { + if (!path && sandbox.loader.find) + path = sandbox.loader.find(id)[1]; + id = sandbox.loader.resolve(id, ""); + main = sandbox.main = moduleMemo[id] = moduleMemo[id] || Module(id, path); + sandbox(id); + return main; + }; + + sandbox.loader = loader; + sandbox.system = SYSTEM; + sandbox.paths = loader.paths; + sandbox.extensions = loader.extensions; + sandbox.debug = debug; + + var Require = function (baseId, basePkg) { + // variations of require.* functions that close on a + // particular [package/]module + var require = function (id, pkg) { + return sandbox(id, baseId, pkg, basePkg); + }; + require.async = function (id, pkg) { + return sandbox.async(id, baseId, pkg, basePkg); + }; + require.once = function (id, scope) { + return sandbox.once(id, baseId, undefined, undefined, scope); + }; + require.once.async = function (id, scope) { + return sandbox.once.async(id, baseId, undefined, undefined, scope); + }; + require.load = function (id, pkg) { + return sandbox.load(id, baseId, pkg, basePkg); + }; + require.load.async = function (id, pkg) { + return sandbox.load.async(id, baseId, pkg, basePkg); + }; + require.force = function (id) { + return sandbox.force(id, baseId); + }; + require.loader = loader; + require.main = main; + require.paths = loader.paths; + require.extensions = loader.extensions; + return require; + }; + + var Load = function (baseId, basePkg) { + var load = function (id, pkg) { + return sandbox.load(id, baseId, pkg, basePkg); + }; + load.async = function (id) { + return sandbox.load.async(id, baseId, pkg, basePkg); + }; + return load; + }; + + var Module = function (baseId, path) { + var module = {}; + module.id = baseId; + module.path = path; + module.xNarwhalCurry = function (block) { + block.xNarwhalCurry = true; + return block; + }; + module.setExports = function (exports) { + return exportsMemo[baseId] = exports; + }; + return module; + }; + + return sandbox; +}; + +exports.sandbox = function(main, system, options) { + options = options || {}; + var prefix = options['prefix']; + var loader = options['loader'] || require.loader; + var modules = options['modules'] || {}; + var print = options['print']; + var debug = options['debug']; + if (!loader) throw new Error( + "sandbox cannot operate without a loader, either explicitly " + + "provided as an option, or implicitly provided by the current " + + "sandbox's 'loader' object." + ); + if (prefix) + loader = require("narwhal/loader/prefix").PrefixLoader(prefix, loader); + var sandbox = exports.Sandbox({ + modules: modules, + loader: loader, + print: print, + debug: debug + }); + + return sandbox.main(main); +}; +})(modules.sandbox = {}); + +// ============================================================================= + +// Set up paths +paths = ["", "lib", "engines/" + require("engine").engine + "/lib", + "engines/" + require("system").platform + "/lib"]; + +// Create require +require = require("sandbox").Sandbox({ + loader: require("loader").Loader({ paths: paths, debug: require.debug }), + modules: modules, + debug: require.debug +}); + +// Add engine-specific system/file if available +try { + require("system-engine"); + require("fs-engine"); +} catch (e) { + if (e.description.indexOf("require error") === -1) { throw e; } +} -require.paths = ["lib"]; -require.loaded = {}; -require.extensions = [".js"]; - -})(); +})(this); diff --git a/test/asp/absolute.asp b/test/asp/absolute.asp new file mode 100644 index 0000000..36fe802 --- /dev/null +++ b/test/asp/absolute.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/cyclic.asp b/test/asp/cyclic.asp new file mode 100644 index 0000000..d242915 --- /dev/null +++ b/test/asp/cyclic.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/determinism.asp b/test/asp/determinism.asp new file mode 100644 index 0000000..61491b2 --- /dev/null +++ b/test/asp/determinism.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/exactExports.asp b/test/asp/exactExports.asp new file mode 100644 index 0000000..a21d894 --- /dev/null +++ b/test/asp/exactExports.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/hasOwnProperty.asp b/test/asp/hasOwnProperty.asp new file mode 100644 index 0000000..525d986 --- /dev/null +++ b/test/asp/hasOwnProperty.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/index.asp b/test/asp/index.asp new file mode 100644 index 0000000..c347f92 --- /dev/null +++ b/test/asp/index.asp @@ -0,0 +1,19 @@ + diff --git a/test/asp/method.asp b/test/asp/method.asp new file mode 100644 index 0000000..83ffc0a --- /dev/null +++ b/test/asp/method.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/missing.asp b/test/asp/missing.asp new file mode 100644 index 0000000..12f350b --- /dev/null +++ b/test/asp/missing.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/monkeys.asp b/test/asp/monkeys.asp new file mode 100644 index 0000000..b1c9d0f --- /dev/null +++ b/test/asp/monkeys.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/nested.asp b/test/asp/nested.asp new file mode 100644 index 0000000..186f413 --- /dev/null +++ b/test/asp/nested.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/relative.asp b/test/asp/relative.asp new file mode 100644 index 0000000..6ba6c11 --- /dev/null +++ b/test/asp/relative.asp @@ -0,0 +1,7 @@ + + diff --git a/test/asp/transitive.asp b/test/asp/transitive.asp new file mode 100644 index 0000000..671a271 --- /dev/null +++ b/test/asp/transitive.asp @@ -0,0 +1,7 @@ + + diff --git a/test/test.asp b/test/test.asp deleted file mode 100644 index 37b6332..0000000 --- a/test/test.asp +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 26d619e..0000000 --- a/test/test.js +++ /dev/null @@ -1,27 +0,0 @@ -// commonjscript test runner - -require.paths.push("../lib"); // to get system module - -var print = require("system").print, - modulesSpecVersion = "1.0"; - tests = ["absolute", - "cyclic", - "determinism", - "exactExports", - "hasOwnProperty", - "method", - "missing", - "monkeys", - "nested", - "relative", - "transitive"]; - -function run(test) { - require.paths = ["commonjs/tests/modules/" + modulesSpecVersion + "/" + - test]; - print("-- " + test + "--") - require("program"); - print(""); -} - -for (var i = 0; i < tests.length; i += 1) { run(tests[i]); } diff --git a/test/test.wsf b/test/test.wsf deleted file mode 100644 index ceb42c8..0000000 --- a/test/test.wsf +++ /dev/null @@ -1,4 +0,0 @@ - - - -