diff --git a/bin/dr.js b/bin/dr.js old mode 100755 new mode 100644 index 179e5e8..b3b1f55 --- a/bin/dr.js +++ b/bin/dr.js @@ -41,14 +41,17 @@ var path = require('path'); var cwd = path.dirname(argv[1]); var libdir = path.join(cwd, "..", "lib"); - -require.paths.unshift(path.join(libdir, "jsctags")); +var jscdir = path.join(libdir, "jsctags"); + +function requireJsc(mod) { + return require(path.join(jscdir, mod)); +} var util = require('util'); -var _ = require('underscore')._; +var _ = requireJsc('underscore')._; var http = require('http'); var url = require('url'); -var servetypes = require('servetypes'); +var servetypes = requireJsc('servetypes'); function usage(msg) { util.print("usage: " + path.basename(argv[1]) + " [options]\n"); @@ -135,7 +138,7 @@ function makeSiteHandler(dir, service) { txt: "text/plain" }; - return function(req, resp) { + return function (req, resp) { var query = url.parse(req.url).pathname; if (service && query === '/analyze') { @@ -150,7 +153,7 @@ function makeSiteHandler(dir, service) { var ext = path.extname(file); var fs = require('fs'); - fs.stat(file, function(err, stats) { + fs.stat(file, function (err, stats) { if (!stats || !stats.isFile()) { util.debug("404: " + file); resp.writeHead(404, "Not Found", { 'Content-type': 'text/plain' }); diff --git a/bin/rn.js b/bin/rn.js old mode 100755 new mode 100644 index 72ec91f..e002d76 --- a/bin/rn.js +++ b/bin/rn.js @@ -38,32 +38,57 @@ var argv = process.argv; var path = require('path'); - + + var cwd = path.dirname(argv[1]); var libdir = path.join(cwd, "..", "lib"); - -require.paths.unshift(path.join(libdir, "jsctags")); +var jscdir = path.join(libdir, "jsctags"); + +function requireJsc(mod) { + return require(path.join(jscdir, mod)); +} var util = require('util'); -var _ = require('underscore')._; +var _ = requireJsc('underscore')._; var getTags = require('../lib/cfa2/jscfa').getTags; var parse = require('../narcissus/lib/parser').parse; -var stdin = process.openStdin(); +// prints a value and exits +function returnVal(val) { + // we're only allowed to return one value + if(returnVal.returned) return false; + returnVal.returned = true; + // wait for the value to be written + process.stdout.once('drain', function () { + process.exit(); + }); + + util.print(val); + return true; +} +// static variable +returnVal.returned = false; + +// prints an object as json string and exits +function returnJson(obj) { + return returnVal(JSON.stringify(obj)); +} + +var stdin = process.openStdin(); stdin.setEncoding("utf8"); var buf = []; stdin.on("data", _(buf.push).bind(buf)); -stdin.on("end", function() { +stdin.on("end", function () { var src = buf.join(""); var lines, ast; try { lines = src.split("\n"); ast = parse(src, "js", 1); } catch (e) { - util.print(JSON.stringify({ error: e.message, stage: "parse" })); - process.exit(); + returnJson({ error: e.message, stage: "parse" }); + return; } var json; @@ -73,6 +98,6 @@ stdin.on("end", function() { json = { error: e.message, stage: "analysis" }; } - util.print(JSON.stringify(json)); - process.exit(); + returnJson(json); + return; }); diff --git a/lib/jsctags/ctags/interp.js b/lib/jsctags/ctags/interp.js index 0d47795..21b3bbb 100644 --- a/lib/jsctags/ctags/interp.js +++ b/lib/jsctags/ctags/interp.js @@ -1,57 +1,57 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Bespin. - * - * The Initial Developer of the Original Code is - * Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2009 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Bespin Team (bespin@mozilla.com) - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -// Abstract interpreter, based on Narcissus. - -var getTags = require('../../cfa2/jscfa').getTags; - -//dimvar: opts stands for options. If we are using commonJS it gives -//info about the module (see getModuleInfo@jsctags.js). -exports.Interpreter = function(ast, file, lines, opts) { - this.ast = ast; - this.file = file; - this.lines = lines; - this.opts = opts; - this.tags = []; -}; - -exports.Interpreter.prototype = { - interpret: function() { - this.tags = getTags(this.ast, this.file, this.lines, this.opts); - } -}; - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Bespin. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bespin Team (bespin@mozilla.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Abstract interpreter, based on Narcissus. + +var getTags = require('../../cfa2/jscfa').getTags; + +//dimvar: opts stands for options. If we are using commonJS it gives +//info about the module (see getModuleInfo@jsctags.js). +exports.Interpreter = function (ast, file, lines, opts) { + this.ast = ast; + this.file = file; + this.lines = lines; + this.opts = opts; + this.tags = []; +}; + +exports.Interpreter.prototype = { + interpret: function () { + this.tags = getTags(this.ast, this.file, this.lines, this.opts); + } +}; + diff --git a/lib/jsctags/ctags/nativefn.js b/lib/jsctags/ctags/nativefn.js index 7547fba..0018b0b 100644 --- a/lib/jsctags/ctags/nativefn.js +++ b/lib/jsctags/ctags/nativefn.js @@ -1,64 +1,64 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Bespin. - * - * The Initial Developer of the Original Code is - * Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2009 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Bespin Team (bespin@mozilla.com) - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -// Functions important for export detection that that we try to automatically -// detect and implement. - -// jQuery's "extend". -function extend(interp, ctx, thisObject, args) { - var objects = args.data.slice(0); - if (objects.length === 0) { - return interp.getNullValue(); - } - - var target = objects.length === 1 ? thisObject : objects.shift(); - interp.coerceToStorable(target); - - objects.forEach(function(obj) { - for (var key in obj.data) { - target.data[key] = obj.data[key]; - } - }); - - return target; -} - -exports.nativeFns = { - extend: extend, // jQuery - mixin: extend // dojo, Bespin -}; - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Bespin. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bespin Team (bespin@mozilla.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Functions important for export detection that that we try to automatically +// detect and implement. + +// jQuery's "extend". +function extend(interp, ctx, thisObject, args) { + var objects = args.data.slice(0); + if (objects.length === 0) { + return interp.getNullValue(); + } + + var target = objects.length === 1 ? thisObject : objects.shift(); + interp.coerceToStorable(target); + + objects.forEach(function (obj) { + for (var key in obj.data) { + target.data[key] = obj.data[key]; + } + }); + + return target; +} + +exports.nativeFns = { + extend: extend, // jQuery + mixin: extend // dojo, Bespin +}; + diff --git a/lib/jsctags/ctags/writer.js b/lib/jsctags/ctags/writer.js index bee3183..26473f0 100644 --- a/lib/jsctags/ctags/writer.js +++ b/lib/jsctags/ctags/writer.js @@ -1,141 +1,141 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Bespin. - * - * The Initial Developer of the Original Code is - * Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2009 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Bespin Team (bespin@mozilla.com) - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -var Trait = require('../traits').Trait; -var _ = require('../underscore')._; - -const ESCAPES = { "\\": "\\\\", "\n": "\\n", "\r": "\\r", "\t": "\\t" }; - -var METATAGS = [ - { name: '!_TAG_FILE_FORMAT', tagfile: 2, addr: "/extended format/" }, - { - name: '!_TAG_FILE_SORTED', - tagfile: 0, - addr: "/0=unsorted, 1=sorted, 2=foldcase/" - }, - { - name: '!_TAG_PROGRAM_AUTHOR', - tagfile: "Patrick Walton", - addr: "/pwalton@mozilla.com/" - }, - { name: '!_TAG_PROGRAM_NAME', tagfile: "jsctags" }, - { - name: '!_TAG_PROGRAM_URL', - tagfile: "http://github.com/pcwalton/jsctags", - addr: "/GitHub repository/" - }, - { name: '!_TAG_PROGRAM_VERSION', tagfile: "0.1" } -]; - -const SPECIAL_FIELDS = { addr: true, kind: true, name: true, tagfile: true }; - -exports.TagWriter = Trait({ - tags: Trait.required, - - init: function() { }, - - write: function(stream, opts) { - if (!opts.hasOwnProperty('sort') || opts.sort === 'no') { - var sortfunc = function(a, b) { return parseInt(a.sortno, 10) - parseInt(b.sortno, 10); }; - } else if (opts.sort === 'yes') { - var sortfunc = function(a, b) { - if (a.name === b.name) return 0; - else if (a.name < b.name) return -1; - else return 1; - }; - for (var i in METATAGS) { - if (METATAGS[i].name == '!_TAG_FILE_SORTED') { - METATAGS[i].tagfile = 1; - break; - } - } - } else { - throw new Error("Invalid value for --sort flag."); - } - this.tags = this.tags.sort(sortfunc); - this.tags = METATAGS.concat(this.tags); - - var lines = this.tags.map(function(tag) { - delete tag.sortno; - var buf = [ tag.name, "\t", tag.tagfile, "\t" ]; - - var addr = tag.addr; - buf.push(addr !== undefined ? addr : "//"); - - var tagfields = []; - for (var key in tag) { - if (!(key in SPECIAL_FIELDS)) { - tagfields.push(key); - } - } - tagfields.sort(); - - var kind = tag.kind; - if (kind === undefined && tagfields.length === 0) { - buf.push("\n"); - return buf.join(""); - } - - buf.push(";\""); - - if (kind !== undefined) { - buf.push("\t", kind); - } - - tagfields.forEach(function(tagfield) { - buf.push("\t", tagfield, ":"); - - var escape = function(str) { return ESCAPES[str]; }; - buf.push(tag[tagfield].replace("[\\\n\r\t]", escape)); - }); - - buf.push("\n"); - return buf.join(""); - }); - - lines.forEach(function(line) { stream.write(line); }); - }, - - writeJSONP: function(stream, func) { - this.tags.map(function(tag) { delete tag.sortno; }); - var json = JSON.stringify(this.tags); - _([ func, "(", json, ");" ]).each(function(str) { - stream.write(str); - }); - }, -}); - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Bespin. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bespin Team (bespin@mozilla.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +var Trait = require('../traits').Trait; +var _ = require('../underscore')._; + +const ESCAPES = { "\\": "\\\\", "\n": "\\n", "\r": "\\r", "\t": "\\t" }; + +var METATAGS = [ + { name: '!_TAG_FILE_FORMAT', tagfile: 2, addr: "/extended format/" }, + { + name: '!_TAG_FILE_SORTED', + tagfile: 0, + addr: "/0=unsorted, 1=sorted, 2=foldcase/" + }, + { + name: '!_TAG_PROGRAM_AUTHOR', + tagfile: "Patrick Walton", + addr: "/pwalton@mozilla.com/" + }, + { name: '!_TAG_PROGRAM_NAME', tagfile: "jsctags" }, + { + name: '!_TAG_PROGRAM_URL', + tagfile: "http://github.com/pcwalton/jsctags", + addr: "/GitHub repository/" + }, + { name: '!_TAG_PROGRAM_VERSION', tagfile: "0.1" } +]; + +const SPECIAL_FIELDS = { addr: true, kind: true, name: true, tagfile: true }; + +exports.TagWriter = Trait({ + tags: Trait.required, + + init: function () { }, + + write: function (stream, opts) { + if (!opts.hasOwnProperty('sort') || opts.sort === 'no') { + var sortfunc = function (a, b) { return parseInt(a.sortno, 10) - parseInt(b.sortno, 10); }; + } else if (opts.sort === 'yes') { + var sortfunc = function (a, b) { + if (a.name === b.name) return 0; + else if (a.name < b.name) return -1; + else return 1; + }; + for (var i in METATAGS) { + if (METATAGS[i].name == '!_TAG_FILE_SORTED') { + METATAGS[i].tagfile = 1; + break; + } + } + } else { + throw new Error("Invalid value for --sort flag."); + } + this.tags = this.tags.sort(sortfunc); + this.tags = METATAGS.concat(this.tags); + + var lines = this.tags.map(function (tag) { + delete tag.sortno; + var buf = [ tag.name, "\t", tag.tagfile, "\t" ]; + + var addr = tag.addr; + buf.push(addr !== undefined ? addr : "//"); + + var tagfields = []; + for (var key in tag) { + if (!(key in SPECIAL_FIELDS)) { + tagfields.push(key); + } + } + tagfields.sort(); + + var kind = tag.kind; + if (kind === undefined && tagfields.length === 0) { + buf.push("\n"); + return buf.join(""); + } + + buf.push(";\""); + + if (kind !== undefined) { + buf.push("\t", kind); + } + + tagfields.forEach(function (tagfield) { + buf.push("\t", tagfield, ":"); + + var escape = function (str) { return ESCAPES[str]; }; + buf.push(tag[tagfield].replace("[\\\n\r\t]", escape)); + }); + + buf.push("\n"); + return buf.join(""); + }); + + lines.forEach(function (line) { stream.write(line); }); + }, + + writeJSONP: function (stream, func) { + this.tags.map(function (tag) { delete tag.sortno; }); + var json = JSON.stringify(this.tags); + _([ func, "(", json, ");" ]).each(function (str) { + stream.write(str); + }); + }, +}); + diff --git a/lib/jsctags/getopt.js b/lib/jsctags/getopt.js index 880dd55..82b15b1 100644 --- a/lib/jsctags/getopt.js +++ b/lib/jsctags/getopt.js @@ -1,135 +1,135 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Bespin. - * - * The Initial Developer of the Original Code is - * Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2009 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Bespin Team (bespin@mozilla.com) - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/** - * Simple option parsing library, vaguely influenced by the venerable Perl - * Getopt::Long. - */ - -exports.getopt = exports.GetOptions = function() { - var table = {}; - for (var i = 0; i < arguments.length; i++) { - var spec = arguments[i]; - var match = /^([A-Za-z0-9_|-]+)(?:=(s)(@)?)?$/.exec(spec); - var aliases = match[1].split("|"); - var info = { id: aliases[0], type: match[2], multi: match[3] }; - aliases.forEach(function(alias) { table[alias] = info; }); - } - - var result = {}; - var argv = process.argv; - var i = 2; - while (i < argv.length) { - var arg = argv[i]; - if (arg === "--") { - argv.splice(i, 1); - break; - } - if (arg.charAt(0) !== "-") { - i++; - continue; - } - - argv.splice(i, 1); - - var info, param; - if (arg.charAt(1) === "-") { - var match = /^--([A-Za-z0-9_|-]+)(?:=(.*))?$/.exec(arg); - if (match == null) { - throw new Error("malformed option \"" + arg + "\""); - } - - var optname = match[1]; - if (!(optname in table)) { - throw new Error("unknown option \"--" + optname + "\""); - } - - info = table[optname]; - if (info.type != null) { - if (match[2] != null) { - param = match[2]; - } else if (i < argv.length) { - param = argv[i]; - argv.splice(i, 1); - } else { - throw new Error("option \"--" + optname + "\" requires " + - "an argument"); - } - } else { - param = true; - } - } else { - var optname = arg.charAt(1); - - if (!(optname in table)) { - throw new Error("unknown option \"-" + optname + "\""); - } - - info = table[optname]; - if (info.type != null) { - if (arg.length > 2) { - param = arg.substr(2); - } else if (i < argv.length) { - param = argv[i]; - argv.splice(i, 1); - } else { - throw new Error("option \"-" + optname + "\" requires " + - "an argument"); - } - } else if (arg.length == 2) { - param = true; - } else { - throw new Error("option \"-" + optname + "\" doesn't take " + - "an argument"); - } - } - - var optid = info.id; - if (info.multi != null) { - if (!(optid in result)) { - result[optid] = [ param ]; - } else { - result[optid].push(param); - } - } else { - result[optid] = param; - } - } - - return result; -} - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Bespin. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bespin Team (bespin@mozilla.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Simple option parsing library, vaguely influenced by the venerable Perl + * Getopt::Long. + */ + +exports.getopt = exports.GetOptions = function () { + var table = {}; + for (var i = 0; i < arguments.length; i++) { + var spec = arguments[i]; + var match = /^([A-Za-z0-9_|-]+)(?:=(s)(@)?)?$/.exec(spec); + var aliases = match[1].split("|"); + var info = { id: aliases[0], type: match[2], multi: match[3] }; + aliases.forEach(function (alias) { table[alias] = info; }); + } + + var result = {}; + var argv = process.argv; + var i = 2; + while (i < argv.length) { + var arg = argv[i]; + if (arg === "--") { + argv.splice(i, 1); + break; + } + if (arg.charAt(0) !== "-") { + i++; + continue; + } + + argv.splice(i, 1); + + var info, param; + if (arg.charAt(1) === "-") { + var match = /^--([A-Za-z0-9_|-]+)(?:=(.*))?$/.exec(arg); + if (match == null) { + throw new Error("malformed option \"" + arg + "\""); + } + + var optname = match[1]; + if (!(optname in table)) { + throw new Error("unknown option \"--" + optname + "\""); + } + + info = table[optname]; + if (info.type != null) { + if (match[2] != null) { + param = match[2]; + } else if (i < argv.length) { + param = argv[i]; + argv.splice(i, 1); + } else { + throw new Error("option \"--" + optname + "\" requires " + + "an argument"); + } + } else { + param = true; + } + } else { + var optname = arg.charAt(1); + + if (!(optname in table)) { + throw new Error("unknown option \"-" + optname + "\""); + } + + info = table[optname]; + if (info.type != null) { + if (arg.length > 2) { + param = arg.substr(2); + } else if (i < argv.length) { + param = argv[i]; + argv.splice(i, 1); + } else { + throw new Error("option \"-" + optname + "\" requires " + + "an argument"); + } + } else if (arg.length == 2) { + param = true; + } else { + throw new Error("option \"-" + optname + "\" doesn't take " + + "an argument"); + } + } + + var optid = info.id; + if (info.multi != null) { + if (!(optid in result)) { + result[optid] = [ param ]; + } else { + result[optid].push(param); + } + } else { + result[optid] = param; + } + } + + return result; +} + diff --git a/lib/jsctags/log.js b/lib/jsctags/log.js index 96b38a5..3563f4c 100644 --- a/lib/jsctags/log.js +++ b/lib/jsctags/log.js @@ -1,55 +1,55 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Bespin. - * - * The Initial Developer of the Original Code is - * Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2009 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Bespin Team (bespin@mozilla.com) - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -var util = require('util'); - -function log(level, args) { - if (level >= exports.level) { - util.puts(Array.prototype.join.call(args, " ")); - } -} - -var levels = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 }; -exports.levels = levels; - -exports.level = levels.ERROR; - -exports.debug = function() { log(levels.DEBUG, arguments); }; -exports.warn = function() { log(levels.WARN, arguments); }; -exports.info = function() { log(levels.INFO, arguments); }; -exports.error = function() { log(levels.ERROR, arguments); }; - +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Bespin. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bespin Team (bespin@mozilla.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +var util = require('util'); + +function log(level, args) { + if (level >= exports.level) { + util.puts(Array.prototype.join.call(args, " ")); + } +} + +var levels = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 }; +exports.levels = levels; + +exports.level = levels.ERROR; + +exports.debug = function () { log(levels.DEBUG, arguments); }; +exports.warn = function () { log(levels.WARN, arguments); }; +exports.info = function () { log(levels.INFO, arguments); }; +exports.error = function () { log(levels.ERROR, arguments); }; + diff --git a/lib/jsctags/paperboy.js b/lib/jsctags/paperboy.js index 061fc61..3869ccc 100644 --- a/lib/jsctags/paperboy.js +++ b/lib/jsctags/paperboy.js @@ -1,289 +1,289 @@ -var - events = require('events'), - fs = require('fs'), - url = require('url'), - path = require('path'); - -exports.filepath = function (webroot, url) { - fp = path.normalize(path.join(webroot, (url === '/') ? 'index.html' : url)); - //Sanitize input, make sure people can't use .. to get above webroot - if (fp.substr(0,webroot.length + 1) != webroot + '/') - return(['Permission Denied', null]); - else - return([null, fp]); -}; - -exports.streamFile = function (filepath, headerFields, stat, res, req, emitter) { - var - emitter = new events.EventEmitter(), - extension = filepath.split('.').slice(-1), - contentType = exports.contentTypes[extension] || 'application/octet-stream', - charset = exports.charsets[contentType]; - - process.nextTick( function() { - - if (charset) - contentType += '; charset=' + charset; - headerFields['Content-Type'] = contentType; - - etag = '"' + stat.ino + '-' + stat.size + '-' + Date.parse(stat.mtime) +'"'; - headerFields['ETag'] = etag; - - var statCode; - //Check to see if we can send a 304 and skip the send - if(req.headers['if-none-match'] == etag){ - statCode = 304; - headerFields['Content-Length'] = 0; - }else { - headerFields['Content-Length'] = stat.size; - statCode = 200; - if (headerFields['Expires'] != undefined) { - var expires = new Date; - expires.setTime(expires.getTime() + headerFields['Expires']); - headerFields['Expires'] = expires.toUTCString(); - } - } - - res.writeHead(statCode, headerFields); - - //If we sent a 304, skip sending a body - if (statCode == 304) { - res.close(); - emitter.emit("success", statCode); - } - else { - fs.createReadStream(filepath,{'flags': 'r', 'encoding': - 'binary', 'mode': 0666, 'bufferSize': 4 * 1024}) - .addListener("data", function(chunk){ - res.write(chunk, 'binary'); - }) - .addListener("end", function(){ - emitter.emit("success", statCode); - }) - .addListener("close",function() { - res.close(); - }) - .addListener("error", function (e) { - emitter.emit("error", 500, e); - }); - } - }); - return emitter; -}; - -exports.deliver = function (webroot, req, res) { - var - stream, - fpRes = exports.filepath(webroot, url.parse(req.url).pathname), - fpErr = fpRes[0], - filepath = fpRes[1], - beforeCallback, - afterCallback, - otherwiseCallback, - errorCallback, - headerFields = {}, - addHeaderCallback, - delegate = { - error: function (callback) { - errorCallback = callback; - return delegate; - }, - before: function (callback) { - beforeCallback = callback; - return delegate; - }, - after: function (callback) { - afterCallback = callback; - return delegate; - }, - otherwise: function (callback) { - otherwiseCallback = callback; - return delegate; - }, - addHeader: function (name, value) { - headerFields[name] = value; - return delegate; - } - }; - - process.nextTick(function() { - //If file is in a directory outside of the webroot, deny the request - if (fpErr) { - statCode = 403; - if (beforeCallback) - beforeCallback(); - if (errorCallback) - errorCallback(403, 'Forbidden'); - } - else { - fs.stat(filepath, function (err, stat) { - if( (err || !stat.isFile())) { - var exactErr = err || 'File not found'; - if (beforeCallback) - beforeCallback(); - if (otherwiseCallback) - otherwiseCallback(exactErr); - } else { - //The before callback can abort the transfer by returning false - cancel = beforeCallback && (beforeCallback() === false); - if (cancel && otherwiseCallback) { - otherwiseCallback(); - } - else { - stream = exports.streamFile(filepath, headerFields, stat, res, req) - - if(afterCallback){ - stream.addListener("success", afterCallback); - } - if(errorCallback){ - stream.addListener("error", errorCallback); - } - } - } - }); - } - }); - - return delegate; -}; - -exports.contentTypes = { - "aiff": "audio/x-aiff", - "arj": "application/x-arj-compressed", - "asf": "video/x-ms-asf", - "asx": "video/x-ms-asx", - "au": "audio/ulaw", - "avi": "video/x-msvideo", - "bcpio": "application/x-bcpio", - "ccad": "application/clariscad", - "cod": "application/vnd.rim.cod", - "com": "application/x-msdos-program", - "cpio": "application/x-cpio", - "cpt": "application/mac-compactpro", - "csh": "application/x-csh", - "css": "text/css", - "deb": "application/x-debian-package", - "dl": "video/dl", - "doc": "application/msword", - "drw": "application/drafting", - "dvi": "application/x-dvi", - "dwg": "application/acad", - "dxf": "application/dxf", - "dxr": "application/x-director", - "etx": "text/x-setext", - "ez": "application/andrew-inset", - "fli": "video/x-fli", - "flv": "video/x-flv", - "gif": "image/gif", - "gl": "video/gl", - "gtar": "application/x-gtar", - "gz": "application/x-gzip", - "hdf": "application/x-hdf", - "hqx": "application/mac-binhex40", - "html": "text/html", - "ice": "x-conference/x-cooltalk", - "ief": "image/ief", - "igs": "model/iges", - "ips": "application/x-ipscript", - "ipx": "application/x-ipix", - "jad": "text/vnd.sun.j2me.app-descriptor", - "jar": "application/java-archive", - "jpeg": "image/jpeg", - "jpg": "image/jpeg", - "js": "text/javascript", - "json": "application/json", - "latex": "application/x-latex", - "lsp": "application/x-lisp", - "lzh": "application/octet-stream", - "m": "text/plain", - "m3u": "audio/x-mpegurl", - "man": "application/x-troff-man", - "me": "application/x-troff-me", - "midi": "audio/midi", - "mif": "application/x-mif", - "mime": "www/mime", - "movie": "video/x-sgi-movie", - "mp4": "video/mp4", - "mpg": "video/mpeg", - "mpga": "audio/mpeg", - "ms": "application/x-troff-ms", - "nc": "application/x-netcdf", - "oda": "application/oda", - "ogm": "application/ogg", - "pbm": "image/x-portable-bitmap", - "pdf": "application/pdf", - "pgm": "image/x-portable-graymap", - "pgn": "application/x-chess-pgn", - "pgp": "application/pgp", - "pm": "application/x-perl", - "png": "image/png", - "pnm": "image/x-portable-anymap", - "ppm": "image/x-portable-pixmap", - "ppz": "application/vnd.ms-powerpoint", - "pre": "application/x-freelance", - "prt": "application/pro_eng", - "ps": "application/postscript", - "qt": "video/quicktime", - "ra": "audio/x-realaudio", - "rar": "application/x-rar-compressed", - "ras": "image/x-cmu-raster", - "rgb": "image/x-rgb", - "rm": "audio/x-pn-realaudio", - "rpm": "audio/x-pn-realaudio-plugin", - "rtf": "text/rtf", - "rtx": "text/richtext", - "scm": "application/x-lotusscreencam", - "set": "application/set", - "sgml": "text/sgml", - "sh": "application/x-sh", - "shar": "application/x-shar", - "silo": "model/mesh", - "sit": "application/x-stuffit", - "skt": "application/x-koan", - "smil": "application/smil", - "snd": "audio/basic", - "sol": "application/solids", - "spl": "application/x-futuresplash", - "src": "application/x-wais-source", - "stl": "application/SLA", - "stp": "application/STEP", - "sv4cpio": "application/x-sv4cpio", - "sv4crc": "application/x-sv4crc", - "swf": "application/x-shockwave-flash", - "tar": "application/x-tar", - "tcl": "application/x-tcl", - "tex": "application/x-tex", - "texinfo": "application/x-texinfo", - "tgz": "application/x-tar-gz", - "tiff": "image/tiff", - "tr": "application/x-troff", - "tsi": "audio/TSP-audio", - "tsp": "application/dsptype", - "tsv": "text/tab-separated-values", - "txt": "text/plain", - "unv": "application/i-deas", - "ustar": "application/x-ustar", - "vcd": "application/x-cdlink", - "vda": "application/vda", - "vivo": "video/vnd.vivo", - "vrm": "x-world/x-vrml", - "wav": "audio/x-wav", - "wax": "audio/x-ms-wax", - "wma": "audio/x-ms-wma", - "wmv": "video/x-ms-wmv", - "wmx": "video/x-ms-wmx", - "wrl": "model/vrml", - "wvx": "video/x-ms-wvx", - "xbm": "image/x-xbitmap", - "xlw": "application/vnd.ms-excel", - "xml": "text/xml", - "xpm": "image/x-xpixmap", - "xwd": "image/x-xwindowdump", - "xyz": "chemical/x-pdb", - "zip": "application/zip" -}; - -exports.charsets = { - 'text/javascript': 'UTF-8', - 'text/html': 'UTF-8' -}; +var + events = require('events'), + fs = require('fs'), + url = require('url'), + path = require('path'); + +exports.filepath = function (webroot, url) { + fp = path.normalize(path.join(webroot, (url === '/') ? 'index.html' : url)); + //Sanitize input, make sure people can't use .. to get above webroot + if (fp.substr(0,webroot.length + 1) != webroot + '/') + return(['Permission Denied', null]); + else + return([null, fp]); +}; + +exports.streamFile = function (filepath, headerFields, stat, res, req, emitter) { + var + emitter = new events.EventEmitter(), + extension = filepath.split('.').slice(-1), + contentType = exports.contentTypes[extension] || 'application/octet-stream', + charset = exports.charsets[contentType]; + + process.nextTick( function () { + + if (charset) + contentType += '; charset=' + charset; + headerFields['Content-Type'] = contentType; + + etag = '"' + stat.ino + '-' + stat.size + '-' + Date.parse(stat.mtime) +'"'; + headerFields['ETag'] = etag; + + var statCode; + //Check to see if we can send a 304 and skip the send + if(req.headers['if-none-match'] == etag){ + statCode = 304; + headerFields['Content-Length'] = 0; + }else { + headerFields['Content-Length'] = stat.size; + statCode = 200; + if (headerFields['Expires'] != undefined) { + var expires = new Date; + expires.setTime(expires.getTime() + headerFields['Expires']); + headerFields['Expires'] = expires.toUTCString(); + } + } + + res.writeHead(statCode, headerFields); + + //If we sent a 304, skip sending a body + if (statCode == 304) { + res.close(); + emitter.emit("success", statCode); + } + else { + fs.createReadStream(filepath,{'flags': 'r', 'encoding': + 'binary', 'mode': 0666, 'bufferSize': 4 * 1024}) + .addListener("data", function (chunk){ + res.write(chunk, 'binary'); + }) + .addListener("end", function (){ + emitter.emit("success", statCode); + }) + .addListener("close",function () { + res.close(); + }) + .addListener("error", function (e) { + emitter.emit("error", 500, e); + }); + } + }); + return emitter; +}; + +exports.deliver = function (webroot, req, res) { + var + stream, + fpRes = exports.filepath(webroot, url.parse(req.url).pathname), + fpErr = fpRes[0], + filepath = fpRes[1], + beforeCallback, + afterCallback, + otherwiseCallback, + errorCallback, + headerFields = {}, + addHeaderCallback, + delegate = { + error: function (callback) { + errorCallback = callback; + return delegate; + }, + before: function (callback) { + beforeCallback = callback; + return delegate; + }, + after: function (callback) { + afterCallback = callback; + return delegate; + }, + otherwise: function (callback) { + otherwiseCallback = callback; + return delegate; + }, + addHeader: function (name, value) { + headerFields[name] = value; + return delegate; + } + }; + + process.nextTick(function () { + //If file is in a directory outside of the webroot, deny the request + if (fpErr) { + statCode = 403; + if (beforeCallback) + beforeCallback(); + if (errorCallback) + errorCallback(403, 'Forbidden'); + } + else { + fs.stat(filepath, function (err, stat) { + if( (err || !stat.isFile())) { + var exactErr = err || 'File not found'; + if (beforeCallback) + beforeCallback(); + if (otherwiseCallback) + otherwiseCallback(exactErr); + } else { + //The before callback can abort the transfer by returning false + cancel = beforeCallback && (beforeCallback() === false); + if (cancel && otherwiseCallback) { + otherwiseCallback(); + } + else { + stream = exports.streamFile(filepath, headerFields, stat, res, req) + + if(afterCallback){ + stream.addListener("success", afterCallback); + } + if(errorCallback){ + stream.addListener("error", errorCallback); + } + } + } + }); + } + }); + + return delegate; +}; + +exports.contentTypes = { + "aiff": "audio/x-aiff", + "arj": "application/x-arj-compressed", + "asf": "video/x-ms-asf", + "asx": "video/x-ms-asx", + "au": "audio/ulaw", + "avi": "video/x-msvideo", + "bcpio": "application/x-bcpio", + "ccad": "application/clariscad", + "cod": "application/vnd.rim.cod", + "com": "application/x-msdos-program", + "cpio": "application/x-cpio", + "cpt": "application/mac-compactpro", + "csh": "application/x-csh", + "css": "text/css", + "deb": "application/x-debian-package", + "dl": "video/dl", + "doc": "application/msword", + "drw": "application/drafting", + "dvi": "application/x-dvi", + "dwg": "application/acad", + "dxf": "application/dxf", + "dxr": "application/x-director", + "etx": "text/x-setext", + "ez": "application/andrew-inset", + "fli": "video/x-fli", + "flv": "video/x-flv", + "gif": "image/gif", + "gl": "video/gl", + "gtar": "application/x-gtar", + "gz": "application/x-gzip", + "hdf": "application/x-hdf", + "hqx": "application/mac-binhex40", + "html": "text/html", + "ice": "x-conference/x-cooltalk", + "ief": "image/ief", + "igs": "model/iges", + "ips": "application/x-ipscript", + "ipx": "application/x-ipix", + "jad": "text/vnd.sun.j2me.app-descriptor", + "jar": "application/java-archive", + "jpeg": "image/jpeg", + "jpg": "image/jpeg", + "js": "text/javascript", + "json": "application/json", + "latex": "application/x-latex", + "lsp": "application/x-lisp", + "lzh": "application/octet-stream", + "m": "text/plain", + "m3u": "audio/x-mpegurl", + "man": "application/x-troff-man", + "me": "application/x-troff-me", + "midi": "audio/midi", + "mif": "application/x-mif", + "mime": "www/mime", + "movie": "video/x-sgi-movie", + "mp4": "video/mp4", + "mpg": "video/mpeg", + "mpga": "audio/mpeg", + "ms": "application/x-troff-ms", + "nc": "application/x-netcdf", + "oda": "application/oda", + "ogm": "application/ogg", + "pbm": "image/x-portable-bitmap", + "pdf": "application/pdf", + "pgm": "image/x-portable-graymap", + "pgn": "application/x-chess-pgn", + "pgp": "application/pgp", + "pm": "application/x-perl", + "png": "image/png", + "pnm": "image/x-portable-anymap", + "ppm": "image/x-portable-pixmap", + "ppz": "application/vnd.ms-powerpoint", + "pre": "application/x-freelance", + "prt": "application/pro_eng", + "ps": "application/postscript", + "qt": "video/quicktime", + "ra": "audio/x-realaudio", + "rar": "application/x-rar-compressed", + "ras": "image/x-cmu-raster", + "rgb": "image/x-rgb", + "rm": "audio/x-pn-realaudio", + "rpm": "audio/x-pn-realaudio-plugin", + "rtf": "text/rtf", + "rtx": "text/richtext", + "scm": "application/x-lotusscreencam", + "set": "application/set", + "sgml": "text/sgml", + "sh": "application/x-sh", + "shar": "application/x-shar", + "silo": "model/mesh", + "sit": "application/x-stuffit", + "skt": "application/x-koan", + "smil": "application/smil", + "snd": "audio/basic", + "sol": "application/solids", + "spl": "application/x-futuresplash", + "src": "application/x-wais-source", + "stl": "application/SLA", + "stp": "application/STEP", + "sv4cpio": "application/x-sv4cpio", + "sv4crc": "application/x-sv4crc", + "swf": "application/x-shockwave-flash", + "tar": "application/x-tar", + "tcl": "application/x-tcl", + "tex": "application/x-tex", + "texinfo": "application/x-texinfo", + "tgz": "application/x-tar-gz", + "tiff": "image/tiff", + "tr": "application/x-troff", + "tsi": "audio/TSP-audio", + "tsp": "application/dsptype", + "tsv": "text/tab-separated-values", + "txt": "text/plain", + "unv": "application/i-deas", + "ustar": "application/x-ustar", + "vcd": "application/x-cdlink", + "vda": "application/vda", + "vivo": "video/vnd.vivo", + "vrm": "x-world/x-vrml", + "wav": "audio/x-wav", + "wax": "audio/x-ms-wax", + "wma": "audio/x-ms-wma", + "wmv": "video/x-ms-wmv", + "wmx": "video/x-ms-wmx", + "wrl": "model/vrml", + "wvx": "video/x-ms-wvx", + "xbm": "image/x-xbitmap", + "xlw": "application/vnd.ms-excel", + "xml": "text/xml", + "xpm": "image/x-xpixmap", + "xwd": "image/x-xwindowdump", + "xyz": "chemical/x-pdb", + "zip": "application/zip" +}; + +exports.charsets = { + 'text/javascript': 'UTF-8', + 'text/html': 'UTF-8' +}; diff --git a/lib/jsctags/servetypes.js b/lib/jsctags/servetypes.js index d2f8b1e..044a925 100644 --- a/lib/jsctags/servetypes.js +++ b/lib/jsctags/servetypes.js @@ -1,66 +1,71 @@ -var path = require('path'); -var parse = require('../../narcissus/lib/parser').parse; -var getTags = require('../cfa2/jscfa').getTags; -var formidable = require('../formidable'); - -function analyze(cwd, req, resp) { - // FIXME: extra field for header message (instead of "Not Found") - function error(code, msg) { - resp.writeHead(code, "Not Found", { 'Content-type': 'text/plain' }); - resp.end(code + " " + msg); - } - - if (req.method !== 'POST') { - resp.writeHead(405, "Invalid Method", { 'Content-type': 'text/plain' }); - resp.end("405 Only POST method allowed"); - return; - } - - // FIXME: prevent formidable from saving files - var form = new formidable.IncomingForm(); - var src; - - form - .addListener('error', function(err) { - error(400, err); - }) - .addListener('field', function(field, value) { - if (field === 'src') - src = value; - }) - .addListener('end', function() { - if (!src) { - error(400, 'No "src" field in POST'); - return; - } - - // farm the work out to rn.js - var spawn = require('child_process').spawn; - var rn = spawn(path.join(cwd, 'rn.js')); - - // on timeout, kill the process and send an error - var timeout = setTimeout(function() { - if (rn) { - rn.kill(); // ooh, brutal ;) - error(500, "Service timed out"); - } - }, 10000); - - // send the input program to rn.js - rn.stdin.end(src); - - // forward the output ctags to the response - var buf = []; - rn.stdout.on("data", _(buf.push).bind(buf)); - rn.stdout.on("end", function() { - clearTimeout(timeout); - rn = null; - resp.writeHead(200, "OK", { "Content-type": "application/json" }); - resp.end(buf.join("")); - }); - }); - - form.parse(req); -} - -exports.analyze = analyze; +var path = require('path'); + +var parse = require('../../narcissus/lib/parser').parse; +var getTags = require('../cfa2/jscfa').getTags; +var formidable = require('../formidable'); + +function analyze(cwd, req, resp) { + // FIXME: extra field for header message (instead of "Not Found") + function error(code, msg) { + resp.writeHead(code, "Not Found", { 'Content-type': 'text/plain' }); + resp.end(code + " " + msg); + } + + if (req.method !== 'POST') { + resp.writeHead(405, "Invalid Method", { 'Content-type': 'text/plain' }); + resp.end("405 Only POST method allowed"); + return; + } + + var form = new formidable.IncomingForm(); + var src; + + // prevent formidable from saving files + form.onPart = function (part) { + if(!part.filename) + this.handlePart(part); + }; + form + .addListener('error', function (err) { + error(400, err); + }) + .addListener('field', function (field, value) { + if (field === 'src') + src = value; + }) + .addListener('end', function () { + if (!src) { + error(400, 'No "src" field in POST'); + return; + } + + // farm the work out to rn.js + var spawn = require('child_process').spawn; + var rn = spawn('node', [path.join(cwd, 'rn.js')]); + + // on timeout, kill the process and send an error + var timeout = setTimeout(function () { + if (rn) { + rn.kill(); // ooh, ulträa brutal ;) + error(500, "Service timed out"); + } + }, 10000); + + // forward the output ctags to the response + var buf = []; + rn.stdout.on("data", _(buf.push).bind(buf)); + rn.stdout.on("end", function () { + clearTimeout(timeout); + rn = null; + resp.writeHead(200, "OK", { "Content-type": "application/json" }); + resp.end(buf.join("")); + }); + + // send the input program to rn.js + rn.stdin.end(src); + }); + + form.parse(req); +} + +exports.analyze = analyze; diff --git a/lib/jsctags/traits.js b/lib/jsctags/traits.js index 82a2d76..0bbcf1e 100644 --- a/lib/jsctags/traits.js +++ b/lib/jsctags/traits.js @@ -1,664 +1,664 @@ -// Copyright (C) 2010 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// See http://code.google.com/p/es-lab/wiki/Traits -// for background on traits and a description of this library - -var Trait = (function(){ - - // == Ancillary functions == - - var SUPPORTS_DEFINEPROP = (function() { - try { - var test = {}; - Object.defineProperty(test, 'x', {get: function() { return 0; } } ); - return test.x === 0; - } catch(e) { - return false; - } - })(); - - // IE8 implements Object.defineProperty and Object.getOwnPropertyDescriptor - // only for DOM objects. These methods don't work on plain objects. - // Hence, we need a more elaborate feature-test to see whether the - // browser truly supports these methods: - function supportsGOPD() { - try { - if (Object.getOwnPropertyDescriptor) { - var test = {x:0}; - return !!Object.getOwnPropertyDescriptor(test,'x'); - } - } catch(e) {} - return false; - }; - function supportsDP() { - try { - if (Object.defineProperty) { - var test = {}; - Object.defineProperty(test,'x',{value:0}); - return test.x === 0; - } - } catch(e) {} - return false; - }; - - var call = Function.prototype.call; - - /** - * An ad hoc version of bind that only binds the 'this' parameter. - */ - var bindThis = Function.prototype.bind ? - function(fun, self) { return Function.prototype.bind.call(fun, self); } : - function(fun, self) { - function funcBound(var_args) { - return fun.apply(self, arguments); - } - return funcBound; - }; - - var hasOwnProperty = bindThis(call, Object.prototype.hasOwnProperty); - var slice = bindThis(call, Array.prototype.slice); - - // feature testing such that traits.js runs on both ES3 and ES5 - var forEach = Array.prototype.forEach ? - bindThis(call, Array.prototype.forEach) : - function(arr, fun) { - for (var i = 0, len = arr.length; i < len; i++) { fun(arr[i]); } - }; - - // var freeze = Object.freeze || function(obj) { return obj; }; - var freeze = function(obj) { return obj; } - var getPrototypeOf = Object.getPrototypeOf || function(obj) { - return Object.prototype; - }; - var getOwnPropertyNames = Object.getOwnPropertyNames || - function(obj) { - var props = []; - for (var p in obj) { if (hasOwnProperty(obj,p)) { props.push(p); } } - return props; - }; - var getOwnPropertyDescriptor = supportsGOPD() ? - Object.getOwnPropertyDescriptor : - function(obj, name) { - return { - value: obj[name], - enumerable: true, - writable: true, - configurable: true - }; - }; - var defineProperty = supportsDP() ? Object.defineProperty : - function(obj, name, pd) { - obj[name] = pd.value; - }; - var defineProperties = Object.defineProperties || - function(obj, propMap) { - for (var name in propMap) { - if (hasOwnProperty(propMap, name)) { - defineProperty(obj, name, propMap[name]); - } - } - }; - var Object_create = Object.create || - function(proto, propMap) { - var self; - function dummy() {}; - dummy.prototype = proto || Object.prototype; - self = new dummy(); - if (propMap) { - defineProperties(self, propMap); - } - return self; - }; - var getOwnProperties = Object.getOwnProperties || - function(obj) { - var map = {}; - forEach(getOwnPropertyNames(obj), function (name) { - map[name] = getOwnPropertyDescriptor(obj, name); - }); - return map; - }; - - // end of ES3 - ES5 compatibility functions - - function makeConflictAccessor(name) { - var accessor = function(var_args) { - throw new Error("Conflicting property: "+name); - }; - freeze(accessor.prototype); - return freeze(accessor); - }; - - function makeRequiredPropDesc(name) { - return freeze({ - value: undefined, - enumerable: false, - required: true - }); - } - - function makeConflictingPropDesc(name) { - var conflict = makeConflictAccessor(name); - if (SUPPORTS_DEFINEPROP) { - return freeze({ - get: conflict, - set: conflict, - enumerable: false, - conflict: true - }); - } else { - return freeze({ - value: conflict, - enumerable: false, - conflict: true - }); - } - } - - /** - * Are x and y not observably distinguishable? - */ - function identical(x, y) { - if (x === y) { - // 0 === -0, but they are not identical - return x !== 0 || 1/x === 1/y; - } else { - // NaN !== NaN, but they are identical. - // NaNs are the only non-reflexive value, i.e., if x !== x, - // then x is a NaN. - return x !== x && y !== y; - } - } - - // Note: isSameDesc should return true if both - // desc1 and desc2 represent a 'required' property - // (otherwise two composed required properties would be turned into - // a conflict) - function isSameDesc(desc1, desc2) { - // for conflicting properties, don't compare values because - // the conflicting property values are never equal - if (desc1.conflict && desc2.conflict) { - return true; - } else { - return ( desc1.get === desc2.get - && desc1.set === desc2.set - && identical(desc1.value, desc2.value) - && desc1.enumerable === desc2.enumerable - && desc1.required === desc2.required - && desc1.conflict === desc2.conflict); - } - } - - function freezeAndBind(meth, self) { - return freeze(bindThis(meth, self)); - } - - /* makeSet(['foo', ...]) => { foo: true, ...} - * - * makeSet returns an object whose own properties represent a set. - * - * Each string in the names array is added to the set. - * - * To test whether an element is in the set, perform: - * hasOwnProperty(set, element) - */ - function makeSet(names) { - var set = {}; - forEach(names, function (name) { - set[name] = true; - }); - return freeze(set); - } - - // == singleton object to be used as the placeholder for a required - // property == - - var required = freeze({ - toString: function() { return ''; } - }); - - // == The public API methods == - - /** - * var newTrait = trait({ foo:required, ... }) - * - * @param object an object record (in principle an object literal) - * @returns a new trait describing all of the own properties of the object - * (both enumerable and non-enumerable) - * - * As a general rule, 'trait' should be invoked with an object - * literal, since the object merely serves as a record - * descriptor. Both its identity and its prototype chain are - * irrelevant. - * - * Data properties bound to function objects in the argument will be - * flagged as 'method' properties. The prototype of these function - * objects is frozen. - * - * Data properties bound to the 'required' singleton exported by - * this module will be marked as 'required' properties. - * - * The trait function is pure if no other code can witness - * the side-effects of freezing the prototypes of the methods. If - * trait is invoked with an object literal whose methods - * are represented as in-place anonymous functions, this should - * normally be the case. - */ - function trait(obj) { - var map = {}; - forEach(getOwnPropertyNames(obj), function (name) { - var pd = getOwnPropertyDescriptor(obj, name); - if (pd.value === required) { - pd = makeRequiredPropDesc(name); - } else if (typeof pd.value === 'function') { - pd.method = true; - if ('prototype' in pd.value) { - freeze(pd.value.prototype); - } - } else { - if (pd.get && pd.get.prototype) { freeze(pd.get.prototype); } - if (pd.set && pd.set.prototype) { freeze(pd.set.prototype); } - } - map[name] = pd; - }); - return map; - } - - /** - * var newTrait = compose(trait_1, trait_2, ..., trait_N) - * - * @param trait_i a trait object - * @returns a new trait containing the combined own properties of - * all the trait_i. - * - * If two or more traits have own properties with the same name, the new - * trait will contain a 'conflict' property for that name. 'compose' is - * a commutative and associative operation, and the order of its - * arguments is not significant. - * - * If 'compose' is invoked with < 2 arguments, then: - * compose(trait_1) returns a trait equivalent to trait_1 - * compose() returns an empty trait - */ - function compose(var_args) { - var traits = slice(arguments, 0); - var newTrait = {}; - - forEach(traits, function (trait) { - forEach(getOwnPropertyNames(trait), function (name) { - var pd = trait[name]; - if (hasOwnProperty(newTrait, name) && - !newTrait[name].required) { - - // a non-required property with the same name was previously - // defined this is not a conflict if pd represents a - // 'required' property itself: - if (pd.required) { - return; // skip this property, the required property is - // now present - } - - if (!isSameDesc(newTrait[name], pd)) { - // a distinct, non-required property with the same name - // was previously defined by another trait => mark as - // conflicting property - newTrait[name] = makeConflictingPropDesc(name); - } // else, - // properties are not in conflict if they refer to the same value - - } else { - newTrait[name] = pd; - } - }); - }); - - return freeze(newTrait); - } - - /* var newTrait = exclude(['name', ...], trait) - * - * @param names a list of strings denoting property names. - * @param trait a trait some properties of which should be excluded. - * @returns a new trait with the same own properties as the original trait, - * except that all property names appearing in the first argument - * are replaced by required property descriptors. - * - * Note: exclude(A, exclude(B,t)) is equivalent to exclude(A U B, t) - */ - function exclude(names, trait) { - var exclusions = makeSet(names); - var newTrait = {}; - - forEach(getOwnPropertyNames(trait), function (name) { - // required properties are not excluded but ignored - if (!hasOwnProperty(exclusions, name) || trait[name].required) { - newTrait[name] = trait[name]; - } else { - // excluded properties are replaced by required properties - newTrait[name] = makeRequiredPropDesc(name); - } - }); - - return freeze(newTrait); - } - - /** - * var newTrait = override(trait_1, trait_2, ..., trait_N) - * - * @returns a new trait with all of the combined properties of the - * argument traits. In contrast to 'compose', 'override' - * immediately resolves all conflicts resulting from this - * composition by overriding the properties of later - * traits. Trait priority is from left to right. I.e. the - * properties of the leftmost trait are never overridden. - * - * override is associative: - * override(t1,t2,t3) is equivalent to override(t1, override(t2, t3)) or - * to override(override(t1, t2), t3) - * override is not commutative: override(t1,t2) is not equivalent - * to override(t2,t1) - * - * override() returns an empty trait - * override(trait_1) returns a trait equivalent to trait_1 - */ - function override(var_args) { - var traits = slice(arguments, 0); - var newTrait = {}; - forEach(traits, function (trait) { - forEach(getOwnPropertyNames(trait), function (name) { - var pd = trait[name]; - // add this trait's property to the composite trait only if - // - the trait does not yet have this property - // - or, the trait does have the property, but it's a required property - if (!hasOwnProperty(newTrait, name) || newTrait[name].required) { - newTrait[name] = pd; - } - }); - }); - return freeze(newTrait); - } - - /** - * var newTrait = override(dominantTrait, recessiveTrait) - * - * @returns a new trait with all of the properties of dominantTrait - * and all of the properties of recessiveTrait not in dominantTrait - * - * Note: override is associative: - * override(t1, override(t2, t3)) is equivalent to - * override(override(t1, t2), t3) - */ - /*function override(frontT, backT) { - var newTrait = {}; - // first copy all of backT's properties into newTrait - forEach(getOwnPropertyNames(backT), function (name) { - newTrait[name] = backT[name]; - }); - // now override all these properties with frontT's properties - forEach(getOwnPropertyNames(frontT), function (name) { - var pd = frontT[name]; - // frontT's required property does not override the provided property - if (!(pd.required && hasOwnProperty(newTrait, name))) { - newTrait[name] = pd; - } - }); - - return freeze(newTrait); - }*/ - - /** - * var newTrait = rename(map, trait) - * - * @param map an object whose own properties serve as a mapping from - old names to new names. - * @param trait a trait object - * @returns a new trait with the same properties as the original trait, - * except that all properties whose name is an own property - * of map will be renamed to map[name], and a 'required' property - * for name will be added instead. - * - * rename({a: 'b'}, t) eqv compose(exclude(['a'],t), - * { a: { required: true }, - * b: t[a] }) - * - * For each renamed property, a required property is generated. If - * the map renames two properties to the same name, a conflict is - * generated. If the map renames a property to an existing - * unrenamed property, a conflict is generated. - * - * Note: rename(A, rename(B, t)) is equivalent to rename(\n -> - * A(B(n)), t) Note: rename({...},exclude([...], t)) is not eqv to - * exclude([...],rename({...}, t)) - */ - function rename(map, trait) { - var renamedTrait = {}; - forEach(getOwnPropertyNames(trait), function (name) { - // required props are never renamed - if (hasOwnProperty(map, name) && !trait[name].required) { - var alias = map[name]; // alias defined in map - if (hasOwnProperty(renamedTrait, alias) && - !renamedTrait[alias].required) { - // could happen if 2 props are mapped to the same alias - renamedTrait[alias] = makeConflictingPropDesc(alias); - } else { - // add the property under an alias - renamedTrait[alias] = trait[name]; - } - // add a required property under the original name - // but only if a property under the original name does not exist - // such a prop could exist if an earlier prop in the trait was - // previously aliased to this name - if (!hasOwnProperty(renamedTrait, name)) { - renamedTrait[name] = makeRequiredPropDesc(name); - } - } else { // no alias defined - if (hasOwnProperty(renamedTrait, name)) { - // could happen if another prop was previously aliased to name - if (!trait[name].required) { - renamedTrait[name] = makeConflictingPropDesc(name); - } - // else required property overridden by a previously aliased - // property and otherwise ignored - } else { - renamedTrait[name] = trait[name]; - } - } - }); - - return freeze(renamedTrait); - } - - /** - * var newTrait = resolve({ oldName: 'newName', excludeName: - * undefined, ... }, trait) - * - * This is a convenience function combining renaming and - * exclusion. It can be implemented as rename(map, - * exclude(exclusions, trait)) where map is the subset of - * mappings from oldName to newName and exclusions is an array of - * all the keys that map to undefined (or another falsy value). - * - * @param resolutions an object whose own properties serve as a - mapping from old names to new names, or to undefined if - the property should be excluded - * @param trait a trait object - * @returns a resolved trait with the same own properties as the - * original trait. - * - * In a resolved trait, all own properties whose name is an own property - * of resolutions will be renamed to resolutions[name] if it is truthy, - * or their value is changed into a required property descriptor if - * resolutions[name] is falsy. - * - * Note, it's important to _first_ exclude, _then_ rename, since exclude - * and rename are not associative, for example: - * rename({a: 'b'}, exclude(['b'], trait({ a:1,b:2 }))) eqv trait({b:1}) - * exclude(['b'], rename({a: 'b'}, trait({ a:1,b:2 }))) eqv - * trait({b:Trait.required}) - * - * writing resolve({a:'b', b: undefined},trait({a:1,b:2})) makes it - * clear that what is meant is to simply drop the old 'b' and rename - * 'a' to 'b' - */ - function resolve(resolutions, trait) { - var renames = {}; - var exclusions = []; - // preprocess renamed and excluded properties - for (var name in resolutions) { - if (hasOwnProperty(resolutions, name)) { - if (resolutions[name]) { // old name -> new name - renames[name] = resolutions[name]; - } else { // name -> undefined - exclusions.push(name); - } - } - } - return rename(renames, exclude(exclusions, trait)); - } - - /** - * var obj = create(proto, trait) - * - * @param proto denotes the prototype of the completed object - * @param trait a trait object to be turned into a complete object - * @returns an object with all of the properties described by the trait. - * @throws 'Missing required property' the trait still contains a - * required property. - * @throws 'Remaining conflicting property' if the trait still - * contains a conflicting property. - * - * Trait.create is like Object.create, except that it generates - * high-integrity or final objects. In addition to creating a new object - * from a trait, it also ensures that: - * - an exception is thrown if 'trait' still contains required properties - * - an exception is thrown if 'trait' still contains conflicting - * properties - * - the object is and all of its accessor and method properties are frozen - * - the 'this' pseudovariable in all accessors and methods of - * the object is bound to the composed object. - * - * Use Object.create instead of Trait.create if you want to create - * abstract or malleable objects. Keep in mind that for such objects: - * - no exception is thrown if 'trait' still contains required properties - * (the properties are simply dropped from the composite object) - * - no exception is thrown if 'trait' still contains conflicting - * properties (these properties remain as conflicting - * properties in the composite object) - * - neither the object nor its accessor and method properties are frozen - * - the 'this' pseudovariable in all accessors and methods of - * the object is left unbound. - */ - function create(proto, trait) { - var self = Object_create(proto); - var properties = {}; - - forEach(getOwnPropertyNames(trait), function (name) { - var pd = trait[name]; - // check for remaining 'required' properties - // Note: it's OK for the prototype to provide the properties - if (pd.required) { - if (!(name in proto)) { - throw new Error('Missing required property: '+name); - } - } else if (pd.conflict) { // check for remaining conflicting properties - throw new Error('Remaining conflicting property: '+name); - } else if ('value' in pd) { // data property - // freeze all function properties and their prototype - if (pd.method) { // the property is meant to be used as a method - // bind 'this' in trait method to the composite object - properties[name] = { - value: freezeAndBind(pd.value, self), - enumerable: pd.enumerable, - configurable: pd.configurable, - writable: pd.writable - }; - } else { - properties[name] = pd; - } - } else { // accessor property - properties[name] = { - get: pd.get ? freezeAndBind(pd.get, self) : undefined, - set: pd.set ? freezeAndBind(pd.set, self) : undefined, - enumerable: pd.enumerable, - configurable: pd.configurable, - writable: pd.writable - }; - } - }); - - defineProperties(self, properties); - return freeze(self); - } - - /** A shorthand for create(Object.prototype, trait({...}), options) */ - function object(record, options) { - return create(Object.prototype, trait(record), options); - } - - /** - * Tests whether two traits are equivalent. T1 is equivalent to T2 iff - * both describe the same set of property names and for all property - * names n, T1[n] is equivalent to T2[n]. Two property descriptors are - * equivalent if they have the same value, accessors and attributes. - * - * @return a boolean indicating whether the two argument traits are - * equivalent. - */ - function eqv(trait1, trait2) { - var names1 = getOwnPropertyNames(trait1); - var names2 = getOwnPropertyNames(trait2); - var name; - if (names1.length !== names2.length) { - return false; - } - for (var i = 0; i < names1.length; i++) { - name = names1[i]; - if (!trait2[name] || !isSameDesc(trait1[name], trait2[name])) { - return false; - } - } - return true; - } - - // if this code is ran in ES3 without an Object.create function, this - // library will define it on Object: - if (!Object.create) { - Object.create = Object_create; - } - // ES5 does not by default provide Object.getOwnProperties - // if it's not defined, the Traits library defines this utility - // function on Object - if(!Object.getOwnProperties) { - Object.getOwnProperties = getOwnProperties; - } - - // expose the public API of this module - function Trait(record) { - // calling Trait as a function creates a new atomic trait - return trait(record); - } - Trait.required = freeze(required); - Trait.compose = freeze(compose); - Trait.resolve = freeze(resolve); - Trait.override = freeze(override); - Trait.create = freeze(create); - Trait.eqv = freeze(eqv); - Trait.object = freeze(object); // not essential, cf. create + trait - return freeze(Trait); - -})(); - -if (typeof exports !== "undefined") { // CommonJS module support - exports.Trait = Trait; +// Copyright (C) 2010 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// See http://code.google.com/p/es-lab/wiki/Traits +// for background on traits and a description of this library + +var Trait = (function (){ + + // == Ancillary functions == + + var SUPPORTS_DEFINEPROP = (function () { + try { + var test = {}; + Object.defineProperty(test, 'x', {get: function () { return 0; } } ); + return test.x === 0; + } catch(e) { + return false; + } + })(); + + // IE8 implements Object.defineProperty and Object.getOwnPropertyDescriptor + // only for DOM objects. These methods don't work on plain objects. + // Hence, we need a more elaborate feature-test to see whether the + // browser truly supports these methods: + function supportsGOPD() { + try { + if (Object.getOwnPropertyDescriptor) { + var test = {x:0}; + return !!Object.getOwnPropertyDescriptor(test,'x'); + } + } catch(e) {} + return false; + }; + function supportsDP() { + try { + if (Object.defineProperty) { + var test = {}; + Object.defineProperty(test,'x',{value:0}); + return test.x === 0; + } + } catch(e) {} + return false; + }; + + var call = Function.prototype.call; + + /** + * An ad hoc version of bind that only binds the 'this' parameter. + */ + var bindThis = Function.prototype.bind ? + function (fun, self) { return Function.prototype.bind.call(fun, self); } : + function (fun, self) { + function funcBound(var_args) { + return fun.apply(self, arguments); + } + return funcBound; + }; + + var hasOwnProperty = bindThis(call, Object.prototype.hasOwnProperty); + var slice = bindThis(call, Array.prototype.slice); + + // feature testing such that traits.js runs on both ES3 and ES5 + var forEach = Array.prototype.forEach ? + bindThis(call, Array.prototype.forEach) : + function (arr, fun) { + for (var i = 0, len = arr.length; i < len; i++) { fun(arr[i]); } + }; + + // var freeze = Object.freeze || function (obj) { return obj; }; + var freeze = function (obj) { return obj; } + var getPrototypeOf = Object.getPrototypeOf || function (obj) { + return Object.prototype; + }; + var getOwnPropertyNames = Object.getOwnPropertyNames || + function (obj) { + var props = []; + for (var p in obj) { if (hasOwnProperty(obj,p)) { props.push(p); } } + return props; + }; + var getOwnPropertyDescriptor = supportsGOPD() ? + Object.getOwnPropertyDescriptor : + function (obj, name) { + return { + value: obj[name], + enumerable: true, + writable: true, + configurable: true + }; + }; + var defineProperty = supportsDP() ? Object.defineProperty : + function (obj, name, pd) { + obj[name] = pd.value; + }; + var defineProperties = Object.defineProperties || + function (obj, propMap) { + for (var name in propMap) { + if (hasOwnProperty(propMap, name)) { + defineProperty(obj, name, propMap[name]); + } + } + }; + var Object_create = Object.create || + function (proto, propMap) { + var self; + function dummy() {}; + dummy.prototype = proto || Object.prototype; + self = new dummy(); + if (propMap) { + defineProperties(self, propMap); + } + return self; + }; + var getOwnProperties = Object.getOwnProperties || + function (obj) { + var map = {}; + forEach(getOwnPropertyNames(obj), function (name) { + map[name] = getOwnPropertyDescriptor(obj, name); + }); + return map; + }; + + // end of ES3 - ES5 compatibility functions + + function makeConflictAccessor(name) { + var accessor = function (var_args) { + throw new Error("Conflicting property: "+name); + }; + freeze(accessor.prototype); + return freeze(accessor); + }; + + function makeRequiredPropDesc(name) { + return freeze({ + value: undefined, + enumerable: false, + required: true + }); + } + + function makeConflictingPropDesc(name) { + var conflict = makeConflictAccessor(name); + if (SUPPORTS_DEFINEPROP) { + return freeze({ + get: conflict, + set: conflict, + enumerable: false, + conflict: true + }); + } else { + return freeze({ + value: conflict, + enumerable: false, + conflict: true + }); + } + } + + /** + * Are x and y not observably distinguishable? + */ + function identical(x, y) { + if (x === y) { + // 0 === -0, but they are not identical + return x !== 0 || 1/x === 1/y; + } else { + // NaN !== NaN, but they are identical. + // NaNs are the only non-reflexive value, i.e., if x !== x, + // then x is a NaN. + return x !== x && y !== y; + } + } + + // Note: isSameDesc should return true if both + // desc1 and desc2 represent a 'required' property + // (otherwise two composed required properties would be turned into + // a conflict) + function isSameDesc(desc1, desc2) { + // for conflicting properties, don't compare values because + // the conflicting property values are never equal + if (desc1.conflict && desc2.conflict) { + return true; + } else { + return ( desc1.get === desc2.get + && desc1.set === desc2.set + && identical(desc1.value, desc2.value) + && desc1.enumerable === desc2.enumerable + && desc1.required === desc2.required + && desc1.conflict === desc2.conflict); + } + } + + function freezeAndBind(meth, self) { + return freeze(bindThis(meth, self)); + } + + /* makeSet(['foo', ...]) => { foo: true, ...} + * + * makeSet returns an object whose own properties represent a set. + * + * Each string in the names array is added to the set. + * + * To test whether an element is in the set, perform: + * hasOwnProperty(set, element) + */ + function makeSet(names) { + var set = {}; + forEach(names, function (name) { + set[name] = true; + }); + return freeze(set); + } + + // == singleton object to be used as the placeholder for a required + // property == + + var required = freeze({ + toString: function () { return ''; } + }); + + // == The public API methods == + + /** + * var newTrait = trait({ foo:required, ... }) + * + * @param object an object record (in principle an object literal) + * @returns a new trait describing all of the own properties of the object + * (both enumerable and non-enumerable) + * + * As a general rule, 'trait' should be invoked with an object + * literal, since the object merely serves as a record + * descriptor. Both its identity and its prototype chain are + * irrelevant. + * + * Data properties bound to function objects in the argument will be + * flagged as 'method' properties. The prototype of these function + * objects is frozen. + * + * Data properties bound to the 'required' singleton exported by + * this module will be marked as 'required' properties. + * + * The trait function is pure if no other code can witness + * the side-effects of freezing the prototypes of the methods. If + * trait is invoked with an object literal whose methods + * are represented as in-place anonymous functions, this should + * normally be the case. + */ + function trait(obj) { + var map = {}; + forEach(getOwnPropertyNames(obj), function (name) { + var pd = getOwnPropertyDescriptor(obj, name); + if (pd.value === required) { + pd = makeRequiredPropDesc(name); + } else if (typeof pd.value === 'function') { + pd.method = true; + if ('prototype' in pd.value) { + freeze(pd.value.prototype); + } + } else { + if (pd.get && pd.get.prototype) { freeze(pd.get.prototype); } + if (pd.set && pd.set.prototype) { freeze(pd.set.prototype); } + } + map[name] = pd; + }); + return map; + } + + /** + * var newTrait = compose(trait_1, trait_2, ..., trait_N) + * + * @param trait_i a trait object + * @returns a new trait containing the combined own properties of + * all the trait_i. + * + * If two or more traits have own properties with the same name, the new + * trait will contain a 'conflict' property for that name. 'compose' is + * a commutative and associative operation, and the order of its + * arguments is not significant. + * + * If 'compose' is invoked with < 2 arguments, then: + * compose(trait_1) returns a trait equivalent to trait_1 + * compose() returns an empty trait + */ + function compose(var_args) { + var traits = slice(arguments, 0); + var newTrait = {}; + + forEach(traits, function (trait) { + forEach(getOwnPropertyNames(trait), function (name) { + var pd = trait[name]; + if (hasOwnProperty(newTrait, name) && + !newTrait[name].required) { + + // a non-required property with the same name was previously + // defined this is not a conflict if pd represents a + // 'required' property itself: + if (pd.required) { + return; // skip this property, the required property is + // now present + } + + if (!isSameDesc(newTrait[name], pd)) { + // a distinct, non-required property with the same name + // was previously defined by another trait => mark as + // conflicting property + newTrait[name] = makeConflictingPropDesc(name); + } // else, + // properties are not in conflict if they refer to the same value + + } else { + newTrait[name] = pd; + } + }); + }); + + return freeze(newTrait); + } + + /* var newTrait = exclude(['name', ...], trait) + * + * @param names a list of strings denoting property names. + * @param trait a trait some properties of which should be excluded. + * @returns a new trait with the same own properties as the original trait, + * except that all property names appearing in the first argument + * are replaced by required property descriptors. + * + * Note: exclude(A, exclude(B,t)) is equivalent to exclude(A U B, t) + */ + function exclude(names, trait) { + var exclusions = makeSet(names); + var newTrait = {}; + + forEach(getOwnPropertyNames(trait), function (name) { + // required properties are not excluded but ignored + if (!hasOwnProperty(exclusions, name) || trait[name].required) { + newTrait[name] = trait[name]; + } else { + // excluded properties are replaced by required properties + newTrait[name] = makeRequiredPropDesc(name); + } + }); + + return freeze(newTrait); + } + + /** + * var newTrait = override(trait_1, trait_2, ..., trait_N) + * + * @returns a new trait with all of the combined properties of the + * argument traits. In contrast to 'compose', 'override' + * immediately resolves all conflicts resulting from this + * composition by overriding the properties of later + * traits. Trait priority is from left to right. I.e. the + * properties of the leftmost trait are never overridden. + * + * override is associative: + * override(t1,t2,t3) is equivalent to override(t1, override(t2, t3)) or + * to override(override(t1, t2), t3) + * override is not commutative: override(t1,t2) is not equivalent + * to override(t2,t1) + * + * override() returns an empty trait + * override(trait_1) returns a trait equivalent to trait_1 + */ + function override(var_args) { + var traits = slice(arguments, 0); + var newTrait = {}; + forEach(traits, function (trait) { + forEach(getOwnPropertyNames(trait), function (name) { + var pd = trait[name]; + // add this trait's property to the composite trait only if + // - the trait does not yet have this property + // - or, the trait does have the property, but it's a required property + if (!hasOwnProperty(newTrait, name) || newTrait[name].required) { + newTrait[name] = pd; + } + }); + }); + return freeze(newTrait); + } + + /** + * var newTrait = override(dominantTrait, recessiveTrait) + * + * @returns a new trait with all of the properties of dominantTrait + * and all of the properties of recessiveTrait not in dominantTrait + * + * Note: override is associative: + * override(t1, override(t2, t3)) is equivalent to + * override(override(t1, t2), t3) + */ + /*function override(frontT, backT) { + var newTrait = {}; + // first copy all of backT's properties into newTrait + forEach(getOwnPropertyNames(backT), function (name) { + newTrait[name] = backT[name]; + }); + // now override all these properties with frontT's properties + forEach(getOwnPropertyNames(frontT), function (name) { + var pd = frontT[name]; + // frontT's required property does not override the provided property + if (!(pd.required && hasOwnProperty(newTrait, name))) { + newTrait[name] = pd; + } + }); + + return freeze(newTrait); + }*/ + + /** + * var newTrait = rename(map, trait) + * + * @param map an object whose own properties serve as a mapping from + old names to new names. + * @param trait a trait object + * @returns a new trait with the same properties as the original trait, + * except that all properties whose name is an own property + * of map will be renamed to map[name], and a 'required' property + * for name will be added instead. + * + * rename({a: 'b'}, t) eqv compose(exclude(['a'],t), + * { a: { required: true }, + * b: t[a] }) + * + * For each renamed property, a required property is generated. If + * the map renames two properties to the same name, a conflict is + * generated. If the map renames a property to an existing + * unrenamed property, a conflict is generated. + * + * Note: rename(A, rename(B, t)) is equivalent to rename(\n -> + * A(B(n)), t) Note: rename({...},exclude([...], t)) is not eqv to + * exclude([...],rename({...}, t)) + */ + function rename(map, trait) { + var renamedTrait = {}; + forEach(getOwnPropertyNames(trait), function (name) { + // required props are never renamed + if (hasOwnProperty(map, name) && !trait[name].required) { + var alias = map[name]; // alias defined in map + if (hasOwnProperty(renamedTrait, alias) && + !renamedTrait[alias].required) { + // could happen if 2 props are mapped to the same alias + renamedTrait[alias] = makeConflictingPropDesc(alias); + } else { + // add the property under an alias + renamedTrait[alias] = trait[name]; + } + // add a required property under the original name + // but only if a property under the original name does not exist + // such a prop could exist if an earlier prop in the trait was + // previously aliased to this name + if (!hasOwnProperty(renamedTrait, name)) { + renamedTrait[name] = makeRequiredPropDesc(name); + } + } else { // no alias defined + if (hasOwnProperty(renamedTrait, name)) { + // could happen if another prop was previously aliased to name + if (!trait[name].required) { + renamedTrait[name] = makeConflictingPropDesc(name); + } + // else required property overridden by a previously aliased + // property and otherwise ignored + } else { + renamedTrait[name] = trait[name]; + } + } + }); + + return freeze(renamedTrait); + } + + /** + * var newTrait = resolve({ oldName: 'newName', excludeName: + * undefined, ... }, trait) + * + * This is a convenience function combining renaming and + * exclusion. It can be implemented as rename(map, + * exclude(exclusions, trait)) where map is the subset of + * mappings from oldName to newName and exclusions is an array of + * all the keys that map to undefined (or another falsy value). + * + * @param resolutions an object whose own properties serve as a + mapping from old names to new names, or to undefined if + the property should be excluded + * @param trait a trait object + * @returns a resolved trait with the same own properties as the + * original trait. + * + * In a resolved trait, all own properties whose name is an own property + * of resolutions will be renamed to resolutions[name] if it is truthy, + * or their value is changed into a required property descriptor if + * resolutions[name] is falsy. + * + * Note, it's important to _first_ exclude, _then_ rename, since exclude + * and rename are not associative, for example: + * rename({a: 'b'}, exclude(['b'], trait({ a:1,b:2 }))) eqv trait({b:1}) + * exclude(['b'], rename({a: 'b'}, trait({ a:1,b:2 }))) eqv + * trait({b:Trait.required}) + * + * writing resolve({a:'b', b: undefined},trait({a:1,b:2})) makes it + * clear that what is meant is to simply drop the old 'b' and rename + * 'a' to 'b' + */ + function resolve(resolutions, trait) { + var renames = {}; + var exclusions = []; + // preprocess renamed and excluded properties + for (var name in resolutions) { + if (hasOwnProperty(resolutions, name)) { + if (resolutions[name]) { // old name -> new name + renames[name] = resolutions[name]; + } else { // name -> undefined + exclusions.push(name); + } + } + } + return rename(renames, exclude(exclusions, trait)); + } + + /** + * var obj = create(proto, trait) + * + * @param proto denotes the prototype of the completed object + * @param trait a trait object to be turned into a complete object + * @returns an object with all of the properties described by the trait. + * @throws 'Missing required property' the trait still contains a + * required property. + * @throws 'Remaining conflicting property' if the trait still + * contains a conflicting property. + * + * Trait.create is like Object.create, except that it generates + * high-integrity or final objects. In addition to creating a new object + * from a trait, it also ensures that: + * - an exception is thrown if 'trait' still contains required properties + * - an exception is thrown if 'trait' still contains conflicting + * properties + * - the object is and all of its accessor and method properties are frozen + * - the 'this' pseudovariable in all accessors and methods of + * the object is bound to the composed object. + * + * Use Object.create instead of Trait.create if you want to create + * abstract or malleable objects. Keep in mind that for such objects: + * - no exception is thrown if 'trait' still contains required properties + * (the properties are simply dropped from the composite object) + * - no exception is thrown if 'trait' still contains conflicting + * properties (these properties remain as conflicting + * properties in the composite object) + * - neither the object nor its accessor and method properties are frozen + * - the 'this' pseudovariable in all accessors and methods of + * the object is left unbound. + */ + function create(proto, trait) { + var self = Object_create(proto); + var properties = {}; + + forEach(getOwnPropertyNames(trait), function (name) { + var pd = trait[name]; + // check for remaining 'required' properties + // Note: it's OK for the prototype to provide the properties + if (pd.required) { + if (!(name in proto)) { + throw new Error('Missing required property: '+name); + } + } else if (pd.conflict) { // check for remaining conflicting properties + throw new Error('Remaining conflicting property: '+name); + } else if ('value' in pd) { // data property + // freeze all function properties and their prototype + if (pd.method) { // the property is meant to be used as a method + // bind 'this' in trait method to the composite object + properties[name] = { + value: freezeAndBind(pd.value, self), + enumerable: pd.enumerable, + configurable: pd.configurable, + writable: pd.writable + }; + } else { + properties[name] = pd; + } + } else { // accessor property + properties[name] = { + get: pd.get ? freezeAndBind(pd.get, self) : undefined, + set: pd.set ? freezeAndBind(pd.set, self) : undefined, + enumerable: pd.enumerable, + configurable: pd.configurable, + writable: pd.writable + }; + } + }); + + defineProperties(self, properties); + return freeze(self); + } + + /** A shorthand for create(Object.prototype, trait({...}), options) */ + function object(record, options) { + return create(Object.prototype, trait(record), options); + } + + /** + * Tests whether two traits are equivalent. T1 is equivalent to T2 iff + * both describe the same set of property names and for all property + * names n, T1[n] is equivalent to T2[n]. Two property descriptors are + * equivalent if they have the same value, accessors and attributes. + * + * @return a boolean indicating whether the two argument traits are + * equivalent. + */ + function eqv(trait1, trait2) { + var names1 = getOwnPropertyNames(trait1); + var names2 = getOwnPropertyNames(trait2); + var name; + if (names1.length !== names2.length) { + return false; + } + for (var i = 0; i < names1.length; i++) { + name = names1[i]; + if (!trait2[name] || !isSameDesc(trait1[name], trait2[name])) { + return false; + } + } + return true; + } + + // if this code is ran in ES3 without an Object.create function, this + // library will define it on Object: + if (!Object.create) { + Object.create = Object_create; + } + // ES5 does not by default provide Object.getOwnProperties + // if it's not defined, the Traits library defines this utility + // function on Object + if(!Object.getOwnProperties) { + Object.getOwnProperties = getOwnProperties; + } + + // expose the public API of this module + function Trait(record) { + // calling Trait as a function creates a new atomic trait + return trait(record); + } + Trait.required = freeze(required); + Trait.compose = freeze(compose); + Trait.resolve = freeze(resolve); + Trait.override = freeze(override); + Trait.create = freeze(create); + Trait.eqv = freeze(eqv); + Trait.object = freeze(object); // not essential, cf. create + trait + return freeze(Trait); + +})(); + +if (typeof exports !== "undefined") { // CommonJS module support + exports.Trait = Trait; } \ No newline at end of file diff --git a/lib/jsctags/underscore.js b/lib/jsctags/underscore.js index 6254112..b179994 100644 --- a/lib/jsctags/underscore.js +++ b/lib/jsctags/underscore.js @@ -1,694 +1,694 @@ -// Underscore.js -// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the terms of the MIT license. -// Portions of Underscore are inspired by or borrowed from Prototype.js, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore - -(function() { - // ------------------------- Baseline setup --------------------------------- - - // Establish the root object, "window" in the browser, or "global" on the server. - var root = this; - - // Save the previous value of the "_" variable. - var previousUnderscore = root._; - - // Establish the object that gets thrown to break out of a loop iteration. - var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__'; - - // Quick regexp-escaping function, because JS doesn't have RegExp.escape(). - var escapeRegExp = function(s) { return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); }; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype; - - // Create quick reference variables for speed access to core prototypes. - var slice = ArrayProto.slice, - unshift = ArrayProto.unshift, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty, - propertyIsEnumerable = ObjProto.propertyIsEnumerable; - - // All ECMA5 native implementations we hope to use are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { return new wrapper(obj); }; - - // Export the Underscore object for CommonJS. - if (typeof exports !== 'undefined') exports._ = _; - - // Export underscore to global scope. - root._ = _; - - // Current version. - _.VERSION = '1.0.2'; - - // ------------------------ Collection Functions: --------------------------- - - // The cornerstone, an each implementation. - // Handles objects implementing forEach, arrays, and raw objects. - // Delegates to JavaScript 1.6's native forEach if available. - var each = _.forEach = function(obj, iterator, context) { - try { - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (_.isNumber(obj.length)) { - for (var i = 0, l = obj.length; i < l; i++) iterator.call(context, obj[i], i, obj); - } else { - for (var key in obj) { - if (hasOwnProperty.call(obj, key)) iterator.call(context, obj[key], key, obj); - } - } - } catch(e) { - if (e != breaker) throw e; - } - return obj; - }; - - // Return the results of applying the iterator to each element. - // Delegates to JavaScript 1.6's native map if available. - _.map = function(obj, iterator, context) { - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - var results = []; - each(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); - return results; - }; - - // Reduce builds up a single result from a list of values, aka inject, or foldl. - // Delegates to JavaScript 1.8's native reduce if available. - _.reduce = function(obj, memo, iterator, context) { - if (nativeReduce && obj.reduce === nativeReduce) return obj.reduce(_.bind(iterator, context), memo); - each(obj, function(value, index, list) { - memo = iterator.call(context, memo, value, index, list); - }); - return memo; - }; - - // The right-associative version of reduce, also known as foldr. Uses - // Delegates to JavaScript 1.8's native reduceRight if available. - _.reduceRight = function(obj, memo, iterator, context) { - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) return obj.reduceRight(_.bind(iterator, context), memo); - var reversed = _.clone(_.toArray(obj)).reverse(); - return _.reduce(reversed, memo, iterator, context); - }; - - // Return the first value which passes a truth test. - _.detect = function(obj, iterator, context) { - var result; - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - _.breakLoop(); - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to JavaScript 1.6's native filter if available. - _.filter = function(obj, iterator, context) { - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - var results = []; - each(obj, function(value, index, list) { - iterator.call(context, value, index, list) && results.push(value); - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - var results = []; - each(obj, function(value, index, list) { - !iterator.call(context, value, index, list) && results.push(value); - }); - return results; - }; - - // Determine whether all of the elements match a truth test. - // Delegates to JavaScript 1.6's native every if available. - _.every = function(obj, iterator, context) { - iterator = iterator || _.identity; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - var result = true; - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop(); - }); - return result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to JavaScript 1.6's native some if available. - _.some = function(obj, iterator, context) { - iterator = iterator || _.identity; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - var result = false; - each(obj, function(value, index, list) { - if (result = iterator.call(context, value, index, list)) _.breakLoop(); - }); - return result; - }; - - // Determine if a given value is included in the array or object using '==='. - _.include = function(obj, target) { - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - var found = false; - each(obj, function(value) { - if (found = value === target) _.breakLoop(); - }); - return found; - }; - - // Invoke a method with arguments on every item in a collection. - _.invoke = function(obj, method) { - var args = _.rest(arguments, 2); - return _.map(obj, function(value) { - return (method ? value[method] : value).apply(value, args); - }); - }; - - // Convenience version of a common use case of map: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - // Return the maximum item or (item-based computation). - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); - var result = {computed : -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); - var result = {computed : Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }), 'value'); - }; - - // Use a comparator function to figure out at what index an object should - // be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator) { - iterator = iterator || _.identity; - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >> 1; - iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; - } - return low; - }; - - // Convert anything iterable into a real, live array. - _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable)) return iterable; - if (_.isArguments(iterable)) return slice.call(iterable); - return _.values(iterable); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - return _.toArray(obj).length; - }; - - // -------------------------- Array Functions: ------------------------------ - - // Get the first element of an array. Passing "n" will return the first N - // values in the array. Aliased as "head". The "guard" check allows it to work - // with _.map. - _.first = function(array, n, guard) { - return n && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the first entry of the array. Aliased as "tail". - // Especially useful on the arguments object. Passing an "index" will return - // the rest of the values in the array from that index onward. The "guard" - //check allows it to work with _.map. - _.rest = function(array, index, guard) { - return slice.call(array, _.isUndefined(index) || guard ? 1 : index); - }; - - // Get the last element of an array. - _.last = function(array) { - return array[array.length - 1]; - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, function(value){ return !!value; }); - }; - - // Return a completely flattened version of an array. - _.flatten = function(array) { - return _.reduce(array, [], function(memo, value) { - if (_.isArray(value)) return memo.concat(_.flatten(value)); - memo.push(value); - return memo; - }); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - var values = _.rest(arguments); - return _.filter(array, function(value){ return !_.include(values, value); }); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - _.uniq = function(array, isSorted) { - return _.reduce(array, [], function(memo, el, i) { - if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); - return memo; - }); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. - _.intersect = function(array) { - var rest = _.rest(arguments); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = _.toArray(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(args, String(i)); - return results; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, MSIE), - // we need this function. Return the position of the first occurence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to JavaScript 1.8's native indexOf if available. - _.indexOf = function(array, item) { - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); - for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; - return -1; - }; - - - // Delegates to JavaScript 1.6's native lastIndexOf if available. - _.lastIndexOf = function(array, item) { - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); - var i = array.length; - while (i--) if (array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python range() function. See: - // http://docs.python.org/library/functions.html#range - _.range = function(start, stop, step) { - var a = _.toArray(arguments); - var solo = a.length <= 1; - var start = solo ? 0 : a[0], stop = solo ? a[0] : a[1], step = a[2] || 1; - var len = Math.ceil((stop - start) / step); - if (len <= 0) return []; - var range = new Array(len); - for (var i = start, idx = 0; true; i += step) { - if ((step > 0 ? i - stop : stop - i) >= 0) return range; - range[idx++] = i; - } - }; - - // ----------------------- Function Functions: ------------------------------ - - // Create a function bound to a given object (assigning 'this', and arguments, - // optionally). Binding with arguments is also known as 'curry'. - _.bind = function(func, obj) { - var args = _.rest(arguments, 2); - return function() { - return func.apply(obj || {}, args.concat(_.toArray(arguments))); - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = _.rest(arguments); - if (funcs.length == 0) funcs = _.functions(obj); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = _.rest(arguments, 2); - return setTimeout(function(){ return func.apply(func, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(_.rest(arguments))); - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func].concat(_.toArray(arguments)); - return wrapper.apply(wrapper, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = _.toArray(arguments); - return function() { - var args = _.toArray(arguments); - for (var i=funcs.length-1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // ------------------------- Object Functions: ------------------------------ - - // Retrieve the names of an object's properties. - // Delegates to ECMA5's native Object.keys - _.keys = nativeKeys || function(obj) { - if (_.isArray(obj)) return _.range(0, obj.length); - var keys = []; - for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key); - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - return _.map(obj, _.identity); - }; - - // Return a sorted list of the function names available on the object. - _.functions = function(obj) { - return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(_.rest(arguments), function(source) { - for (var prop in source) obj[prop] = source[prop]; - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (_.isArray(obj)) return obj.slice(0); - return _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - // Check object identity. - if (a === b) return true; - // Different types? - var atype = typeof(a), btype = typeof(b); - if (atype != btype) return false; - // Basic equality test (watch out for coercions). - if (a == b) return true; - // One is falsy and the other truthy. - if ((!a && b) || (a && !b)) return false; - // One of them implements an isEqual()? - if (a.isEqual) return a.isEqual(b); - // Check dates' integer values. - if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); - // Both are NaN? - if (_.isNaN(a) && _.isNaN(b)) return true; - // Compare regular expressions. - if (_.isRegExp(a) && _.isRegExp(b)) - return a.source === b.source && - a.global === b.global && - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - // If a is not an object by this point, we can't handle it. - if (atype !== 'object') return false; - // Check for different array lengths before comparing contents. - if (a.length && (a.length !== b.length)) return false; - // Nothing else worked, deep compare the contents. - var aKeys = _.keys(a), bKeys = _.keys(b); - // Different object sizes? - if (aKeys.length != bKeys.length) return false; - // Recursive comparison of contents. - for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; - return true; - }; - - // Is a given array or object empty? - _.isEmpty = function(obj) { - if (_.isArray(obj)) return obj.length === 0; - for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return !!(obj && obj.concat && obj.unshift && !obj.callee); - }; - - // Is a given variable an arguments object? - _.isArguments = function(obj) { - return obj && obj.callee; - }; - - // Is a given value a function? - _.isFunction = function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }; - - // Is a given value a string? - _.isString = function(obj) { - return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); - }; - - // Is a given value a number? - _.isNumber = function(obj) { - return (obj === +obj) || (toString.call(obj) === '[object Number]'); - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false; - }; - - // Is a given value a date? - _.isDate = function(obj) { - return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); - }; - - // Is the given value a regular expression? - _.isRegExp = function(obj) { - return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); - }; - - // Is the given value NaN -- this one is interesting. NaN != NaN, and - // isNaN(undefined) == true, so we make sure it's a number first. - _.isNaN = function(obj) { - return _.isNumber(obj) && isNaN(obj); - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return typeof obj == 'undefined'; - }; - - // -------------------------- Utility Functions: ---------------------------- - - // Run Underscore.js in noConflict mode, returning the '_' variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - // Run a function n times. - _.times = function (n, iterator, context) { - for (var i = 0; i < n; i++) iterator.call(context, i); - }; - - // Break out of the middle of an iteration. - _.breakLoop = function() { - throw breaker; - }; - - // Add your own custom functions to the Underscore object, ensuring that - // they're correctly added to the OOP wrapper as well. - _.mixin = function(obj) { - each(_.functions(obj), function(name){ - addToWrapper(name, _[name] = obj[name]); - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = idCounter++; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - start : '<%', - end : '%>', - interpolate : /<%=(.+?)%>/g - }; - - // JavaScript templating a-la ERB, pilfered from John Resig's - // "Secrets of the JavaScript Ninja", page 83. - // Single-quote fix from Rick Strahl's version. - // With alterations for arbitrary delimiters. - _.template = function(str, data) { - var c = _.templateSettings; - var endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g"); - var fn = new Function('obj', - 'var p=[],print=function(){p.push.apply(p,arguments);};' + - 'with(obj){p.push(\'' + - str.replace(/[\r\t\n]/g, " ") - .replace(endMatch,"\t") - .split("'").join("\\'") - .split("\t").join("'") - .replace(c.interpolate, "',$1,'") - .split(c.start).join("');") - .split(c.end).join("p.push('") - + "');}return p.join('');"); - return data ? fn(data) : fn; - }; - - // ------------------------------- Aliases ---------------------------------- - - _.each = _.forEach; - _.foldl = _.inject = _.reduce; - _.foldr = _.reduceRight; - _.select = _.filter; - _.all = _.every; - _.any = _.some; - _.head = _.first; - _.tail = _.rest; - _.methods = _.functions; - - // ------------------------ Setup the OOP Wrapper: -------------------------- - - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - - // Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - // A method to easily add functions to the OOP wrapper. - var addToWrapper = function(name, func) { - wrapper.prototype[name] = function() { - var args = _.toArray(arguments); - unshift.call(args, this._wrapped); - return result(func.apply(_, args), this._chain); - }; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - method.apply(this._wrapped, arguments); - return result(this._wrapped, this._chain); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); - }; - }); - - // Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; - - // Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; - -})(); +// Underscore.js +// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the terms of the MIT license. +// Portions of Underscore are inspired by or borrowed from Prototype.js, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore + +(function () { + // ------------------------- Baseline setup --------------------------------- + + // Establish the root object, "window" in the browser, or "global" on the server. + var root = this; + + // Save the previous value of the "_" variable. + var previousUnderscore = root._; + + // Establish the object that gets thrown to break out of a loop iteration. + var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__'; + + // Quick regexp-escaping function, because JS doesn't have RegExp.escape(). + var escapeRegExp = function (s) { return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); }; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype; + + // Create quick reference variables for speed access to core prototypes. + var slice = ArrayProto.slice, + unshift = ArrayProto.unshift, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty, + propertyIsEnumerable = ObjProto.propertyIsEnumerable; + + // All ECMA5 native implementations we hope to use are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys; + + // Create a safe reference to the Underscore object for use below. + var _ = function (obj) { return new wrapper(obj); }; + + // Export the Underscore object for CommonJS. + if (typeof exports !== 'undefined') exports._ = _; + + // Export underscore to global scope. + root._ = _; + + // Current version. + _.VERSION = '1.0.2'; + + // ------------------------ Collection Functions: --------------------------- + + // The cornerstone, an each implementation. + // Handles objects implementing forEach, arrays, and raw objects. + // Delegates to JavaScript 1.6's native forEach if available. + var each = _.forEach = function (obj, iterator, context) { + try { + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (_.isNumber(obj.length)) { + for (var i = 0, l = obj.length; i < l; i++) iterator.call(context, obj[i], i, obj); + } else { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) iterator.call(context, obj[key], key, obj); + } + } + } catch(e) { + if (e != breaker) throw e; + } + return obj; + }; + + // Return the results of applying the iterator to each element. + // Delegates to JavaScript 1.6's native map if available. + _.map = function (obj, iterator, context) { + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + var results = []; + each(obj, function (value, index, list) { + results.push(iterator.call(context, value, index, list)); + }); + return results; + }; + + // Reduce builds up a single result from a list of values, aka inject, or foldl. + // Delegates to JavaScript 1.8's native reduce if available. + _.reduce = function (obj, memo, iterator, context) { + if (nativeReduce && obj.reduce === nativeReduce) return obj.reduce(_.bind(iterator, context), memo); + each(obj, function (value, index, list) { + memo = iterator.call(context, memo, value, index, list); + }); + return memo; + }; + + // The right-associative version of reduce, also known as foldr. Uses + // Delegates to JavaScript 1.8's native reduceRight if available. + _.reduceRight = function (obj, memo, iterator, context) { + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) return obj.reduceRight(_.bind(iterator, context), memo); + var reversed = _.clone(_.toArray(obj)).reverse(); + return _.reduce(reversed, memo, iterator, context); + }; + + // Return the first value which passes a truth test. + _.detect = function (obj, iterator, context) { + var result; + each(obj, function (value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + _.breakLoop(); + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to JavaScript 1.6's native filter if available. + _.filter = function (obj, iterator, context) { + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + var results = []; + each(obj, function (value, index, list) { + iterator.call(context, value, index, list) && results.push(value); + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function (obj, iterator, context) { + var results = []; + each(obj, function (value, index, list) { + !iterator.call(context, value, index, list) && results.push(value); + }); + return results; + }; + + // Determine whether all of the elements match a truth test. + // Delegates to JavaScript 1.6's native every if available. + _.every = function (obj, iterator, context) { + iterator = iterator || _.identity; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + var result = true; + each(obj, function (value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop(); + }); + return result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to JavaScript 1.6's native some if available. + _.some = function (obj, iterator, context) { + iterator = iterator || _.identity; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + var result = false; + each(obj, function (value, index, list) { + if (result = iterator.call(context, value, index, list)) _.breakLoop(); + }); + return result; + }; + + // Determine if a given value is included in the array or object using '==='. + _.include = function (obj, target) { + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + var found = false; + each(obj, function (value) { + if (found = value === target) _.breakLoop(); + }); + return found; + }; + + // Invoke a method with arguments on every item in a collection. + _.invoke = function (obj, method) { + var args = _.rest(arguments, 2); + return _.map(obj, function (value) { + return (method ? value[method] : value).apply(value, args); + }); + }; + + // Convenience version of a common use case of map: fetching a property. + _.pluck = function (obj, key) { + return _.map(obj, function (value){ return value[key]; }); + }; + + // Return the maximum item or (item-based computation). + _.max = function (obj, iterator, context) { + if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); + var result = {computed : -Infinity}; + each(obj, function (value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function (obj, iterator, context) { + if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); + var result = {computed : Infinity}; + each(obj, function (value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function (obj, iterator, context) { + return _.pluck(_.map(obj, function (value, index, list) { + return { + value : value, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function (left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }), 'value'); + }; + + // Use a comparator function to figure out at what index an object should + // be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function (array, obj, iterator) { + iterator = iterator || _.identity; + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >> 1; + iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; + } + return low; + }; + + // Convert anything iterable into a real, live array. + _.toArray = function (iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + if (_.isArray(iterable)) return iterable; + if (_.isArguments(iterable)) return slice.call(iterable); + return _.values(iterable); + }; + + // Return the number of elements in an object. + _.size = function (obj) { + return _.toArray(obj).length; + }; + + // -------------------------- Array Functions: ------------------------------ + + // Get the first element of an array. Passing "n" will return the first N + // values in the array. Aliased as "head". The "guard" check allows it to work + // with _.map. + _.first = function (array, n, guard) { + return n && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the first entry of the array. Aliased as "tail". + // Especially useful on the arguments object. Passing an "index" will return + // the rest of the values in the array from that index onward. The "guard" + //check allows it to work with _.map. + _.rest = function (array, index, guard) { + return slice.call(array, _.isUndefined(index) || guard ? 1 : index); + }; + + // Get the last element of an array. + _.last = function (array) { + return array[array.length - 1]; + }; + + // Trim out all falsy values from an array. + _.compact = function (array) { + return _.filter(array, function (value){ return !!value; }); + }; + + // Return a completely flattened version of an array. + _.flatten = function (array) { + return _.reduce(array, [], function (memo, value) { + if (_.isArray(value)) return memo.concat(_.flatten(value)); + memo.push(value); + return memo; + }); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function (array) { + var values = _.rest(arguments); + return _.filter(array, function (value){ return !_.include(values, value); }); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + _.uniq = function (array, isSorted) { + return _.reduce(array, [], function (memo, el, i) { + if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); + return memo; + }); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersect = function (array) { + var rest = _.rest(arguments); + return _.filter(_.uniq(array), function (item) { + return _.every(rest, function (other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function () { + var args = _.toArray(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) results[i] = _.pluck(args, String(i)); + return results; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, MSIE), + // we need this function. Return the position of the first occurence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to JavaScript 1.8's native indexOf if available. + _.indexOf = function (array, item) { + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); + for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + + // Delegates to JavaScript 1.6's native lastIndexOf if available. + _.lastIndexOf = function (array, item) { + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); + var i = array.length; + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python range() function. See: + // http://docs.python.org/library/functions.html#range + _.range = function (start, stop, step) { + var a = _.toArray(arguments); + var solo = a.length <= 1; + var start = solo ? 0 : a[0], stop = solo ? a[0] : a[1], step = a[2] || 1; + var len = Math.ceil((stop - start) / step); + if (len <= 0) return []; + var range = new Array(len); + for (var i = start, idx = 0; true; i += step) { + if ((step > 0 ? i - stop : stop - i) >= 0) return range; + range[idx++] = i; + } + }; + + // ----------------------- Function Functions: ------------------------------ + + // Create a function bound to a given object (assigning 'this', and arguments, + // optionally). Binding with arguments is also known as 'curry'. + _.bind = function (func, obj) { + var args = _.rest(arguments, 2); + return function () { + return func.apply(obj || {}, args.concat(_.toArray(arguments))); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function (obj) { + var funcs = _.rest(arguments); + if (funcs.length == 0) funcs = _.functions(obj); + each(funcs, function (f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function (func, wait) { + var args = _.rest(arguments, 2); + return setTimeout(function (){ return func.apply(func, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function (func) { + return _.delay.apply(_, [func, 1].concat(_.rest(arguments))); + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function (func, wrapper) { + return function () { + var args = [func].concat(_.toArray(arguments)); + return wrapper.apply(wrapper, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function () { + var funcs = _.toArray(arguments); + return function () { + var args = _.toArray(arguments); + for (var i=funcs.length-1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // ------------------------- Object Functions: ------------------------------ + + // Retrieve the names of an object's properties. + // Delegates to ECMA5's native Object.keys + _.keys = nativeKeys || function (obj) { + if (_.isArray(obj)) return _.range(0, obj.length); + var keys = []; + for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key); + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function (obj) { + return _.map(obj, _.identity); + }; + + // Return a sorted list of the function names available on the object. + _.functions = function (obj) { + return _.filter(_.keys(obj), function (key){ return _.isFunction(obj[key]); }).sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function (obj) { + each(_.rest(arguments), function (source) { + for (var prop in source) obj[prop] = source[prop]; + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function (obj) { + if (_.isArray(obj)) return obj.slice(0); + return _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain. + _.tap = function (obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function (a, b) { + // Check object identity. + if (a === b) return true; + // Different types? + var atype = typeof(a), btype = typeof(b); + if (atype != btype) return false; + // Basic equality test (watch out for coercions). + if (a == b) return true; + // One is falsy and the other truthy. + if ((!a && b) || (a && !b)) return false; + // One of them implements an isEqual()? + if (a.isEqual) return a.isEqual(b); + // Check dates' integer values. + if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); + // Both are NaN? + if (_.isNaN(a) && _.isNaN(b)) return true; + // Compare regular expressions. + if (_.isRegExp(a) && _.isRegExp(b)) + return a.source === b.source && + a.global === b.global && + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + // If a is not an object by this point, we can't handle it. + if (atype !== 'object') return false; + // Check for different array lengths before comparing contents. + if (a.length && (a.length !== b.length)) return false; + // Nothing else worked, deep compare the contents. + var aKeys = _.keys(a), bKeys = _.keys(b); + // Different object sizes? + if (aKeys.length != bKeys.length) return false; + // Recursive comparison of contents. + for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; + return true; + }; + + // Is a given array or object empty? + _.isEmpty = function (obj) { + if (_.isArray(obj)) return obj.length === 0; + for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function (obj) { + return !!(obj && obj.nodeType == 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function (obj) { + return !!(obj && obj.concat && obj.unshift && !obj.callee); + }; + + // Is a given variable an arguments object? + _.isArguments = function (obj) { + return obj && obj.callee; + }; + + // Is a given value a function? + _.isFunction = function (obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + }; + + // Is a given value a string? + _.isString = function (obj) { + return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); + }; + + // Is a given value a number? + _.isNumber = function (obj) { + return (obj === +obj) || (toString.call(obj) === '[object Number]'); + }; + + // Is a given value a boolean? + _.isBoolean = function (obj) { + return obj === true || obj === false; + }; + + // Is a given value a date? + _.isDate = function (obj) { + return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); + }; + + // Is the given value a regular expression? + _.isRegExp = function (obj) { + return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); + }; + + // Is the given value NaN -- this one is interesting. NaN != NaN, and + // isNaN(undefined) == true, so we make sure it's a number first. + _.isNaN = function (obj) { + return _.isNumber(obj) && isNaN(obj); + }; + + // Is a given value equal to null? + _.isNull = function (obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function (obj) { + return typeof obj == 'undefined'; + }; + + // -------------------------- Utility Functions: ---------------------------- + + // Run Underscore.js in noConflict mode, returning the '_' variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function () { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function (value) { + return value; + }; + + // Run a function n times. + _.times = function (n, iterator, context) { + for (var i = 0; i < n; i++) iterator.call(context, i); + }; + + // Break out of the middle of an iteration. + _.breakLoop = function () { + throw breaker; + }; + + // Add your own custom functions to the Underscore object, ensuring that + // they're correctly added to the OOP wrapper as well. + _.mixin = function (obj) { + each(_.functions(obj), function (name){ + addToWrapper(name, _[name] = obj[name]); + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function (prefix) { + var id = idCounter++; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + start : '<%', + end : '%>', + interpolate : /<%=(.+?)%>/g + }; + + // JavaScript templating a-la ERB, pilfered from John Resig's + // "Secrets of the JavaScript Ninja", page 83. + // Single-quote fix from Rick Strahl's version. + // With alterations for arbitrary delimiters. + _.template = function (str, data) { + var c = _.templateSettings; + var endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g"); + var fn = new Function('obj', + 'var p=[],print=function (){p.push.apply(p,arguments);};' + + 'with(obj){p.push(\'' + + str.replace(/[\r\t\n]/g, " ") + .replace(endMatch,"\t") + .split("'").join("\\'") + .split("\t").join("'") + .replace(c.interpolate, "',$1,'") + .split(c.start).join("');") + .split(c.end).join("p.push('") + + "');}return p.join('');"); + return data ? fn(data) : fn; + }; + + // ------------------------------- Aliases ---------------------------------- + + _.each = _.forEach; + _.foldl = _.inject = _.reduce; + _.foldr = _.reduceRight; + _.select = _.filter; + _.all = _.every; + _.any = _.some; + _.head = _.first; + _.tail = _.rest; + _.methods = _.functions; + + // ------------------------ Setup the OOP Wrapper: -------------------------- + + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + var wrapper = function (obj) { this._wrapped = obj; }; + + // Helper function to continue chaining intermediate results. + var result = function (obj, chain) { + return chain ? _(obj).chain() : obj; + }; + + // A method to easily add functions to the OOP wrapper. + var addToWrapper = function (name, func) { + wrapper.prototype[name] = function () { + var args = _.toArray(arguments); + unshift.call(args, this._wrapped); + return result(func.apply(_, args), this._chain); + }; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function (name) { + var method = ArrayProto[name]; + wrapper.prototype[name] = function () { + method.apply(this._wrapped, arguments); + return result(this._wrapped, this._chain); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function (name) { + var method = ArrayProto[name]; + wrapper.prototype[name] = function () { + return result(method.apply(this._wrapped, arguments), this._chain); + }; + }); + + // Start chaining a wrapped Underscore object. + wrapper.prototype.chain = function () { + this._chain = true; + return this; + }; + + // Extracts the result from a wrapped and chained object. + wrapper.prototype.value = function () { + return this._wrapped; + }; + +})();