commit 325c322a276d03c7de50233aef63598c7dd8c308 Author: Peter 'Pita' Martischka Date: Sat Mar 26 13:10:41 2011 +0000 first-commit diff --git a/bin/generateJsDoc.sh b/bin/generateJsDoc.sh new file mode 100755 index 000000000..aff696f83 --- /dev/null +++ b/bin/generateJsDoc.sh @@ -0,0 +1,16 @@ +#!/bin/sh +if [ ! -x /usr/bin/java ]; then + echo "You need to install Java to generate the JSDocs!" + exit 1 +fi + +cd "../doc/jsdoc-toolkit" + +JSRUN="jsrun.jar" +RUNJS="app/run.js" +OUTPUT_DIR="../jsdoc" +NODE_DIR="../../node" +TEMPLATE_DIR="templates/jsdoc" + +java -jar $JSRUN $RUNJS -v -d=$OUTPUT_DIR -t=$TEMPLATE_DIR $NODE_DIR && +echo "Look on http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs to get Tipps for better documentation" diff --git a/bin/run.sh b/bin/run.sh new file mode 100755 index 000000000..dd45a9268 --- /dev/null +++ b/bin/run.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +#if [[ $EUID -eq 0 ]]; then +# echo "You shouldn't start LinePad as root!" 1>&2 +# exit 1 +#fi + +#if [ ! type -P node &>/dev/null ]; then +# echo "You have no node installed!" 1>&2 +# exit 1 +#fi +#|| { echo "I require foo but it's not installed. Aborting." >&2; exit 1; } + +cd ../node +authbind node server.js diff --git a/bin/runTests.sh b/bin/runTests.sh new file mode 100755 index 000000000..68ebde89a --- /dev/null +++ b/bin/runTests.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +type -P nodeunit &>/dev/null || { + echo "You need to install Nodeunit to run the tests!" >&2 + echo "You can install it with npm" >&2 + echo "Run: npm install nodeunit" >&2 + exit 1 +} + +nodeunit ../tests diff --git a/doc/easysync/easysync-notes.txt b/doc/easysync/easysync-notes.txt new file mode 100644 index 000000000..d3b3dc558 --- /dev/null +++ b/doc/easysync/easysync-notes.txt @@ -0,0 +1,133 @@ + + +Copied from the old Etherpad. Found in /infrastructure/ace/ + +Goals: + +- no unicode (for efficient escaping, sightliness) +- efficient operations for ACE and collab (attributed text, etc.) +- good for time-slider +- good for API +- line-ending aware +X more coherent (deleting or styling text merging with insertion) +- server-side syntax highlighting? +- unify author map with attribute pool +- unify attributed text with changeset rep +- not: reversible +- force final newline of document to be preserved + +- Unicode bad: + - ugly (hard to read) + - more complex to parse + - harder to store and transmit correctly + - doesn't save all that much space anyway + - blows up in size when string-escaped + - embarrassing for API + + +# Attributes: + +An "attribute" is a (key,value) pair such as (author,abc123456) or +(bold,true). Sometimes an attribute is treated as an instruction to +add that attribute, in which case an empty value means to remove it. +So (bold,) removes the "bold" attribute. Attributes are interned and +given numeric IDs, so the number "6" could represent "(bold,true)", +for example. This mapping is stored in an attribute "pool" which may +be shared by multiple changesets. + +Entries in the pool must be unique, so that attributes can be compared +by their IDs. Attribute names cannot contain commas. + +A changeset looks something like the following: + +Z:5g>1|5=2p=v*4*5+1$x + +With the corresponding pool containing these entries: + +... +4 -> (author,1059348573) +5 -> (bold,true) +... + +This changeset, together with the pool, represents inserting +a bold letter "x" into the middle of a line. The string consists of: + +- a letter Z (the "magic character" and format version identifier) +- a series of opcodes (punctuation) and numeric values in base 36 (the + alphanumerics) +- a dollar sign ($) +- a string of characters used by insertion operations (the "char bank") + +If we separate out the operations and convert the numbers to base 10, we get: + +Z :196 >1 |5=97 =31 *4 *5 +1 $"x" + +Here are descriptions of the operations, where capital letters are variables: + +":N" : Source text has length N (must be first op) +">N" : Final text is N (positive) characters longer than source text (must be second op) +"0" : Final text is same length as source text +"+N" : Insert N characters from the bank, none of them newlines +"-N" : Skip over (delete) N characters from the source text, none of them newlines +"=N" : Keep N characters from the source text, none of them newlines +"|L+N" : Insert N characters from the source text, containing L newlines. The last + character inserted MUST be a newline, but not the (new) document's final newline. +"|L-N" : Delete N characters from the source text, containing L newlines. The last + character inserted MUST be a newline, but not the (old) document's final newline. +"|L=N" : Keep N characters from the source text, containing L newlines. The last character + kept MUST be a newline, and the final newline of the document is allowed. +"*I" : Apply attribute I from the pool to the following +, =, |+, or |= command. + In other words, any number of * ops can come before a +, =, or | but not + between a | and the corresponding + or =. + If +, text is inserted having this attribute. If =, text is kept but with + the attribute applied as an attribute addition or removal. + Consecutive attributes must be sorted lexically by (key,value) with key + and value taken as strings. It's illegal to have duplicate keys + for (key,value) pairs that apply to the same text. It's illegal to + have an empty value for a key in the case of an insertion (+), the + pair should just be omitted. + +Characters from the source text that aren't accounted for are assumed to be kept +with the same attributes. + +Additional Constraints: + +- Consecutive +, -, and = ops of the same type that could be combined are not allowed. + Whether combination is possible depends on the attributes of the ops and whether + each is multiline or not. For example, two multiline deletions can never be + consecutive, nor can any insertion come after a non-multiline insertion with the + same attributes. +- "No-op" ops are not allowed, such as deleting 0 characters. However, attribute + applications that don't have any effect are allowed. +- Characters at the end of the source text cannot be explicitly kept with no changes; + if the change doesn't affect the last N characters, those "keep" ops must be left off. +- In any consecutive sequence of insertions (+) and deletions (-) with no keeps (=), + the deletions must come before the insertions. +- The document text before and after will always end with a newline. This policy avoids + a lot of special-casing of the end of the document. If a final newline is + always added when importing text and removed when exporting text, then the + changeset representation can be used to process text files that may or may not + have a final newline. + +Attribution string: + +An "attribution string" is a series of inserts with no deletions or keeps. +For example, "*3+8|1+5" describes the attributes of a string of length 13, +where the first 8 chars have attribute 3 and the next 5 chars have no +attributes, with the last of these 5 chars being a newline. Constraints +apply similar to those affecting changesets, but the restriction about +the final newline of the new document being added doesn't apply. + +Attributes in an attribution string cannot be empty, like "(bold,)", they should +instead be absent. + + + + + +------- +Considerations: + +- composing changesets/attributions with different pools +- generalizing "applyToAttribution" to make "mutateAttributionLines" and "compose" diff --git a/doc/easysync/easysync.odt b/doc/easysync/easysync.odt new file mode 100644 index 000000000..37a99f7db Binary files /dev/null and b/doc/easysync/easysync.odt differ diff --git a/doc/jsdoc-toolkit/README.txt b/doc/jsdoc-toolkit/README.txt new file mode 100644 index 000000000..3782da88d --- /dev/null +++ b/doc/jsdoc-toolkit/README.txt @@ -0,0 +1,183 @@ +====================================================================== + +DESCRIPTION: + +This is the source code for JsDoc Toolkit, an automatic documentation +generation tool for JavaScript. It is written in JavaScript and is run +from a command line (or terminal) using Java and Mozilla's Rhino +JavaScript runtime engine. + +Using this tool you can automatically turn JavaDoc-like comments in +your JavaScript source code into published output files, such as HTML +or XML. + +For more information, to report a bug, or to browse the technical +documentation for this tool please visit the official JsDoc Toolkit +project homepage at http://code.google.com/p/jsdoc-toolkit/ + +For the most up-to-date documentation on JsDoc Toolkit see the +official wiki at http://code.google.com/p/jsdoc-toolkit/w/list + +====================================================================== + +REQUIREMENTS: + +JsDoc Toolkit is known to work with: +java version "1.6.0_03" +Java(TM) SE Runtime Environment (build 1.6.0_03-b05) +on Windows XP, +and java version "1.5.0_19" +Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_19-b02-304) +on Mac OS X 10.5. + +Other versions of java may or may not work with JsDoc Toolkit. + +====================================================================== + +USAGE: + +Running JsDoc Toolkit requires you to have Java installed on your +computer. For more information see http://www.java.com/getjava/ + +Before running the JsDoc Toolkit app you should change your current +working directory to the jsdoc-toolkit folder. Then follow the +examples below, or as shown on the project wiki. + +On a computer running Windows a valid command line to run JsDoc +Toolkit might look like this: + +> java -jar jsrun.jar app\run.js -a -t=templates\jsdoc mycode.js + +On Mac OS X or Linux the same command would look like this: + +$ java -jar jsrun.jar app/run.js -a -t=templates/jsdoc mycode.js + +The above assumes your current working directory contains jsrun.jar, +the "app" and "templates" subdirectories from the standard JsDoc +Toolkit distribution and that the relative path to the code you wish +to document is "mycode.js". + +The output documentation files will be saved to a new directory named +"out" (by default) in the current directory, or if you specify a +-d=somewhere_else option, to the somewhere_else directory. + +For help (usage notes) enter this on the command line: + +$ java -jar jsrun.jar app/run.js --help + +More information about the various command line options used by JsDoc +Toolkit are available on the project wiki. + +====================================================================== + +RUNNING VIA SHELL SCRIPT + +Avi Deitcher has contributed the file jsrun.sh with the following usage notes: + +A script to simplify running jsdoc from the command-line, especially when +running from within a development or build environment such as ant. + +Normally, to run jsdoc, you need a command-line as the following: +java -Djsdoc.dir=/some/long/dir/path/to/jsdoc -jar +/some/long/dir/path/to/jsdoc/jsrun.jar /some/long/dir/path/to/jsdoc/app/run.js +-t=template -r=4 /some/long/dir/path/to/my/src/code + +This can get tedious to redo time and again, and difficult to use from within a build environment. + +To simplify the process, jsrun.sh will automatically run this path, as well as passing through any arguments. + +Usage: jsrun.sh + +All will be passed through. +Additionally, jsrun.sh will take the following actions: +1) If the environment variable JSDOCDIR is set, it will add +"-Djsdoc.dir=$JSDOCDIR" to the command-line +2) If the environment variable JSDOCTEMPLATEDIR is set, it will add +"-Djsdoc.template.dir=$JSDOCTEMPLATEDIR" to the command-line +3) java with the appropriate path to jsrun.jar and run.js will be instantiated + +If not variables are set, it is assumed that the path to jsrun.jar and app/ is in the current working directory. + +Example: +# jsrun.sh ./src/ +Assuming JSDOCDIR=/some/path/to/my/jsdoc will cause the following command to +execute: +java -Djsdoc.dir=/some/path/to/my/jsdoc -jar /some/path/to/my/jsdoc/jsrun.jar +/some/path/to/my/jsdoc/app/run.js ./src/ + +====================================================================== + +TESTING: + +To run the suite of unit tests included with JsDoc Toolkit enter this +on the command line: + +$ java -jar jsrun.jar app/run.js -T + +To see a dump of the internal data structure that JsDoc Toolkit has +built from your source files use this command: + +$ java -jar jsrun.jar app/run.js mycode.js -Z + +====================================================================== + +LICENSE: + +JSDoc.pm + +This project is based on the JSDoc.pm tool, created by Michael +Mathews and Gabriel Reid. More information on JsDoc.pm can +be found on the JSDoc.pm homepage: http://jsdoc.sourceforge.net/ + +Complete documentation on JsDoc Toolkit can be found on the project +wiki at http://code.google.com/p/jsdoc-toolkit/w/list + +Rhino + +Rhino (JavaScript in Java) is open source and licensed by Mozilla +under the MPL 1.1 or later/GPL 2.0 or later licenses, the text of +which is available at http://www.mozilla.org/MPL/ + +You can obtain the source code for Rhino from the Mozilla web site at +http://www.mozilla.org/rhino/download.html + +JsDoc Toolkit is a larger work that uses the Rhino JavaScript engine +but is not derived from it in any way. The Rhino library is used +without modification and without any claims whatsoever. + +The Rhino Debugger + +You can obtain more information about the Rhino Debugger from the +Mozilla web site at http://www.mozilla.org/rhino/debugger.html + +JsDoc Toolkit is a larger work that uses the Rhino Debugger but +is not derived from it in any way. The Rhino Debugger is used +without modification and without any claims whatsoever. + +JsDoc Toolkit + +All code specific to JsDoc Toolkit are free, open source and licensed +for use under the X11/MIT License. + +JsDoc Toolkit is Copyright (c)2009 Michael Mathews + +This program is free software; you can redistribute it and/or +modify it under the terms below. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: The above copyright notice and this +permission notice must be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/doc/jsdoc-toolkit/app/frame.js b/doc/jsdoc-toolkit/app/frame.js new file mode 100644 index 000000000..1beb40559 --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame.js @@ -0,0 +1,33 @@ +IO.include("frame/Opt.js"); +IO.include("frame/Chain.js"); +IO.include("frame/Link.js"); +IO.include("frame/String.js"); +IO.include("frame/Hash.js"); +IO.include("frame/Namespace.js"); +//IO.include("frame/Reflection.js"); + +/** A few helper functions to make life a little easier. */ + +function defined(o) { + return (o !== undefined); +} + +function copy(o) { // todo check for circular refs + if (o == null || typeof(o) != 'object') return o; + var c = new o.constructor(); + for(var p in o) c[p] = copy(o[p]); + return c; +} + +function isUnique(arr) { + var l = arr.length; + for(var i = 0; i < l; i++ ) { + if (arr.lastIndexOf(arr[i]) > i) return false; + } + return true; +} + +/** Returns the given string with all regex meta characters backslashed. */ +RegExp.escapeMeta = function(str) { + return str.replace(/([$^\\\/()|?+*\[\]{}.-])/g, "\\$1"); +} diff --git a/doc/jsdoc-toolkit/app/frame/Chain.js b/doc/jsdoc-toolkit/app/frame/Chain.js new file mode 100644 index 000000000..506469d18 --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame/Chain.js @@ -0,0 +1,102 @@ +/**@constructor*/ +function ChainNode(object, link) { + this.value = object; + this.link = link; // describes this node's relationship to the previous node +} + +/**@constructor*/ +function Chain(valueLinks) { + this.nodes = []; + this.cursor = -1; + + if (valueLinks && valueLinks.length > 0) { + this.push(valueLinks[0], "//"); + for (var i = 1, l = valueLinks.length; i < l; i+=2) { + this.push(valueLinks[i+1], valueLinks[i]); + } + } +} + +Chain.prototype.push = function(o, link) { + if (this.nodes.length > 0 && link) this.nodes.push(new ChainNode(o, link)); + else this.nodes.push(new ChainNode(o)); +} + +Chain.prototype.unshift = function(o, link) { + if (this.nodes.length > 0 && link) this.nodes[0].link = link; + this.nodes.unshift(new ChainNode(o)); + this.cursor++; +} + +Chain.prototype.get = function() { + if (this.cursor < 0 || this.cursor > this.nodes.length-1) return null; + return this.nodes[this.cursor]; +} + +Chain.prototype.first = function() { + this.cursor = 0; + return this.get(); +} + +Chain.prototype.last = function() { + this.cursor = this.nodes.length-1; + return this.get(); +} + +Chain.prototype.next = function() { + this.cursor++; + return this.get(); +} + +Chain.prototype.prev = function() { + this.cursor--; + return this.get(); +} + +Chain.prototype.toString = function() { + var string = ""; + for (var i = 0, l = this.nodes.length; i < l; i++) { + if (this.nodes[i].link) string += " -("+this.nodes[i].link+")-> "; + string += this.nodes[i].value.toString(); + } + return string; +} + +Chain.prototype.joinLeft = function() { + var result = ""; + for (var i = 0, l = this.cursor; i < l; i++) { + if (result && this.nodes[i].link) result += this.nodes[i].link; + result += this.nodes[i].value.toString(); + } + return result; +} + + +/* USAGE: + +var path = "one/two/three.four/five-six"; +var pathChain = new Chain(path.split(/([\/.-])/)); +print(pathChain); + +var lineage = new Chain(); +lineage.push("Port"); +lineage.push("Les", "son"); +lineage.push("Dawn", "daughter"); +lineage.unshift("Purdie", "son"); + +print(lineage); + +// walk left +for (var node = lineage.last(); node !== null; node = lineage.prev()) { + print("< "+node.value); +} + +// walk right +var node = lineage.first() +while (node !== null) { + print(node.value); + node = lineage.next(); + if (node && node.link) print("had a "+node.link+" named"); +} + +*/ \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/frame/Dumper.js b/doc/jsdoc-toolkit/app/frame/Dumper.js new file mode 100644 index 000000000..d8b007b1d --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame/Dumper.js @@ -0,0 +1,144 @@ +/** + * @class +
+This is a lightly modified version of Kevin Jones' JavaScript
+library Data.Dump. To download the original visit:
+    http://openjsan.org/doc/k/ke/kevinj/Data/Dump/
+
+AUTHORS
+
+The Data.Dump JavaScript module is written by Kevin Jones 
+(kevinj@cpan.org), based on Data::Dump by Gisle Aas (gisle@aas.no),
+based on Data::Dumper by Gurusamy Sarathy (gsar@umich.edu).
+
+COPYRIGHT
+
+Copyright 2007 Kevin Jones. Copyright 1998-2000,2003-2004 Gisle Aas.
+Copyright 1996-1998 Gurusamy Sarathy.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the Perl Artistic License
+
+See http://www.perl.com/perl/misc/Artistic.html
+
+ * @static + */ +Dumper = { + /** @param [...] The objects to dump. */ + dump: function () { + if (arguments.length > 1) + return this._dump(arguments); + else if (arguments.length == 1) + return this._dump(arguments[0]); + else + return "()"; + }, + + _dump: function (obj) { + if (typeof obj == 'undefined') return 'undefined'; + var out; + if (obj.serialize) { return obj.serialize(); } + var type = this._typeof(obj); + if (obj.circularReference) obj.circularReference++; + switch (type) { + case 'circular': + out = "{ //circularReference\n}"; + break; + case 'object': + var pairs = new Array; + + for (var prop in obj) { + if (prop != "circularReference" && obj.hasOwnProperty(prop)) { //hide inherited properties + pairs.push(prop + ': ' + this._dump(obj[prop])); + } + } + + out = '{' + this._format_list(pairs) + '}'; + break; + + case 'string': + for (var prop in Dumper.ESC) { + if (Dumper.ESC.hasOwnProperty(prop)) { + obj = obj.replace(prop, Dumper.ESC[prop]); + } + } + + // Escape UTF-8 Strings + if (obj.match(/^[\x00-\x7f]*$/)) { + out = '"' + obj.replace(/\"/g, "\\\"").replace(/([\n\r]+)/g, "\\$1") + '"'; + } + else { + out = "unescape('"+escape(obj)+"')"; + } + break; + + case 'array': + var elems = new Array; + + for (var i=0; i 60 ? '\n' : ' '; + return nl + list.join(',' + nl) + nl; + }, + + _typeof: function (obj) { + if (obj && obj.circularReference && obj.circularReference > 1) return 'circular'; + if (Array.prototype.isPrototypeOf(obj)) return 'array'; + if (Date.prototype.isPrototypeOf(obj)) return 'date'; + if (typeof obj.nodeType != 'undefined') return 'element'; + return typeof(obj); + }, + + _dump_dom: function (obj) { + return '"' + Dumper.nodeTypes[obj.nodeType] + '"'; + } +}; + +Dumper.ESC = { + "\t": "\\t", + "\n": "\\n", + "\f": "\\f" +}; + +Dumper.nodeTypes = { + 1: "ELEMENT_NODE", + 2: "ATTRIBUTE_NODE", + 3: "TEXT_NODE", + 4: "CDATA_SECTION_NODE", + 5: "ENTITY_REFERENCE_NODE", + 6: "ENTITY_NODE", + 7: "PROCESSING_INSTRUCTION_NODE", + 8: "COMMENT_NODE", + 9: "DOCUMENT_NODE", + 10: "DOCUMENT_TYPE_NODE", + 11: "DOCUMENT_FRAGMENT_NODE", + 12: "NOTATION_NODE" +}; \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/frame/Hash.js b/doc/jsdoc-toolkit/app/frame/Hash.js new file mode 100644 index 000000000..62cfad646 --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame/Hash.js @@ -0,0 +1,84 @@ +/** + @constructor + @example + var _index = new Hash(); + _index.set("a", "apple"); + _index.set("b", "blue"); + _index.set("c", "coffee"); + + for (var p = _index.first(); p; p = _index.next()) { + print(p.key+" is for "+p.value); + } + + */ +var Hash = function() { + this._map = {}; + this._keys = []; + this._vals = []; + this.reset(); +} + +Hash.prototype.set = function(k, v) { + if (k != "") { + this._keys.push(k); + this._map["="+k] = this._vals.length; + this._vals.push(v); + } +} + +Hash.prototype.replace = function(k, k2, v) { + if (k == k2) return; + + var offset = this._map["="+k]; + this._keys[offset] = k2; + if (typeof v != "undefined") this._vals[offset] = v; + this._map["="+k2] = offset; + delete(this._map["="+k]); +} + +Hash.prototype.drop = function(k) { + if (k != "") { + var offset = this._map["="+k]; + this._keys.splice(offset, 1); + this._vals.splice(offset, 1); + delete(this._map["="+k]); + for (var p in this._map) { + if (this._map[p] >= offset) this._map[p]--; + } + if (this._cursor >= offset && this._cursor > 0) this._cursor--; + } +} + +Hash.prototype.get = function(k) { + if (k != "") { + return this._vals[this._map["="+k]]; + } +} + +Hash.prototype.keys = function() { + return this._keys; +} + +Hash.prototype.hasKey = function(k) { + if (k != "") { + return (typeof this._map["="+k] != "undefined"); + } +} + +Hash.prototype.values = function() { + return this._vals; +} + +Hash.prototype.reset = function() { + this._cursor = 0; +} + +Hash.prototype.first = function() { + this.reset(); + return this.next(); +} + +Hash.prototype.next = function() { + if (this._cursor++ < this._keys.length) + return {key: this._keys[this._cursor-1], value: this._vals[this._cursor-1]}; +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/frame/Link.js b/doc/jsdoc-toolkit/app/frame/Link.js new file mode 100644 index 000000000..ef5dc0a4d --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame/Link.js @@ -0,0 +1,153 @@ +/** Handle the creation of HTML links to documented symbols. + @constructor +*/ +function Link() { + this.alias = ""; + this.src = ""; + this.file = ""; + this.text = ""; + this.innerName = ""; + this.classLink = false; + this.targetName = ""; + + this.target = function(targetName) { + if (defined(targetName)) this.targetName = targetName; + return this; + } + this.inner = function(inner) { + if (defined(inner)) this.innerName = inner; + return this; + } + this.withText = function(text) { + if (defined(text)) this.text = text; + return this; + } + this.toSrc = function(filename) { + if (defined(filename)) this.src = filename; + return this; + } + this.toSymbol = function(alias) { + if (defined(alias)) this.alias = new String(alias); + return this; + } + this.toClass = function(alias) { + this.classLink = true; + return this.toSymbol(alias); + } + this.toFile = function(file) { + if (defined(file)) this.file = file; + return this; + } + + this.toString = function() { + var linkString; + var thisLink = this; + + if (this.alias) { + linkString = this.alias.replace(/(^|[^a-z$0-9_#.:^-])([|a-z$0-9_#.:^-]+)($|[^a-z$0-9_#.:^-])/i, + function(match, prematch, symbolName, postmatch) { + var symbolNames = symbolName.split("|"); + var links = []; + for (var i = 0, l = symbolNames.length; i < l; i++) { + thisLink.alias = symbolNames[i]; + links.push(thisLink._makeSymbolLink(symbolNames[i])); + } + return prematch+links.join("|")+postmatch; + } + ); + } + else if (this.src) { + linkString = thisLink._makeSrcLink(this.src); + } + else if (this.file) { + linkString = thisLink._makeFileLink(this.file); + } + + return linkString; + } +} + +/** prefixed for hashes */ +Link.hashPrefix = ""; + +/** Appended to the front of relative link paths. */ +Link.base = ""; + +Link.symbolNameToLinkName = function(symbol) { + var linker = ""; + if (symbol.isStatic) linker = "."; + else if (symbol.isInner) linker = "-"; + + return Link.hashPrefix+linker+symbol.name; +} + +/** Create a link to another symbol. */ +Link.prototype._makeSymbolLink = function(alias) { + var linkBase = Link.base+publish.conf.symbolsDir; + var linkTo = Link.symbolSet.getSymbol(alias); + var linkPath; + var target = (this.targetName)? " target=\""+this.targetName+"\"" : ""; + + // is it an internal link? + if (alias.charAt(0) == "#") var linkPath = alias; + + // if there is no symbol by that name just return the name unaltered + else if (!linkTo) return this.text || alias; + + // it's a symbol in another file + else { + if (!linkTo.is("CONSTRUCTOR") && !linkTo.isNamespace) { // it's a method or property + if (linkTo.isEvent) { + linkPath = + (Link.filemap)? Link.filemap[linkTo.memberOf] + : + escape(linkTo.memberOf) || "_global_"; + linkPath += publish.conf.ext + "#event:" + Link.symbolNameToLinkName(linkTo); + } + else { + linkPath = + (Link.filemap)? Link.filemap[linkTo.memberOf] + : + escape(linkTo.memberOf) || "_global_"; + linkPath += publish.conf.ext + "#" + Link.symbolNameToLinkName(linkTo); + } + } + else { + linkPath = (Link.filemap)? Link.filemap[linkTo.alias] : escape(linkTo.alias); + linkPath += publish.conf.ext;// + (this.classLink? "":"#" + Link.hashPrefix + "constructor"); + } + linkPath = linkBase + linkPath + } + + var linkText = this.text || alias; + + var link = {linkPath: linkPath, linkText: linkText, linkInner: (this.innerName? "#"+this.innerName : "")}; + + if (typeof JSDOC.PluginManager != "undefined") { + JSDOC.PluginManager.run("onSymbolLink", link); + } + + return ""+link.linkText+""; +} + +/** Create a link to a source file. */ +Link.prototype._makeSrcLink = function(srcFilePath) { + var target = (this.targetName)? " target=\""+this.targetName+"\"" : ""; + + // transform filepath into a filename + var srcFile = srcFilePath.replace(/\.\.?[\\\/]/g, "").replace(/[:\\\/]/g, "_"); + var outFilePath = Link.base + publish.conf.srcDir + srcFile + publish.conf.ext; + + if (!this.text) this.text = FilePath.fileName(srcFilePath); + return ""+this.text+""; +} + +/** Create a link to a source file. */ +Link.prototype._makeFileLink = function(filePath) { + var target = (this.targetName)? " target=\""+this.targetName+"\"" : ""; + + var outFilePath = Link.base + filePath; + + if (!this.text) this.text = filePath; + return ""+this.text+""; +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/frame/Namespace.js b/doc/jsdoc-toolkit/app/frame/Namespace.js new file mode 100644 index 000000000..fa1e41d10 --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame/Namespace.js @@ -0,0 +1,10 @@ +_global_ = this; + +function Namespace(name, f) { + var n = name.split("."); + for (var o = _global_, i = 0, l = n.length; i < l; i++) { + o = o[n[i]] = o[n[i]] || {}; + } + + if (f) f(); +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/frame/Opt.js b/doc/jsdoc-toolkit/app/frame/Opt.js new file mode 100644 index 000000000..352f15903 --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame/Opt.js @@ -0,0 +1,134 @@ +/** @namespace */ +Opt = { + /** + * Get commandline option values. + * @param {Array} args Commandline arguments. Like ["-a=xml", "-b", "--class=new", "--debug"] + * @param {object} optNames Map short names to long names. Like {a:"accept", b:"backtrace", c:"class", d:"debug"}. + * @return {object} Short names and values. Like {a:"xml", b:true, c:"new", d:true} + */ + get: function(args, optNames) { + var opt = {"_": []}; // the unnamed option allows multiple values + for (var i = 0; i < args.length; i++) { + var arg = new String(args[i]); + var name; + var value; + if (arg.charAt(0) == "-") { + if (arg.charAt(1) == "-") { // it's a longname like --foo + arg = arg.substring(2); + var m = arg.split("="); + name = m.shift(); + value = m.shift(); + if (typeof value == "undefined") value = true; + + for (var n in optNames) { // convert it to a shortname + if (name == optNames[n]) { + name = n; + } + } + } + else { // it's a shortname like -f + arg = arg.substring(1); + var m = arg.split("="); + name = m.shift(); + value = m.shift(); + if (typeof value == "undefined") value = true; + + for (var n in optNames) { // find the matching key + if (name == n || name+'[]' == n) { + name = n; + break; + } + } + } + if (name.match(/(.+)\[\]$/)) { // it's an array type like n[] + name = RegExp.$1; + if (!opt[name]) opt[name] = []; + } + + if (opt[name] && opt[name].push) { + opt[name].push(value); + } + else { + opt[name] = value; + } + } + else { // not associated with any optname + opt._.push(args[i]); + } + } + return opt; + } +} + +/*t: + plan(11, "Testing Opt."); + + is( + typeof Opt, + "object", + "Opt is an object." + ); + + is( + typeof Opt.get, + "function", + "Opt.get is a function." + ); + + var optNames = {a:"accept", b:"backtrace", c:"class", d:"debug", "e[]":"exceptions"}; + var t_options = Opt.get(["-a=xml", "-b", "--class=new", "--debug", "-e=one", "-e=two", "foo", "bar"], optNames); + + is( + t_options.a, + "xml", + "an option defined with a short name can be accessed by its short name." + ); + + is( + t_options.b, + true, + "an option defined with a short name and no value are true." + ); + + is( + t_options.c, + "new", + "an option defined with a long name can be accessed by its short name." + ); + + is( + t_options.d, + true, + "an option defined with a long name and no value are true." + ); + + is( + typeof t_options.e, + "object", + "an option that can accept multiple values is defined." + ); + + is( + t_options.e.length, + 2, + "an option that can accept multiple values can have more than one value." + ); + + is( + t_options.e[1], + "two", + "an option that can accept multiple values can be accessed as an array." + ); + + is( + typeof t_options._, + "object", + "the property '_' is defined for unnamed options." + ); + + is( + t_options._[0], + "foo", + "the property '_' can be accessed as an array." + ); + */ \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/frame/Reflection.js b/doc/jsdoc-toolkit/app/frame/Reflection.js new file mode 100644 index 000000000..0968f1c64 --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame/Reflection.js @@ -0,0 +1,26 @@ +/**@constructor*/ +function Reflection(obj) { + this.obj = obj; +} + +Reflection.prototype.getConstructorName = function() { + if (this.obj.constructor.name) return this.obj.constructor.name; + var src = this.obj.constructor.toSource(); + var name = src.substring(name.indexOf("function")+8, src.indexOf('(')).replace(/ /g,''); + return name; +} + +Reflection.prototype.getMethod = function(name) { + for (var p in this.obj) { + if (p == name && typeof(this.obj[p]) == "function") return this.obj[p]; + } + return null; +} + +Reflection.prototype.getParameterNames = function() { + var src = this.obj.toSource(); + src = src.substring( + src.indexOf("(", 8)+1, src.indexOf(")") + ); + return src.split(/, ?/); +} diff --git a/doc/jsdoc-toolkit/app/frame/String.js b/doc/jsdoc-toolkit/app/frame/String.js new file mode 100644 index 000000000..c183c27d5 --- /dev/null +++ b/doc/jsdoc-toolkit/app/frame/String.js @@ -0,0 +1,93 @@ +/** + @name String + @class Additions to the core string object. +*/ + +/** @author Steven Levithan, released as public domain. */ +String.prototype.trim = function() { + var str = this.replace(/^\s+/, ''); + for (var i = str.length - 1; i >= 0; i--) { + if (/\S/.test(str.charAt(i))) { + str = str.substring(0, i + 1); + break; + } + } + return str; +} +/*t: + plan(6, "Testing String.prototype.trim."); + + var s = " a bc ".trim(); + is(s, "a bc", "multiple spaces front and back are trimmed."); + + s = "a bc\n\n".trim(); + is(s, "a bc", "newlines only in back are trimmed."); + + s = "\ta bc".trim(); + is(s, "a bc", "tabs only in front are trimmed."); + + s = "\n \t".trim(); + is(s, "", "an all-space string is trimmed to empty."); + + s = "a b\nc".trim(); + is(s, "a b\nc", "a string with no spaces in front or back is trimmed to itself."); + + s = "".trim(); + is(s, "", "an empty string is trimmed to empty."); + +*/ + +String.prototype.balance = function(open, close) { + var i = 0; + while (this.charAt(i) != open) { + if (i == this.length) return [-1, -1]; + i++; + } + + var j = i+1; + var balance = 1; + while (j < this.length) { + if (this.charAt(j) == open) balance++; + if (this.charAt(j) == close) balance--; + if (balance == 0) break; + j++; + if (j == this.length) return [-1, -1]; + } + + return [i, j]; +} +/*t: + plan(16, "Testing String.prototype.balance."); + + var s = "{abc}".balance("{","}"); + is(s[0], 0, "opener in first is found."); + is(s[1], 4, "closer in last is found."); + + s = "ab{c}de".balance("{","}"); + is(s[0], 2, "opener in middle is found."); + is(s[1], 4, "closer in middle is found."); + + s = "a{b{c}de}f".balance("{","}"); + is(s[0], 1, "nested opener is found."); + is(s[1], 8, "nested closer is found."); + + s = "{}".balance("{","}"); + is(s[0], 0, "opener with no content is found."); + is(s[1], 1, "closer with no content is found."); + + s = "".balance("{","}"); + is(s[0], -1, "empty string opener is -1."); + is(s[1], -1, "empty string closer is -1."); + + s = "{abc".balance("{","}"); + is(s[0], -1, "opener with no closer returns -1."); + is(s[1], -1, "no closer returns -1."); + + s = "abc".balance("{","}"); + is(s[0], -1, "no opener or closer returns -1 for opener."); + is(s[1], -1, "no opener or closer returns -1 for closer."); + + s = "aX11/MIT License + * (See the accompanying README file for full details.) + */ + +/** + Yet another unit testing tool for JavaScript. + @author Michael Mathews micmath@gmail.com + @param {object} testCases Properties are testcase names, values are functions to execute as tests. +*/ +function testrun(testCases) { + var ran = 0; + for (t in testCases) { + var result = testCases[t](); + ran++; + } + + return testrun.reportOut+"-------------------------------\n"+((testrun.fails>0)? ":( Failed "+testrun.fails+"/" : ":) Passed all ")+testrun.count+" test"+((testrun.count == 1)? "":"s")+".\n"; +} + + +testrun.count = 0; +testrun.current = null; +testrun.passes = 0; +testrun.fails = 0; +testrun.reportOut = ""; + +/** @private */ +testrun.report = function(text) { + testrun.reportOut += text+"\n"; +} + +/** + Check if test evaluates to true. + @param {string} test To be evaluated. + @param {string} message Optional. To be displayed in the report. + @return {boolean} True if the string test evaluates to true. +*/ +ok = function(test, message) { + testrun.count++; + + var result; + try { + result = eval(test); + + if (result) { + testrun.passes++; + testrun.report(" OK "+testrun.count+" - "+((message != null)? message : "")); + } + else { + testrun.fails++; + testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : "")); + } + } + catch(e) { + testrun.fails++ + testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : "")); + + } +} + +/** + Check if test is same as expected. + @param {string} test To be evaluated. + @param {string} expected + @param {string} message Optional. To be displayed in the report. + @return {boolean} True if (test == expected). Note that the comparison is not a strict equality check. +*/ +is = function(test, expected, message) { + testrun.count++; + + var result; + try { + result = eval(test); + + if (result == expected) { + testrun.passes++ + testrun.report(" OK "+testrun.count+" - "+((message != null)? message : "")); + } + else { + testrun.fails++ + testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : "")); + testrun.report("expected: "+expected); + testrun.report(" got: "+result); + } + } + catch(e) { + testrun.fails++ + testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : "")); + testrun.report("expected: "+expected); + testrun.report(" got: "+result);} +} + +/** + Check if test matches pattern. + @param {string} test To be evaluated. + @param {string} pattern Used to create a RegExp. + @param {string} message Optional. To be displayed in the report. + @return {boolean} True if test matches pattern. +*/ +like = function(test, pattern, message) { + testrun.count++; + + var result; + try { + result = eval(test); + var rgx = new RegExp(pattern); + + if (rgx.test(result)) { + testrun.passes++ + testrun.report(" OK "+testrun.count+" - "+((message != null)? message : "")); + } + else { + testrun.fails++ + testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : "")); + testrun.report(" this: "+result); + testrun.report("is not like: "+pattern); + } + } + catch(e) { + testrun.fails++ + testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : "")); + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/handlers/FOODOC.js b/doc/jsdoc-toolkit/app/handlers/FOODOC.js new file mode 100644 index 000000000..b208f55b1 --- /dev/null +++ b/doc/jsdoc-toolkit/app/handlers/FOODOC.js @@ -0,0 +1,26 @@ +/** + This is the main container for the FOODOC handler. + @namespace +*/ +FOODOC = { +}; + +/** The current version string of this application. */ +FOODOC.VERSION = "1.0"; + +FOODOC.handle = function(srcFile, src) { + LOG.inform("Handling file '" + srcFile + "'"); + + return [ + new JSDOC.Symbol( + "foo", + [], + "VIRTUAL", + new JSDOC.DocComment("/** This is a foo. */") + ) + ]; +}; + +FOODOC.publish = function(symbolgroup) { + LOG.inform("Publishing symbolgroup."); +}; diff --git a/doc/jsdoc-toolkit/app/handlers/XMLDOC.js b/doc/jsdoc-toolkit/app/handlers/XMLDOC.js new file mode 100755 index 000000000..40f87b355 --- /dev/null +++ b/doc/jsdoc-toolkit/app/handlers/XMLDOC.js @@ -0,0 +1,26 @@ +/** + * This is the main container for the XMLDOC handler. + * @namespace + * @author Brett Fattori (bfattori@fry.com) + * @version $Revision: 498 $ + */ +XMLDOC = { + +}; + +/** The current version string of this application. */ +XMLDOC.VERSION = "1.0"; + +/** Include the library necessary to handle XML files */ +IO.includeDir("handlers/XMLDOC/"); + +/** + * @type Symbol[] + */ +XMLDOC.handle = function(srcFile, src) { + +}; + +XMLDOC.publish = function(symbolgroup) { + +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/handlers/XMLDOC/DomReader.js b/doc/jsdoc-toolkit/app/handlers/XMLDOC/DomReader.js new file mode 100755 index 000000000..240563daf --- /dev/null +++ b/doc/jsdoc-toolkit/app/handlers/XMLDOC/DomReader.js @@ -0,0 +1,159 @@ +LOG.inform("XMLDOC.DomReader loaded"); + +XMLDOC.DomReader = function(root) { + + this.dom = root; + + /** + * The current node the reader is on + */ + this.node = root; + + /** + * Get the current node the reader is on + * @type XMLDOC.Parser.node + */ + XMLDOC.DomReader.prototype.getNode = function() { + return this.node; + }; + + /** + * Set the node the reader should be positioned on. + * @param node {XMLDOC.Parser.node} + */ + XMLDOC.DomReader.prototype.setNode = function(node) { + this.node = node; + }; + + /** + * A helper method to make sure the current node will + * never return null, unless null is passed as the root. + * @param step {String} An expression to evaluate - should return a node or null + */ + XMLDOC.DomReader.prototype.navigate = function(step) { + var n; + if ((n = step) != null) + { + this.node = n; + return this.node; + } + return null; + }; + + /** + * Get the root node of the current node's document. + */ + XMLDOC.DomReader.prototype.root = function() { + this.navigate(this.dom); + }; + + /** + * Get the parent of the current node. + */ + XMLDOC.DomReader.prototype.parent = function() { + return this.navigate(this.node.parentNode()); + }; + + /** + * Get the first child of the current node. + */ + XMLDOC.DomReader.prototype.firstChild = function() { + return this.navigate(this.node.firstChild()); + }; + + /** + * Get the last child of the current node. + */ + XMLDOC.DomReader.prototype.lastChild = function() { + return this.navigate(this.node.lastChild()); + }; + + /** + * Get the next sibling of the current node. + */ + XMLDOC.DomReader.prototype.nextSibling = function() { + return this.navigate(this.node.nextSibling()); + }; + + /** + * Get the previous sibling of the current node. + */ + XMLDOC.DomReader.prototype.prevSibling = function() { + return this.navigate(this.node.prevSibling()); + }; + + //=============================================================================================== + // Support methods + + /** + * Walk the tree starting with the current node, calling the plug-in for + * each node visited. Each time the plug-in is called, the DomReader + * is passed as the only parameter. Use the {@link XMLDOC.DomReader#getNode} method + * to access the current node. This method uses a depth first traversal pattern. + * + * @param srcFile {String} The source file being evaluated + */ + XMLDOC.DomReader.prototype.getSymbols = function(srcFile) + { + XMLDOC.DomReader.symbols = []; + XMLDOC.DomReader.currentFile = srcFile; + JSDOC.Symbol.srcFile = (srcFile || ""); + + if (defined(JSDOC.PluginManager)) { + JSDOC.PluginManager.run("onDomGetSymbols", this); + } + + return XMLDOC.DomReader.symbols; + }; + + /** + * Find the node with the given name using a depth first traversal. + * Does not modify the DomReader's current node. + * + * @param name {String} The name of the node to find + * @return the node that was found, or null if not found + */ + XMLDOC.DomReader.prototype.findNode = function(name) + { + var findNode = null; + + // Start at the current node and move into the subtree, + // looking for the node with the given name + function deeper(node, find) + { + var look = null; + + if (node) { + if (node.name == find) + { + return node; + } + + if (node.firstChild()) + { + look = deeper(node.firstChild(), find); + } + + if (!look && node.nextSibling()) + { + look = deeper(node.nextSibling(), find); + } + } + + return look; + } + + return deeper(this.getNode().firstChild(), name); + }; + + /** + * Find the next node with the given name using a depth first traversal. + * + * @param name {String} The name of the node to find + */ + XMLDOC.DomReader.prototype.findPreviousNode = function(name) + { + }; + +}; + diff --git a/doc/jsdoc-toolkit/app/handlers/XMLDOC/XMLDoc.js b/doc/jsdoc-toolkit/app/handlers/XMLDOC/XMLDoc.js new file mode 100755 index 000000000..e9b3e3ce8 --- /dev/null +++ b/doc/jsdoc-toolkit/app/handlers/XMLDOC/XMLDoc.js @@ -0,0 +1,16 @@ +LOG.inform("XMLDOC.symbolize loaded"); + +/** + * Convert the source file to a set of symbols + */ +XMLDOC.symbolize = function(srcFile, src) { + + LOG.inform("Symbolizing file '" + srcFile + "'"); + + // XML files already have a defined structure, so we don't need to + // do anything but parse them. The DOM reader can create a symbol + // table from the parsed XML. + var dr = new XMLDOC.DomReader(XMLDOC.Parser.parse(src)); + return dr.getSymbols(srcFile); + +}; diff --git a/doc/jsdoc-toolkit/app/handlers/XMLDOC/XMLParse.js b/doc/jsdoc-toolkit/app/handlers/XMLDOC/XMLParse.js new file mode 100755 index 000000000..78e8f4556 --- /dev/null +++ b/doc/jsdoc-toolkit/app/handlers/XMLDOC/XMLParse.js @@ -0,0 +1,292 @@ +LOG.inform("XMLDOC.Parser loaded"); + +/** + * XML Parser object. Returns an {@link #XMLDOC.Parser.node} which is + * the root element of the parsed document. + *

+ * By default, this parser will only handle well formed XML. To + * allow the parser to handle HTML, set the XMLDOC.Parser.strictMode + * variable to false before calling XMLDOC.Parser.parse(). + *

+ * Note: If you pass poorly formed XML, it will cause the parser to throw + * an exception. + * + * @author Brett Fattori (bfattori@fry.com) + * @author $Author: micmath $ + * @version $Revision: 497 $ + */ +XMLDOC.Parser = {}; + +/** + * Strict mode setting. Setting this to false allows HTML-style source to + * be parsed. Normally, well formed XML has defined end tags, or empty tags + * are properly formed. Default: true + * @type Boolean + */ +XMLDOC.Parser.strictMode = true; + +/** + * A node in an XML Document. Node types are ROOT, ELEMENT, COMMENT, PI, and TEXT. + * @param parent {XMLDOC.Parser.node} The parent node + * @param name {String} The node name + * @param type {String} One of the types + */ +XMLDOC.Parser.node = function(parent, name, type) +{ + this.name = name; + this.type = type || "ELEMENT"; + this.parent = parent; + this.charData = ""; + this.attrs = {}; + this.nodes = []; + this.cPtr = 0; + + XMLDOC.Parser.node.prototype.getAttributeNames = function() { + var a = []; + for (var o in this.attrs) + { + a.push(o); + } + + return a; + }; + + XMLDOC.Parser.node.prototype.getAttribute = function(attr) { + return this.attrs[attr]; + }; + + XMLDOC.Parser.node.prototype.setAttribute = function(attr, val) { + this.attrs[attr] = val; + }; + + XMLDOC.Parser.node.prototype.getChild = function(idx) { + return this.nodes[idx]; + }; + + XMLDOC.Parser.node.prototype.parentNode = function() { + return this.parent; + }; + + XMLDOC.Parser.node.prototype.firstChild = function() { + return this.nodes[0]; + }; + + XMLDOC.Parser.node.prototype.lastChild = function() { + return this.nodes[this.nodes.length - 1]; + }; + + XMLDOC.Parser.node.prototype.nextSibling = function() { + var p = this.parent; + if (p && (p.nodes.indexOf(this) + 1 != p.nodes.length)) + { + return p.getChild(p.nodes.indexOf(this) + 1); + } + return null; + }; + + XMLDOC.Parser.node.prototype.prevSibling = function() { + var p = this.parent; + if (p && (p.nodes.indexOf(this) - 1 >= 0)) + { + return p.getChild(p.nodes.indexOf(this) - 1); + } + return null; + }; +}; + +/** + * Parse an XML Document from the specified source. The XML should be + * well formed, unless strict mode is disabled, then the parser will + * handle HTML-style XML documents. + * @param src {String} The source to parse + */ +XMLDOC.Parser.parse = function(src) +{ + var A = []; + + // Normailize whitespace + A = src.split("\r\n"); + src = A.join("\n"); + A = src.split("\r"); + src = A.join("\n"); + + // Remove XML and DOCTYPE specifier + src.replace(/<\?XML .*\?>/i, ""); + src.replace(//i, ""); + + // The document is the root node and cannot be modified or removed + var doc = new XMLDOC.Parser.node(null, "ROOT", "DOCUMENT"); + + // Let's break it down + XMLDOC.Parser.eat(doc, src); + + return doc; +}; + +/** + * The XML fragment processing routine. This method is private and should not be called + * directly. + * @param parentNode {XMLDOC.Parser.node} The node which is the parent of this fragment + * @param src {String} The source within the fragment to process + * @private + */ +XMLDOC.Parser.eat = function(parentNode, src) +{ + // A simple tag def + var reTag = new RegExp("<(!|)(\\?|--|)((.|\\s)*?)\\2>","g"); + + // Special tag types + var reCommentTag = //; + var rePITag = /<\?((.|\s)*?)\?>/; + + // A start tag (with potential empty marker) + var reStartTag = /<(.*?)( +([\w_\-]*)=(\"|')(.*)\4)*(\/)?>/; + + // An empty HTML style tag (not proper XML, but we'll accept it so we can process HTML) + var reHTMLEmptyTag = /<(.*?)( +([\w_\-]*)=(\"|')(.*)\4)*>/; + + // Fully enclosing tag with nested tags + var reEnclosingTag = /<(.*?)( +([\w_\-]*)=(\"|')(.*?)\4)*>((.|\s)*?)<\/\1>/; + + // Breaks down attributes + var reAttributes = new RegExp(" +([\\w_\\-]*)=(\"|')(.*?)\\2","g"); + + // Find us a tag + var tag; + while ((tag = reTag.exec(src)) != null) + { + if (tag.index > 0) + { + // The next tag has some text before it + var text = src.substring(0, tag.index).replace(/^[ \t\n]+((.|\n)*?)[ \t\n]+$/, "$1"); + + if (text.length > 0 && (text != "\n")) + { + var txtnode = new XMLDOC.Parser.node(parentNode, "", "TEXT"); + txtnode.charData = text; + + // Append the new text node + parentNode.nodes.push(txtnode); + } + + // Reset the lastIndex of reTag + reTag.lastIndex -= src.substring(0, tag.index).length; + + // Eat the text + src = src.substring(tag.index); + } + + if (reCommentTag.test(tag[0])) + { + // Is this a comment? + var comment = new XMLDOC.Parser.node(parentNode, "", "COMMENT"); + comment.charData = reCommentTag.exec(tag[0])[1]; + + // Append the comment + parentNode.nodes.push(comment); + + // Move the lastIndex of reTag + reTag.lastIndex -= tag[0].length; + + // Eat the tag + src = src.replace(reCommentTag, ""); + } + else if (rePITag.test(tag[0])) + { + // Is this a processing instruction? + var pi = new XMLDOC.Parser.node(parentNode, "", "PI"); + pi.charData = rePITag.exec(tag[0])[1]; + + // Append the processing instruction + parentNode.nodes.push(pi); + + // Move the lastIndex of reTag + reTag.lastIndex -= tag[0].length; + + // Eat the tag + src = src.replace(rePITag, ""); + } + else if (reStartTag.test(tag[0])) + { + // Break it down + var e = reStartTag.exec(tag[0]); + var elem = new XMLDOC.Parser.node(parentNode, e[1], "ELEMENT"); + + // Get attributes from the tag + var a; + while ((a = reAttributes.exec(e[2])) != null ) + { + elem.attrs[a[1]] = a[3]; + } + + // Is this an empty XML-style tag? + if (e[6] == "/") + { + // Append the empty element + parentNode.nodes.push(elem); + + // Move the lastIndex of reTag (include the start tag length) + reTag.lastIndex -= e[0].length; + + // Eat the tag + src = src.replace(reStartTag, ""); + } + else + { + // Check for malformed XML tags + var htmlParsed = false; + var htmlStartTag = reHTMLEmptyTag.exec(src); + + // See if there isn't an end tag within this block + var reHTMLEndTag = new RegExp(""); + var htmlEndTag = reHTMLEndTag.exec(src); + + if (XMLDOC.Parser.strictMode && htmlEndTag == null) + { + // Poorly formed XML fails in strict mode + var err = new Error("Malformed XML passed to XMLDOC.Parser... Error contains malformed 'src'"); + err.src = src; + throw err; + } + else if (htmlEndTag == null) + { + // This is an HTML-style empty tag, store the element for it in non-strict mode + parentNode.nodes.push(elem); + + // Eat the tag + src = src.replace(reHTMLEmptyTag, ""); + htmlParsed = true; + } + + // If we didn't parse HTML-style, it must be an enclosing tag + if (!htmlParsed) + { + var enc = reEnclosingTag.exec(src); + + // Go deeper into the document + XMLDOC.Parser.eat(elem, enc[6]); + + // Append the new element node + parentNode.nodes.push(elem); + + // Eat the tag + src = src.replace(reEnclosingTag, ""); + } + } + + // Reset the lastIndex of reTag + reTag.lastIndex = 0; + } + } + + // No tag was found... append the text if there is any + src = src.replace(/^[ \t\n]+((.|\n)*?)[ \t\n]+$/, "$1"); + if (src.length > 0 && (src != "\n")) + { + var txtNode = new XMLDOC.Parser.node(parentNode, "", "TEXT"); + txtNode.charData = src; + + // Append the new text node + parentNode.nodes.push(txtNode); + } +}; diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC.js b/doc/jsdoc-toolkit/app/lib/JSDOC.js new file mode 100644 index 000000000..ca59089a7 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC.js @@ -0,0 +1,104 @@ +/** + @overview + @date $Date: 2009-09-04 07:43:41 +0100 (Fri, 04 Sep 2009) $ + @version $Revision: 814 $ + @location $HeadURL: https://jsdoc-toolkit.googlecode.com/svn/tags/jsdoc_toolkit-2.3.2/jsdoc-toolkit/app/lib/JSDOC.js $ + @name JSDOC.js + */ + +/** + This is the main container for the JSDOC application. + @namespace +*/ +JSDOC = { +}; + +/** + @requires Opt + */ +if (typeof arguments == "undefined") arguments = []; +JSDOC.opt = Opt.get( + arguments, + { + a: "allfunctions", + c: "conf", + d: "directory", + "D[]": "define", + e: "encoding", + "E[]": "exclude", + h: "help", + n: "nocode", + o: "out", + p: "private", + q: "quiet", + r: "recurse", + S: "securemodules", + s: "suppress", + t: "template", + T: "testmode", + u: "unique", + v: "verbose", + x: "ext" + } +); + +/** The current version string of this application. */ +JSDOC.VERSION = "2.3.2"; + +/** Print out usage information and quit. */ +JSDOC.usage = function() { + print("USAGE: java -jar jsrun.jar app/run.js [OPTIONS] ..."); + print(""); + print("OPTIONS:"); + print(" -a or --allfunctions\n Include all functions, even undocumented ones.\n"); + print(" -c or --conf\n Load a configuration file.\n"); + print(" -d= or --directory=\n Output to this directory (defaults to \"out\").\n"); + print(" -D=\"myVar:My value\" or --define=\"myVar:My value\"\n Multiple. Define a variable, available in JsDoc as JSDOC.opt.D.myVar.\n"); + print(" -e= or --encoding=\n Use this encoding to read and write files.\n"); + print(" -E=\"REGEX\" or --exclude=\"REGEX\"\n Multiple. Exclude files based on the supplied regex.\n"); + print(" -h or --help\n Show this message and exit.\n"); + print(" -n or --nocode\n Ignore all code, only document comments with @name tags.\n"); + print(" -o= or --out=\n Print log messages to a file (defaults to stdout).\n"); + print(" -p or --private\n Include symbols tagged as private, underscored and inner symbols.\n"); + print(" -q or --quiet\n Do not output any messages, not even warnings.\n"); + print(" -r= or --recurse=\n Descend into src directories.\n"); + print(" -s or --suppress\n Suppress source code output.\n"); + print(" -S or --securemodules\n Use Secure Modules mode to parse source code.\n"); + print(" -t= or --template=\n Required. Use this template to format the output.\n"); + print(" -T or --test\n Run all unit tests and exit.\n"); + print(" -u or --unique\n Force file names to be unique, but not based on symbol names.\n"); + print(" -v or --verbose\n Provide verbose feedback about what is happening.\n"); + print(" -x=[,EXT]... or --ext=[,EXT]...\n Scan source files with the given extension/s (defaults to js).\n"); + + quit(); +} + +/*t: + plan(4, "Testing JSDOC namespace."); + + is( + typeof JSDOC, + "object", + "JSDOC.usage is a function." + ); + + is( + typeof JSDOC.VERSION, + "string", + "JSDOC.VERSION is a string." + ); + + is( + typeof JSDOC.usage, + "function", + "JSDOC.usage is a function." + ); + + is( + typeof JSDOC.opt, + "object", + "JSDOC.opt is a object." + ); + */ + +if (this.IO) IO.includeDir("lib/JSDOC/"); diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/DocComment.js b/doc/jsdoc-toolkit/app/lib/JSDOC/DocComment.js new file mode 100644 index 000000000..c6c8d7d4f --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/DocComment.js @@ -0,0 +1,200 @@ +if (typeof JSDOC == "undefined") JSDOC = {}; + +/** + Create a new DocComment. This takes a raw documentation comment, + and wraps it in useful accessors. + @class Represents a documentation comment object. + */ +JSDOC.DocComment = function(/**String*/comment) { + this.init(); + if (typeof comment != "undefined") { + this.parse(comment); + } +} + +JSDOC.DocComment.prototype.init = function() { + this.isUserComment = true; + this.src = ""; + this.meta = ""; + this.tagTexts = []; + this.tags = []; +} + +/** + @requires JSDOC.DocTag + */ +JSDOC.DocComment.prototype.parse = function(/**String*/comment) { + if (comment == "") { + comment = "/** @desc */"; + this.isUserComment = false; + } + + this.src = JSDOC.DocComment.unwrapComment(comment); + + this.meta = ""; + if (this.src.indexOf("#") == 0) { + this.src.match(/#(.+[+-])([\s\S]*)$/); + if (RegExp.$1) this.meta = RegExp.$1; + if (RegExp.$2) this.src = RegExp.$2; + } + + if (typeof JSDOC.PluginManager != "undefined") { + JSDOC.PluginManager.run("onDocCommentSrc", this); + } + + this.fixDesc(); + + this.src = JSDOC.DocComment.shared+"\n"+this.src; + + this.tagTexts = + this.src + .split(/(^|[\r\n])\s*@/) + .filter(function($){return $.match(/\S/)}); + + /** + The tags found in the comment. + @type JSDOC.DocTag[] + */ + this.tags = this.tagTexts.map(function($){return new JSDOC.DocTag($)}); + + if (typeof JSDOC.PluginManager != "undefined") { + JSDOC.PluginManager.run("onDocCommentTags", this); + } +} + +/*t: + plan(5, "testing JSDOC.DocComment"); + requires("../frame/String.js"); + requires("../lib/JSDOC/DocTag.js"); + + var com = new JSDOC.DocComment("/**@foo some\n* comment here*"+"/"); + is(com.tagTexts[0], "foo some\ncomment here", "first tag text is found."); + is(com.tags[0].title, "foo", "the title is found in a comment with one tag."); + + var com = new JSDOC.DocComment("/** @foo first\n* @bar second*"+"/"); + is(com.getTag("bar").length, 1, "getTag() returns one tag by that title."); + + JSDOC.DocComment.shared = "@author John Smith"; + var com = new JSDOC.DocComment("/**@foo some\n* comment here*"+"/"); + is(com.tags[0].title, "author", "shared comment is added."); + is(com.tags[1].title, "foo", "shared comment is added to existing tag."); +*/ + +/** + If no @desc tag is provided, this function will add it. + */ +JSDOC.DocComment.prototype.fixDesc = function() { + if (this.meta && this.meta != "@+") return; + if (/^\s*[^@\s]/.test(this.src)) { + this.src = "@desc "+this.src; + } +} + +/*t: + plan(5, "testing JSDOC.DocComment#fixDesc"); + + var com = new JSDOC.DocComment(); + + com.src = "this is a desc\n@author foo"; + com.fixDesc(); + is(com.src, "@desc this is a desc\n@author foo", "if no @desc tag is provided one is added."); + + com.src = "x"; + com.fixDesc(); + is(com.src, "@desc x", "if no @desc tag is provided one is added to a single character."); + + com.src = "\nx"; + com.fixDesc(); + is(com.src, "@desc \nx", "if no @desc tag is provided one is added to return and character."); + + com.src = " "; + com.fixDesc(); + is(com.src, " ", "if no @desc tag is provided one is not added to just whitespace."); + + com.src = ""; + com.fixDesc(); + is(com.src, "", "if no @desc tag is provided one is not added to empty."); +*/ + +/** + Remove slash-star comment wrapper from a raw comment string. + @type String + */ +JSDOC.DocComment.unwrapComment = function(/**String*/comment) { + if (!comment) return ""; + var unwrapped = comment.replace(/(^\/\*\*|\*\/$)/g, "").replace(/^\s*\* ?/gm, ""); + return unwrapped; +} + +/*t: + plan(5, "testing JSDOC.DocComment.unwrapComment"); + + var com = "/**x*"+"/"; + var unwrapped = JSDOC.DocComment.unwrapComment(com); + is(unwrapped, "x", "a single character jsdoc is found."); + + com = "/***x*"+"/"; + unwrapped = JSDOC.DocComment.unwrapComment(com); + is(unwrapped, "x", "three stars are allowed in the opener."); + + com = "/****x*"+"/"; + unwrapped = JSDOC.DocComment.unwrapComment(com); + is(unwrapped, "*x", "fourth star in the opener is kept."); + + com = "/**x\n * y\n*"+"/"; + unwrapped = JSDOC.DocComment.unwrapComment(com); + is(unwrapped, "x\ny\n", "leading stars and spaces are trimmed."); + + com = "/**x\n * y\n*"+"/"; + unwrapped = JSDOC.DocComment.unwrapComment(com); + is(unwrapped, "x\n y\n", "only first space after leading stars are trimmed."); +*/ + +/** + Provides a printable version of the comment. + @type String + */ +JSDOC.DocComment.prototype.toString = function() { + return this.src; +} + +/*t: + plan(1, "testing JSDOC.DocComment#fixDesc"); + var com = new JSDOC.DocComment(); + com.src = "foo"; + is(""+com, "foo", "stringifying a comment returns the unwrapped src."); +*/ + +/** + Given the title of a tag, returns all tags that have that title. + @type JSDOC.DocTag[] + */ +JSDOC.DocComment.prototype.getTag = function(/**String*/tagTitle) { + return this.tags.filter(function($){return $.title == tagTitle}); +} + +/*t: + plan(1, "testing JSDOC.DocComment#getTag"); + requires("../frame/String.js"); + requires("../lib/JSDOC/DocTag.js"); + + var com = new JSDOC.DocComment("/**@foo some\n* @bar\n* @bar*"+"/"); + is(com.getTag("bar").length, 2, "getTag returns expected number of tags."); +*/ + +/** + Used to store the currently shared tag text. +*/ +JSDOC.DocComment.shared = ""; + +/*t: + plan(2, "testing JSDOC.DocComment.shared"); + requires("../frame/String.js"); + requires("../lib/JSDOC/DocTag.js"); + + JSDOC.DocComment.shared = "@author Michael"; + + var com = new JSDOC.DocComment("/**@foo\n* @foo*"+"/"); + is(com.getTag("author").length, 1, "getTag returns shared tag."); + is(com.getTag("foo").length, 2, "getTag returns unshared tags too."); +*/ \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/DocTag.js b/doc/jsdoc-toolkit/app/lib/JSDOC/DocTag.js new file mode 100644 index 000000000..77ec07cac --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/DocTag.js @@ -0,0 +1,294 @@ +if (typeof JSDOC == "undefined") JSDOC = {}; + +/** + @constructor + */ +JSDOC.DocTag = function(src) { + this.init(); + if (typeof src != "undefined") { + this.parse(src); + } +} + +/** + Create and initialize the properties of this. + */ +JSDOC.DocTag.prototype.init = function() { + this.title = ""; + this.type = ""; + this.name = ""; + this.isOptional = false; + this.defaultValue = ""; + this.desc = ""; + + return this; +} + +/** + Populate the properties of this from the given tag src. + @param {string} src + */ +JSDOC.DocTag.prototype.parse = function(src) { + if (typeof src != "string") throw "src must be a string not "+(typeof src); + + try { + src = this.nibbleTitle(src); + if (JSDOC.PluginManager) { + JSDOC.PluginManager.run("onDocTagSynonym", this); + } + + src = this.nibbleType(src); + + // only some tags are allowed to have names. + if (this.title == "param" || this.title == "property" || this.title == "config") { // @config is deprecated + src = this.nibbleName(src); + } + } + catch(e) { + if (LOG) LOG.warn(e); + else throw e; + } + this.desc = src; // whatever is left + + // example tags need to have whitespace preserved + if (this.title != "example") this.desc = this.desc.trim(); + + if (JSDOC.PluginManager) { + JSDOC.PluginManager.run("onDocTag", this); + } +} + +/** + Automatically called when this is stringified. + */ +JSDOC.DocTag.prototype.toString = function() { + return this.desc; +} + +/*t: + plan(1, "testing JSDOC.DocTag#toString"); + + var tag = new JSDOC.DocTag("param {object} date A valid date."); + is(""+tag, "A valid date.", "stringifying a tag returns the desc."); + */ + +/** + Find and shift off the title of a tag. + @param {string} src + @return src + */ +JSDOC.DocTag.prototype.nibbleTitle = function(src) { + if (typeof src != "string") throw "src must be a string not "+(typeof src); + + var parts = src.match(/^\s*(\S+)(?:\s([\s\S]*))?$/); + + if (parts && parts[1]) this.title = parts[1]; + if (parts && parts[2]) src = parts[2]; + else src = ""; + + return src; +} + +/*t: + plan(8, "testing JSDOC.DocTag#nibbleTitle"); + + var tag = new JSDOC.DocTag(); + + tag.init().nibbleTitle("aTitleGoesHere"); + is(tag.title, "aTitleGoesHere", "a title can be found in a single-word string."); + + var src = tag.init().nibbleTitle("aTitleGoesHere and the rest"); + is(tag.title, "aTitleGoesHere", "a title can be found in a multi-word string."); + is(src, "and the rest", "the rest is returned when the title is nibbled off."); + + src = tag.init().nibbleTitle(""); + is(tag.title, "", "given an empty string the title is empty."); + is(src, "", "the rest is empty when the tag is empty."); + + var src = tag.init().nibbleTitle(" aTitleGoesHere\n a description"); + is(tag.title, "aTitleGoesHere", "leading and trailing spaces are not part of the title."); + is(src, " a description", "leading spaces (less one) are part of the description."); + + tag.init().nibbleTitle("a.Title::Goes_Here foo"); + is(tag.title, "a.Title::Goes_Here", "titles with punctuation are allowed."); + */ + +/** + Find and shift off the type of a tag. + @requires frame/String.js + @param {string} src + @return src + */ +JSDOC.DocTag.prototype.nibbleType = function(src) { + if (typeof src != "string") throw "src must be a string not "+(typeof src); + + if (src.match(/^\s*\{/)) { + var typeRange = src.balance("{", "}"); + if (typeRange[1] == -1) { + throw "Malformed comment tag ignored. Tag type requires an opening { and a closing }: "+src; + } + this.type = src.substring(typeRange[0]+1, typeRange[1]).trim(); + this.type = this.type.replace(/\s*,\s*/g, "|"); // multiples can be separated by , or | + src = src.substring(typeRange[1]+1); + } + + return src; +} + +/*t: + plan(5, "testing JSDOC.DocTag.parser.nibbleType"); + requires("../frame/String.js"); + + var tag = new JSDOC.DocTag(); + + tag.init().nibbleType("{String[]} aliases"); + is(tag.type, "String[]", "type can have non-alpha characters."); + + tag.init().nibbleType("{ aTypeGoesHere } etc etc"); + is(tag.type, "aTypeGoesHere", "type is trimmed."); + + tag.init().nibbleType("{ oneType, twoType ,\n threeType } etc etc"); + is(tag.type, "oneType|twoType|threeType", "multiple types can be separated by commas."); + + var error; + try { tag.init().nibbleType("{widget foo"); } + catch(e) { error = e; } + is(typeof error, "string", "malformed tag type throws error."); + isnt(error.indexOf("Malformed"), -1, "error message tells tag is malformed."); + */ + +/** + Find and shift off the name of a tag. + @requires frame/String.js + @param {string} src + @return src + */ +JSDOC.DocTag.prototype.nibbleName = function(src) { + if (typeof src != "string") throw "src must be a string not "+(typeof src); + + src = src.trim(); + + // is optional? + if (src.charAt(0) == "[") { + var nameRange = src.balance("[", "]"); + if (nameRange[1] == -1) { + throw "Malformed comment tag ignored. Tag optional name requires an opening [ and a closing ]: "+src; + } + this.name = src.substring(nameRange[0]+1, nameRange[1]).trim(); + this.isOptional = true; + + src = src.substring(nameRange[1]+1); + + // has default value? + var nameAndValue = this.name.split("="); + if (nameAndValue.length) { + this.name = nameAndValue.shift().trim(); + this.defaultValue = nameAndValue.join("="); + } + } + else { + var parts = src.match(/^(\S+)(?:\s([\s\S]*))?$/); + if (parts) { + if (parts[1]) this.name = parts[1]; + if (parts[2]) src = parts[2].trim(); + else src = ""; + } + } + + return src; +} + +/*t: + requires("../frame/String.js"); + plan(9, "testing JSDOC.DocTag.parser.nibbleName"); + + var tag = new JSDOC.DocTag(); + + tag.init().nibbleName("[foo] This is a description."); + is(tag.isOptional, true, "isOptional syntax is detected."); + is(tag.name, "foo", "optional param name is found."); + + tag.init().nibbleName("[foo] This is a description."); + is(tag.isOptional, true, "isOptional syntax is detected when no type."); + is(tag.name, "foo", "optional param name is found when no type."); + + tag.init().nibbleName("[foo=7] This is a description."); + is(tag.name, "foo", "optional param name is found when default value."); + is(tag.defaultValue, 7, "optional param default value is found when default value."); + + //tag.init().nibbleName("[foo= a value] This is a description."); + //is(tag.defaultValue, " a value", "optional param default value is found when default value has spaces (issue #112)."); + + tag.init().nibbleName("[foo=[]] This is a description."); + is(tag.defaultValue, "[]", "optional param default value is found when default value is [] (issue #95)."); + + tag.init().nibbleName("[foo=a=b] This is a description."); + is(tag.name, "foo", "optional param name is found when default value is a=b."); + is(tag.defaultValue, "a=b", "optional param default value is found when default value is a=b.") + */ + +/*t: + plan(32, "Testing JSDOC.DocTag.parser."); + requires("../frame/String.js"); + + var tag = new JSDOC.DocTag(); + + is(typeof tag, "object", "JSDOC.DocTag.parser with an empty string returns an object."); + is(typeof tag.title, "string", "returned object has a string property 'title'."); + is(typeof tag.type, "string", "returned object has a string property 'type'."); + is(typeof tag.name, "string", "returned object has a string property 'name'."); + is(typeof tag.defaultValue, "string", "returned object has a string property 'defaultValue'."); + is(typeof tag.isOptional, "boolean", "returned object has a boolean property 'isOptional'."); + is(typeof tag.desc, "string", "returned object has a string property 'desc'."); + + tag = new JSDOC.DocTag("param {widget} foo"); + is(tag.title, "param", "param title is found."); + is(tag.name, "foo", "param name is found when desc is missing."); + is(tag.desc, "", "param desc is empty when missing."); + + tag = new JSDOC.DocTag("param {object} date A valid date."); + is(tag.name, "date", "param name is found with a type."); + is(tag.type, "object", "param type is found."); + is(tag.desc, "A valid date.", "param desc is found with a type."); + + tag = new JSDOC.DocTag("param aName a description goes\n here."); + is(tag.name, "aName", "param name is found without a type."); + is(tag.desc, "a description goes\n here.", "param desc is found without a type."); + + tag = new JSDOC.DocTag("param {widget}"); + is(tag.name, "", "param name is empty when it is not given."); + + tag = new JSDOC.DocTag("param {widget} [foo] This is a description."); + is(tag.name, "foo", "optional param name is found."); + + tag = new JSDOC.DocTag("return {aType} This is a description."); + is(tag.type, "aType", "when return tag has no name, type is found."); + is(tag.desc, "This is a description.", "when return tag has no name, desc is found."); + + tag = new JSDOC.DocTag("author Joe Coder "); + is(tag.title, "author", "author tag has a title."); + is(tag.type, "", "the author tag has no type."); + is(tag.name, "", "the author tag has no name."); + is(tag.desc, "Joe Coder ", "author tag has desc."); + + tag = new JSDOC.DocTag("private \t\n "); + is(tag.title, "private", "private tag has a title."); + is(tag.type, "", "the private tag has no type."); + is(tag.name, "", "the private tag has no name."); + is(tag.desc, "", "private tag has no desc."); + + tag = new JSDOC.DocTag("example\n example(code);\n more();"); + is(tag.desc, " example(code);\n more();", "leading whitespace (less one) in examples code is preserved."); + + tag = new JSDOC.DocTag("param theName \n"); + is(tag.name, "theName", "name only is found."); + + tag = new JSDOC.DocTag("type theDesc \n"); + is(tag.desc, "theDesc", "desc only is found."); + + tag = new JSDOC.DocTag("type {theType} \n"); + is(tag.type, "theType", "type only is found."); + + tag = new JSDOC.DocTag(""); + is(tag.title, "", "title is empty when tag is empty."); + */ \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/JsDoc.js b/doc/jsdoc-toolkit/app/lib/JSDOC/JsDoc.js new file mode 100644 index 000000000..2c4bfb82b --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/JsDoc.js @@ -0,0 +1,126 @@ +/** + @constructor + @param [opt] Used to override the commandline options. Useful for testing. + @version $Id: JsDoc.js 773 2009-01-24 09:42:04Z micmath $ +*/ +JSDOC.JsDoc = function(/**object*/ opt) { + if (opt) { + JSDOC.opt = opt; + } + + if (JSDOC.opt.h) { + JSDOC.usage(); + quit(); + } + + // defend against options that are not sane + if (JSDOC.opt._.length == 0) { + LOG.warn("No source files to work on. Nothing to do."); + quit(); + } + if (JSDOC.opt.t === true || JSDOC.opt.d === true) { + JSDOC.usage(); + } + + if (typeof JSDOC.opt.d == "string") { + if (!JSDOC.opt.d.charAt(JSDOC.opt.d.length-1).match(/[\\\/]/)) { + JSDOC.opt.d = JSDOC.opt.d+"/"; + } + LOG.inform("Output directory set to '"+JSDOC.opt.d+"'."); + IO.mkPath(JSDOC.opt.d); + } + if (JSDOC.opt.e) IO.setEncoding(JSDOC.opt.e); + + // the -r option: scan source directories recursively + if (typeof JSDOC.opt.r == "boolean") JSDOC.opt.r = 10; + else if (!isNaN(parseInt(JSDOC.opt.r))) JSDOC.opt.r = parseInt(JSDOC.opt.r); + else JSDOC.opt.r = 1; + + // the -D option: define user variables + var D = {}; + if (JSDOC.opt.D) { + for (var i = 0; i < JSDOC.opt.D.length; i++) { + var defineParts = JSDOC.opt.D[i].split(":", 2); + if (defineParts) D[defineParts[0]] = defineParts[1]; + } + } + JSDOC.opt.D = D; + // combine any conf file D options with the commandline D options + if (defined(JSDOC.conf)) for (var c in JSDOC.conf.D) { + if (!defined(JSDOC.opt.D[c])) { + JSDOC.opt.D[c] = JSDOC.conf.D[c]; + } + } + + // Give plugins a chance to initialize + if (defined(JSDOC.PluginManager)) { + JSDOC.PluginManager.run("onInit", JSDOC.opt); + } + + JSDOC.opt.srcFiles = JSDOC.JsDoc._getSrcFiles(); + JSDOC.JsDoc._parseSrcFiles(); + JSDOC.JsDoc.symbolSet = JSDOC.Parser.symbols; +} + +/** + Retrieve source file list. + @returns {String[]} The pathnames of the files to be parsed. + */ +JSDOC.JsDoc._getSrcFiles = function() { + JSDOC.JsDoc.srcFiles = []; + + var ext = ["js"]; + if (JSDOC.opt.x) { + ext = JSDOC.opt.x.split(",").map(function($) {return $.toLowerCase()}); + } + + for (var i = 0; i < JSDOC.opt._.length; i++) { + JSDOC.JsDoc.srcFiles = JSDOC.JsDoc.srcFiles.concat( + IO.ls(JSDOC.opt._[i], JSDOC.opt.r).filter( + function($) { + var thisExt = $.split(".").pop().toLowerCase(); + + if (JSDOC.opt.E) { + for(var n = 0; n < JSDOC.opt.E.length; n++) { + if ($.match(new RegExp(JSDOC.opt.E[n]))) { + LOG.inform("Excluding " + $); + return false; // if the file matches the regex then it's excluded. + } + } + } + + return (ext.indexOf(thisExt) > -1); // we're only interested in files with certain extensions + } + ) + ); + } + + return JSDOC.JsDoc.srcFiles; +} + +JSDOC.JsDoc._parseSrcFiles = function() { + JSDOC.Parser.init(); + for (var i = 0, l = JSDOC.JsDoc.srcFiles.length; i < l; i++) { + var srcFile = JSDOC.JsDoc.srcFiles[i]; + + if (JSDOC.opt.v) LOG.inform("Parsing file: " + srcFile); + + try { + var src = IO.readFile(srcFile); + } + catch(e) { + LOG.warn("Can't read source file '"+srcFile+"': "+e.message); + } + + var tr = new JSDOC.TokenReader(); + var ts = new JSDOC.TokenStream(tr.tokenize(new JSDOC.TextStream(src))); + + JSDOC.Parser.parse(ts, srcFile); + + } + JSDOC.Parser.finish(); + + if (JSDOC.PluginManager) { + JSDOC.PluginManager.run("onFinishedParsing", JSDOC.Parser.symbols); + } +} diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/JsPlate.js b/doc/jsdoc-toolkit/app/lib/JSDOC/JsPlate.js new file mode 100644 index 000000000..bcaebc9c5 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/JsPlate.js @@ -0,0 +1,109 @@ +/** + @constructor +*/ +JSDOC.JsPlate = function(templateFile) { + if (templateFile) this.template = IO.readFile(templateFile); + + this.templateFile = templateFile; + this.code = ""; + this.parse(); +} + +JSDOC.JsPlate.prototype.parse = function() { + this.template = this.template.replace(/\{#[\s\S]+?#\}/gi, ""); + this.code = "var output=\u001e"+this.template; + + this.code = this.code.replace( + //gi, + function (match, eachName, inName) { + return "\u001e;\rvar $"+eachName+"_keys = keys("+inName+");\rfor(var $"+eachName+"_i = 0; $"+eachName+"_i < $"+eachName+"_keys.length; $"+eachName+"_i++) {\rvar $"+eachName+"_last = ($"+eachName+"_i == $"+eachName+"_keys.length-1);\rvar $"+eachName+"_key = $"+eachName+"_keys[$"+eachName+"_i];\rvar "+eachName+" = "+inName+"[$"+eachName+"_key];\routput+=\u001e"; + } + ); + this.code = this.code.replace(//g, "\u001e;\rif ($1) { output+=\u001e"); + this.code = this.code.replace(//g, "\u001e;}\relse if ($1) { output+=\u001e"); + this.code = this.code.replace(//g, "\u001e;}\relse { output+=\u001e"); + this.code = this.code.replace(/<\/(if|for)>/g, "\u001e;\r};\routput+=\u001e"); + this.code = this.code.replace( + /\{\+\s*([\s\S]+?)\s*\+\}/gi, + function (match, code) { + code = code.replace(/"/g, "\u001e"); // prevent qoute-escaping of inline code + code = code.replace(/(\r?\n)/g, " "); + return "\u001e+ ("+code+") +\u001e"; + } + ); + this.code = this.code.replace( + /\{!\s*([\s\S]+?)\s*!\}/gi, + function (match, code) { + code = code.replace(/"/g, "\u001e"); // prevent qoute-escaping of inline code + code = code.replace(/(\n)/g, " "); + return "\u001e; "+code+";\routput+=\u001e"; + } + ); + this.code = this.code+"\u001e;"; + + this.code = this.code.replace(/(\r?\n)/g, "\\n"); + this.code = this.code.replace(/"/g, "\\\""); + this.code = this.code.replace(/\u001e/g, "\""); +} + +JSDOC.JsPlate.prototype.toCode = function() { + return this.code; +} + +JSDOC.JsPlate.keys = function(obj) { + var keys = []; + if (obj.constructor.toString().indexOf("Array") > -1) { + for (var i = 0; i < obj.length; i++) { + keys.push(i); + } + } + else { + for (var i in obj) { + keys.push(i); + } + } + return keys; +}; + +JSDOC.JsPlate.values = function(obj) { + var values = []; + if (obj.constructor.toString().indexOf("Array") > -1) { + for (var i = 0; i < obj.length; i++) { + values.push(obj[i]); + } + } + else { + for (var i in obj) { + values.push(obj[i]); + } + } + return values; +}; + +JSDOC.JsPlate.prototype.process = function(data, compact) { + var keys = JSDOC.JsPlate.keys; + var values = JSDOC.JsPlate.values; + + try { + eval(this.code); + } + catch (e) { + print(">> There was an error evaluating the compiled code from template: "+this.templateFile); + print(" The error was on line "+e.lineNumber+" "+e.name+": "+e.message); + var lines = this.code.split("\r"); + if (e.lineNumber-2 >= 0) print("line "+(e.lineNumber-1)+": "+lines[e.lineNumber-2]); + print("line "+e.lineNumber+": "+lines[e.lineNumber-1]); + print(""); + } + + if (compact) { // patch by mcbain.asm + // Remove lines that contain only space-characters, usually left by lines in the template + // which originally only contained JSPlate tags or code. This makes it easier to write + // non-tricky templates which still put out nice code (not bloated with extra lines). + // Lines purposely left blank (just a line ending) are left alone. + output = output.replace(/\s+?(\r?)\n/g, "$1\n"); + } + + /*debug*///print(this.code); + return output; +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/Lang.js b/doc/jsdoc-toolkit/app/lib/JSDOC/Lang.js new file mode 100644 index 000000000..62919d7d8 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/Lang.js @@ -0,0 +1,144 @@ +/** + @namespace +*/ +JSDOC.Lang = { +} + +JSDOC.Lang.isBuiltin = function(name) { + return (JSDOC.Lang.isBuiltin.coreObjects.indexOf(name) > -1); +} +JSDOC.Lang.isBuiltin.coreObjects = ['_global_', 'Array', 'Boolean', 'Date', 'Error', 'Function', 'Math', 'Number', 'Object', 'RegExp', 'String']; + +JSDOC.Lang.whitespace = function(ch) { + return JSDOC.Lang.whitespace.names[ch]; +} +JSDOC.Lang.whitespace.names = { + " ": "SPACE", + "\f": "FORMFEED", + "\t": "TAB", + "\u0009": "UNICODE_TAB", + "\u000A": "UNICODE_NBR", + "\u0008": "VERTICAL_TAB" +}; + +JSDOC.Lang.newline = function(ch) { + return JSDOC.Lang.newline.names[ch]; +} +JSDOC.Lang.newline.names = { + "\n": "NEWLINE", + "\r": "RETURN", + "\u000A": "UNICODE_LF", + "\u000D": "UNICODE_CR", + "\u2029": "UNICODE_PS", + "\u2028": "UNICODE_LS" +}; + +JSDOC.Lang.keyword = function(word) { + return JSDOC.Lang.keyword.names["="+word]; +} +JSDOC.Lang.keyword.names = { + "=break": "BREAK", + "=case": "CASE", + "=catch": "CATCH", + "=const": "VAR", + "=continue": "CONTINUE", + "=default": "DEFAULT", + "=delete": "DELETE", + "=do": "DO", + "=else": "ELSE", + "=false": "FALSE", + "=finally": "FINALLY", + "=for": "FOR", + "=function": "FUNCTION", + "=if": "IF", + "=in": "IN", + "=instanceof": "INSTANCEOF", + "=new": "NEW", + "=null": "NULL", + "=return": "RETURN", + "=switch": "SWITCH", + "=this": "THIS", + "=throw": "THROW", + "=true": "TRUE", + "=try": "TRY", + "=typeof": "TYPEOF", + "=void": "VOID", + "=while": "WHILE", + "=with": "WITH", + "=var": "VAR" +}; + +JSDOC.Lang.punc = function(ch) { + return JSDOC.Lang.punc.names[ch]; +} +JSDOC.Lang.punc.names = { + ";": "SEMICOLON", + ",": "COMMA", + "?": "HOOK", + ":": "COLON", + "||": "OR", + "&&": "AND", + "|": "BITWISE_OR", + "^": "BITWISE_XOR", + "&": "BITWISE_AND", + "===": "STRICT_EQ", + "==": "EQ", + "=": "ASSIGN", + "!==": "STRICT_NE", + "!=": "NE", + "<<": "LSH", + "<=": "LE", + "<": "LT", + ">>>": "URSH", + ">>": "RSH", + ">=": "GE", + ">": "GT", + "++": "INCREMENT", + "--": "DECREMENT", + "+": "PLUS", + "-": "MINUS", + "*": "MUL", + "/": "DIV", + "%": "MOD", + "!": "NOT", + "~": "BITWISE_NOT", + ".": "DOT", + "[": "LEFT_BRACKET", + "]": "RIGHT_BRACKET", + "{": "LEFT_CURLY", + "}": "RIGHT_CURLY", + "(": "LEFT_PAREN", + ")": "RIGHT_PAREN" +}; + +JSDOC.Lang.matching = function(name) { + return JSDOC.Lang.matching.names[name]; +} +JSDOC.Lang.matching.names = { + "LEFT_PAREN": "RIGHT_PAREN", + "RIGHT_PAREN": "LEFT_PAREN", + "LEFT_CURLY": "RIGHT_CURLY", + "RIGHT_CURLY": "LEFT_CURLY", + "LEFT_BRACE": "RIGHT_BRACE", + "RIGHT_BRACE": "LEFT_BRACE" +} + +JSDOC.Lang.isNumber = function(str) { + return /^(\.[0-9]|[0-9]+\.|[0-9])[0-9]*([eE][+-][0-9]+)?$/i.test(str); +} + +JSDOC.Lang.isHexDec = function(str) { + return /^0x[0-9A-F]+$/i.test(str); +} + +JSDOC.Lang.isWordChar = function(str) { + return /^[a-zA-Z0-9$_.]+$/.test(str); +} + +JSDOC.Lang.isSpace = function(str) { + return (typeof JSDOC.Lang.whitespace(str) != "undefined"); +} + +JSDOC.Lang.isNewline = function(str) { + return (typeof JSDOC.Lang.newline(str) != "undefined"); +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/Parser.js b/doc/jsdoc-toolkit/app/lib/JSDOC/Parser.js new file mode 100644 index 000000000..f407dd1a1 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/Parser.js @@ -0,0 +1,145 @@ +if (typeof JSDOC == "undefined") JSDOC = {}; + +/** + @namespace + @requires JSDOC.Walker + @requires JSDOC.Symbol + @requires JSDOC.DocComment +*/ +JSDOC.Parser = { + conf: { + ignoreCode: JSDOC.opt.n, + ignoreAnonymous: true, // factory: true + treatUnderscoredAsPrivate: true, // factory: true + explain: false // factory: false + }, + + addSymbol: function(symbol) { + +if (JSDOC.Parser.rename) { + for (var n in JSDOC.Parser.rename) { + if (symbol.alias.indexOf(n) == 0) { + if (symbol.name == symbol.alias) { + symbol.name = symbol.name.replace(n, JSDOC.Parser.rename[n]); + } + symbol.alias = symbol.alias.replace(n, JSDOC.Parser.rename[n]); + } + } +} + +if (JSDOC.opt.S) { + if (typeof JSDOC.Parser.secureModules == "undefined") JSDOC.Parser.secureModules = {}; + if (/^exports\./.test(symbol.alias)) { + symbol.srcFile.match(/(^|[\\\/])([^\\\/]+)\.js/i); + var fileNS = RegExp.$2; + symbol.alias = symbol.alias.replace(/^exports\./, fileNS); + symbol.name = symbol.name.replace(/^exports\./, ""); + symbol.memberOf = fileNS; + + // need to create the namespace associated with this file first + if (!JSDOC.Parser.secureModules[fileNS]) { + JSDOC.Parser.secureModules[fileNS] = 1; + var nsSymbol = new JSDOC.Symbol(fileNS, [], "GLOBAL", new JSDOC.DocComment("")); + nsSymbol.isNamespace = true; + nsSymbol.srcFile = ""; + nsSymbol.isPrivate = false; + nsSymbol.srcFile = symbol.srcFile; + nsSymbol.desc = (JSDOC.Parser.symbols.getSymbol(symbol.srcFile) || {desc: ""}).desc; + JSDOC.Parser.addSymbol(nsSymbol); + + } + } + else { // a method that is not exported? + if (!symbol.isNamespace) return; + } +} + // if a symbol alias is documented more than once the last one with the user docs wins + if (JSDOC.Parser.symbols.hasSymbol(symbol.alias)) { + var oldSymbol = JSDOC.Parser.symbols.getSymbol(symbol.alias); + if (oldSymbol.comment.isUserComment) { + if (symbol.comment.isUserComment) { // old and new are both documented + LOG.warn("The symbol '"+symbol.alias+"' is documented more than once."); + } + else { // old is documented but new isn't + return; + } + } + } + + // we don't document anonymous things + if (JSDOC.Parser.conf.ignoreAnonymous && symbol.name.match(/\$anonymous\b/)) return; + + // uderscored things may be treated as if they were marked private, this cascades + if (JSDOC.Parser.conf.treatUnderscoredAsPrivate && symbol.name.match(/[.#-]_[^.#-]+$/)) { + if (!symbol.comment.getTag("public").length > 0) symbol.isPrivate = true; + } + + // -p flag is required to document private things + if (!JSDOC.opt.p && symbol.isPrivate) return; // issue #161 fixed by mcbain.asm + + // ignored things are not documented, this doesn't cascade + if (symbol.isIgnored) return; + JSDOC.Parser.symbols.addSymbol(symbol); + }, + + addBuiltin: function(name) { + var builtin = new JSDOC.Symbol(name, [], "CONSTRUCTOR", new JSDOC.DocComment("")); + builtin.isNamespace = true; + builtin.srcFile = ""; + builtin.isPrivate = false; + JSDOC.Parser.addSymbol(builtin); + return builtin; + }, + + init: function() { + JSDOC.Parser.symbols = new JSDOC.SymbolSet(); + JSDOC.Parser.walker = new JSDOC.Walker(); + }, + + finish: function() { + JSDOC.Parser.symbols.relate(); + + // make a litle report about what was found + if (JSDOC.Parser.conf.explain) { + var symbols = JSDOC.Parser.symbols.toArray(); + var srcFile = ""; + for (var i = 0, l = symbols.length; i < l; i++) { + var symbol = symbols[i]; + if (srcFile != symbol.srcFile) { + srcFile = symbol.srcFile; + print("\n"+srcFile+"\n-------------------"); + } + print(i+":\n alias => "+symbol.alias + "\n name => "+symbol.name+ "\n isa => "+symbol.isa + "\n memberOf => " + symbol.memberOf + "\n isStatic => " + symbol.isStatic + ", isInner => " + symbol.isInner+ ", isPrivate => " + symbol.isPrivate); + } + print("-------------------\n"); + } + } +} + +JSDOC.Parser.parse = function(/**JSDOC.TokenStream*/ts, /**String*/srcFile) { + JSDOC.Symbol.srcFile = (srcFile || ""); + JSDOC.DocComment.shared = ""; // shared comments don't cross file boundaries + + if (!JSDOC.Parser.walker) JSDOC.Parser.init(); + JSDOC.Parser.walker.walk(ts); // adds to our symbols + + // filter symbols by option + for (var p = JSDOC.Parser.symbols._index.first(); p; p = JSDOC.Parser.symbols._index.next()) { + var symbol = p.value; + + if (!symbol) continue; + + if (symbol.is("FILE") || symbol.is("GLOBAL")) { + continue; + } + else if (!JSDOC.opt.a && !symbol.comment.isUserComment) { + JSDOC.Parser.symbols.deleteSymbol(symbol.alias); + } + + if (/#$/.test(symbol.alias)) { // we don't document prototypes + JSDOC.Parser.symbols.deleteSymbol(symbol.alias); + } + } + + return JSDOC.Parser.symbols.toArray(); +} diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/PluginManager.js b/doc/jsdoc-toolkit/app/lib/JSDOC/PluginManager.js new file mode 100644 index 000000000..9c9119310 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/PluginManager.js @@ -0,0 +1,33 @@ +/** + @namespace Holds functionality related to running plugins. +*/ +JSDOC.PluginManager = { +} + +/** + @param name A unique name that identifies that plugin. + @param handlers A collection of named functions. The names correspond to hooks in the core code. +*/ +JSDOC.PluginManager.registerPlugin = function(/**String*/name, /**Object*/handlers) { + if (!defined(JSDOC.PluginManager.plugins)) + /** The collection of all plugins. Requires a unique name for each. + */ + JSDOC.PluginManager.plugins = {}; + + + JSDOC.PluginManager.plugins[name] = handlers; +} + +/** + @param hook The name of the hook that is being caught. + @param target Any object. This will be passed as the only argument to the handler whose + name matches the hook name. Handlers cannot return a value, so must modify the target + object to have an effect. +*/ +JSDOC.PluginManager.run = function(/**String*/hook, /**Mixed*/target) { + for (var name in JSDOC.PluginManager.plugins) { + if (defined(JSDOC.PluginManager.plugins[name][hook])) { + JSDOC.PluginManager.plugins[name][hook](target); + } + } +} diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/Symbol.js b/doc/jsdoc-toolkit/app/lib/JSDOC/Symbol.js new file mode 100644 index 000000000..c5b76a860 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/Symbol.js @@ -0,0 +1,645 @@ +if (typeof JSDOC == "undefined") JSDOC = {}; + +/** + Create a new Symbol. + @class Represents a symbol in the source code. + */ +JSDOC.Symbol = function() { + this.init(); + if (arguments.length) this.populate.apply(this, arguments); +} + +JSDOC.Symbol.count = 0; + +JSDOC.Symbol.prototype.init = function() { + this._name = ""; + this._params = []; + this.$args = []; + this.addOn = ""; + this.alias = ""; + this.augments = []; + this.author = ""; + this.classDesc = ""; + this.comment = {}; + this.defaultValue = undefined; + this.deprecated = ""; + this.desc = ""; + this.example = []; + this.exceptions = []; + this.fires = []; + this.id = JSDOC.Symbol.count++; + this.inherits = []; + this.inheritsFrom = []; + this.isa = "OBJECT"; + this.isConstant = false; + this.isEvent = false; + this.isIgnored = false; + this.isInner = false; + this.isNamespace = false; + this.isPrivate = false; + this.isStatic = false; + this.memberOf = ""; + this.methods = []; + this.properties = []; + this.requires = []; + this.returns = []; + this.see = []; + this.since = ""; + this.srcFile = {}; + this.type = ""; + this.version = ""; +} + +JSDOC.Symbol.prototype.serialize = function() { + var keys = []; + for (var p in this) { + keys.push (p); + } + keys = keys.sort(); + + var out = ""; + for (var i in keys) { + if (typeof this[keys[i]] == "function") continue; + out += keys[i]+" => "+Dumper.dump(this[keys[i]])+",\n"; + } + return "\n{\n" + out + "}\n"; +} + +JSDOC.Symbol.prototype.clone = function() { + var clone = new JSDOC.Symbol(); + clone.populate.apply(clone, this.$args); // repopulate using the original arguments + clone.srcFile = this.srcFile; // not the current srcFile, the one when the original was made + return clone; +} + +JSDOC.Symbol.prototype.__defineSetter__("name", + function(n) { n = n.replace(/^_global_[.#-]/, ""); n = n.replace(/\.prototype\.?/g, '#'); this._name = n; } +); +JSDOC.Symbol.prototype.__defineGetter__("name", + function() { return this._name; } +); +JSDOC.Symbol.prototype.__defineSetter__("params", + function(v) { + for (var i = 0, l = v.length; i < l; i++) { + if (v[i].constructor != JSDOC.DocTag) { // may be a generic object parsed from signature, like {type:..., name:...} + this._params[i] = new JSDOC.DocTag("param"+((v[i].type)?" {"+v[i].type+"}":"")+" "+v[i].name); + } + else { + this._params[i] = v[i]; + } + } + } +); +JSDOC.Symbol.prototype.__defineGetter__("params", + function() { return this._params; } +); + +JSDOC.Symbol.prototype.getEvents = function() { + var events = []; + for (var i = 0, l = this.methods.length; i < l; i++) { + if (this.methods[i].isEvent) { + this.methods[i].name = this.methods[i].name.replace("event:", ""); + events.push(this.methods[i]); + } + } + return events; +} + +JSDOC.Symbol.prototype.getMethods = function() { + var nonEvents = []; + for (var i = 0, l = this.methods.length; i < l; i++) { + if (!this.methods[i].isEvent) { + nonEvents.push(this.methods[i]); + } + } + return nonEvents; +} + + +JSDOC.Symbol.prototype.populate = function( + /** String */ name, + /** Object[] */ params, + /** String */ isa, + /** JSDOC.DocComment */ comment +) { + this.$args = arguments; + + this.name = name; + this.alias = this.name; + + this.params = params; + this.isa = (isa == "VIRTUAL")? "OBJECT":isa; + this.comment = comment || new JSDOC.DocComment(""); + this.srcFile = JSDOC.Symbol.srcFile; + + if (this.is("FILE") && !this.alias) this.alias = this.srcFile; + + this.setTags(); + + if (typeof JSDOC.PluginManager != "undefined") { + JSDOC.PluginManager.run("onSymbol", this); + } +} + +JSDOC.Symbol.prototype.setTags = function() { + // @author + var authors = this.comment.getTag("author"); + if (authors.length) { + this.author = authors.map(function($){return $.desc;}).join(", "); + } + + /*t: + plan(34, "testing JSDOC.Symbol"); + + requires("../lib/JSDOC/DocComment.js"); + requires("../frame/String.js"); + requires("../lib/JSDOC/DocTag.js"); + + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@author Joe Smith*"+"/")); + is(sym.author, "Joe Smith", "@author tag, author is found."); + */ + + // @desc + var descs = this.comment.getTag("desc"); + if (descs.length) { + this.desc = descs.map(function($){return $.desc;}).join("\n"); // multiple descriptions are concatenated into one + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@desc This is a description.*"+"/")); + is(sym.desc, "This is a description.", "@desc tag, description is found."); + */ + + // @overview + if (this.is("FILE")) { + if (!this.alias) this.alias = this.srcFile; + + var overviews = this.comment.getTag("overview"); + if (overviews.length) { + this.desc = [this.desc].concat(overviews.map(function($){return $.desc;})).join("\n"); + } + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@overview This is an overview.*"+"/")); + is(sym.desc, "\nThis is an overview.", "@overview tag, description is found."); + */ + + // @since + var sinces = this.comment.getTag("since"); + if (sinces.length) { + this.since = sinces.map(function($){return $.desc;}).join(", "); + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@since 1.01*"+"/")); + is(sym.since, "1.01", "@since tag, description is found."); + */ + + // @constant + if (this.comment.getTag("constant").length) { + this.isConstant = true; + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@constant*"+"/")); + is(sym.isConstant, true, "@constant tag, isConstant set."); + */ + + // @version + var versions = this.comment.getTag("version"); + if (versions.length) { + this.version = versions.map(function($){return $.desc;}).join(", "); + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@version 2.0x*"+"/")); + is(sym.version, "2.0x", "@version tag, version is found."); + */ + + // @deprecated + var deprecateds = this.comment.getTag("deprecated"); + if (deprecateds.length) { + this.deprecated = deprecateds.map(function($){return $.desc;}).join("\n"); + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@deprecated Use other method.*"+"/")); + is(sym.deprecated, "Use other method.", "@deprecated tag, desc is found."); + */ + + // @example + var examples = this.comment.getTag("example"); + if (examples.length) { + this.example = examples.map( + // trim trailing whitespace + function($) { + $.desc = $.desc.replace(/\s+$/, ""); + return $; + } + ); + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@example This\n is an example. \n*"+"/")); + isnt(typeof sym.example[0], "undefined", "@example tag, creates sym.example array."); + is(sym.example[0], "This\n is an example.", "@example tag, desc is found."); + */ + + // @see + var sees = this.comment.getTag("see"); + if (sees.length) { + var thisSee = this.see; + sees.map(function($){thisSee.push($.desc);}); + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@see The other thing.*"+"/")); + is(sym.see, "The other thing.", "@see tag, desc is found."); + */ + + // @class + var classes = this.comment.getTag("class"); + if (classes.length) { + this.isa = "CONSTRUCTOR"; + this.classDesc = classes[0].desc; // desc can't apply to the constructor as there is none. + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@class This describes the class.*"+"/")); + is(sym.isa, "CONSTRUCTOR", "@class tag, makes symbol a constructor."); + is(sym.classDesc, "This describes the class.", "@class tag, class description is found."); + */ + + // @namespace + var namespaces = this.comment.getTag("namespace"); + if (namespaces.length) { + this.classDesc = namespaces[0].desc; + this.isNamespace = true; + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@namespace This describes the namespace.*"+"/")); + is(sym.classDesc, "This describes the namespace.", "@namespace tag, class description is found."); + */ + + // @param + var params = this.comment.getTag("param"); + if (params.length) { + // user-defined params overwrite those with same name defined by the parser + var thisParams = this.params; + + if (thisParams.length == 0) { // none exist yet, so just bung all these user-defined params straight in + this.params = params; + } + else { // need to overlay these user-defined params on to existing parser-defined params + for (var i = 0, l = params.length; i < l; i++) { + if (thisParams[i]) { + if (params[i].type) thisParams[i].type = params[i].type; + thisParams[i].name = params[i].name; + thisParams[i].desc = params[i].desc; + thisParams[i].isOptional = params[i].isOptional; + thisParams[i].defaultValue = params[i].defaultValue; + } + else thisParams[i] = params[i]; + } + } + } + + /*t: + var sym = new JSDOC.Symbol("foo", [{type: "array", name: "pages"}], "FUNCTION", new JSDOC.DocComment("/**Description.*"+"/")); + is(sym.params.length, 1, "parser defined param is found."); + + sym = new JSDOC.Symbol("foo", [], "FUNCTION", new JSDOC.DocComment("/**Description.\n@param {array} pages*"+"/")); + is(sym.params.length, 1, "user defined param is found."); + is(sym.params[0].type, "array", "user defined param type is found."); + is(sym.params[0].name, "pages", "user defined param name is found."); + + sym = new JSDOC.Symbol("foo", [{type: "array", name: "pages"}], "FUNCTION", new JSDOC.DocComment("/**Description.\n@param {string} uid*"+"/")); + is(sym.params.length, 1, "user defined param overwrites parser defined param."); + is(sym.params[0].type, "string", "user defined param type overwrites parser defined param type."); + is(sym.params[0].name, "uid", "user defined param name overwrites parser defined param name."); + + sym = new JSDOC.Symbol("foo", [{type: "array", name: "pages"}, {type: "number", name: "count"}], "FUNCTION", new JSDOC.DocComment("/**Description.\n@param {string} uid*"+"/")); + is(sym.params.length, 2, "user defined params overlay parser defined params."); + is(sym.params[1].type, "number", "user defined param type overlays parser defined param type."); + is(sym.params[1].name, "count", "user defined param name overlays parser defined param name."); + + sym = new JSDOC.Symbol("foo", [], "FUNCTION", new JSDOC.DocComment("/**Description.\n@param {array} pages The pages description.*"+"/")); + is(sym.params.length, 1, "user defined param with description is found."); + is(sym.params[0].desc, "The pages description.", "user defined param description is found."); + */ + + // @constructor + if (this.comment.getTag("constructor").length) { + this.isa = "CONSTRUCTOR"; + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@constructor*"+"/")); + is(sym.isa, "CONSTRUCTOR", "@constructor tag, makes symbol a constructor."); + */ + + // @static + if (this.comment.getTag("static").length) { + this.isStatic = true; + if (this.isa == "CONSTRUCTOR") { + this.isNamespace = true; + } + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@static\n@constructor*"+"/")); + is(sym.isStatic, true, "@static tag, makes isStatic true."); + is(sym.isNamespace, true, "@static and @constructor tag, makes isNamespace true."); + */ + + // @inner + if (this.comment.getTag("inner").length) { + this.isInner = true; + this.isStatic = false; + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@inner*"+"/")); + is(sym.isStatic, false, "@inner tag, makes isStatic false."); + is(sym.isInner, true, "@inner makes isInner true."); + */ + + // @name + var names = this.comment.getTag("name"); + if (names.length) { + this.name = names[0].desc; + } + + /*t: + // todo + */ + + // @field + if (this.comment.getTag("field").length) { + this.isa = "OBJECT"; + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "FUNCTION", new JSDOC.DocComment("/**@field*"+"/")); + is(sym.isa, "OBJECT", "@field tag, makes symbol an object."); + */ + + // @function + if (this.comment.getTag("function").length) { + this.isa = "FUNCTION"; + if (/event:/.test(this.alias)) this.isEvent = true; + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@function*"+"/")); + is(sym.isa, "FUNCTION", "@function tag, makes symbol a function."); + */ + + // @event + var events = this.comment.getTag("event"); + if (events.length) { + this.isa = "FUNCTION"; + this.isEvent = true; + if (!/event:/.test(this.alias)) + this.alias = this.alias.replace(/^(.*[.#-])([^.#-]+)$/, "$1event:$2"); + } + + /*t: + var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@event*"+"/")); + is(sym.isa, "FUNCTION", "@event tag, makes symbol a function."); + is(sym.isEvent, true, "@event makes isEvent true."); + */ + + // @fires + var fires = this.comment.getTag("fires"); + if (fires.length) { + for (var i = 0; i < fires.length; i++) { + this.fires.push(fires[i].desc); + } + } + + /*t: + // todo + */ + + // @property + var properties = this.comment.getTag("property"); + if (properties.length) { + thisProperties = this.properties; + for (var i = 0; i < properties.length; i++) { + var property = new JSDOC.Symbol(this.alias+"#"+properties[i].name, [], "OBJECT", new JSDOC.DocComment("/**"+properties[i].desc+"\n@name "+properties[i].name+"\n@memberOf "+this.alias+"#*/")); + // TODO: shouldn't the following happen in the addProperty method of Symbol? + property.name = properties[i].name; + property.memberOf = this.alias; + if (properties[i].type) property.type = properties[i].type; + if (properties[i].defaultValue) property.defaultValue = properties[i].defaultValue; + this.addProperty(property); + JSDOC.Parser.addSymbol(property); + } + } + + /*t: + // todo + */ + + // @return + var returns = this.comment.getTag("return"); + if (returns.length) { // there can be many return tags in a single doclet + this.returns = returns; + this.type = returns.map(function($){return $.type}).join(", "); + } + + /*t: + // todo + */ + + // @exception + this.exceptions = this.comment.getTag("throws"); + + /*t: + // todo + */ + + // @requires + var requires = this.comment.getTag("requires"); + if (requires.length) { + this.requires = requires.map(function($){return $.desc}); + } + + /*t: + // todo + */ + + // @type + var types = this.comment.getTag("type"); + if (types.length) { + this.type = types[0].desc; //multiple type tags are ignored + } + + /*t: + // todo + */ + + // @private + if (this.comment.getTag("private").length || this.isInner) { + this.isPrivate = true; + } + + // @ignore + if (this.comment.getTag("ignore").length) { + this.isIgnored = true; + } + + /*t: + // todo + */ + + // @inherits ... as ... + var inherits = this.comment.getTag("inherits"); + if (inherits.length) { + for (var i = 0; i < inherits.length; i++) { + if (/^\s*([a-z$0-9_.#:-]+)(?:\s+as\s+([a-z$0-9_.#:-]+))?/i.test(inherits[i].desc)) { + var inAlias = RegExp.$1; + var inAs = RegExp.$2 || inAlias; + + if (inAlias) inAlias = inAlias.replace(/\.prototype\.?/g, "#"); + + if (inAs) { + inAs = inAs.replace(/\.prototype\.?/g, "#"); + inAs = inAs.replace(/^this\.?/, "#"); + } + + if (inAs.indexOf(inAlias) != 0) { //not a full namepath + var joiner = "."; + if (this.alias.charAt(this.alias.length-1) == "#" || inAs.charAt(0) == "#") { + joiner = ""; + } + inAs = this.alias + joiner + inAs; + } + } + this.inherits.push({alias: inAlias, as: inAs}); + } + } + + /*t: + // todo + */ + + // @augments + this.augments = this.comment.getTag("augments"); + + // @default + var defaults = this.comment.getTag("default"); + if (defaults.length) { + if (this.is("OBJECT")) { + this.defaultValue = defaults[0].desc; + } + } + + /*t: + // todo + */ + + // @memberOf + var memberOfs = this.comment.getTag("memberOf"); + if (memberOfs.length) { + this.memberOf = memberOfs[0].desc; + this.memberOf = this.memberOf.replace(/\.prototype\.?/g, "#"); + } + + /*t: + // todo + */ + + // @public + if (this.comment.getTag("public").length) { + this.isPrivate = false; + } + + /*t: + // todo + */ + + if (JSDOC.PluginManager) { + JSDOC.PluginManager.run("onSetTags", this); + } +} + +JSDOC.Symbol.prototype.is = function(what) { + return this.isa === what; +} + +JSDOC.Symbol.prototype.isBuiltin = function() { + return JSDOC.Lang.isBuiltin(this.alias); +} + +JSDOC.Symbol.prototype.setType = function(/**String*/comment, /**Boolean*/overwrite) { + if (!overwrite && this.type) return; + var typeComment = JSDOC.DocComment.unwrapComment(comment); + this.type = typeComment; +} + +JSDOC.Symbol.prototype.inherit = function(symbol) { + if (!this.hasMember(symbol.name) && !symbol.isInner) { + if (symbol.is("FUNCTION")) + this.methods.push(symbol); + else if (symbol.is("OBJECT")) + this.properties.push(symbol); + } +} + +JSDOC.Symbol.prototype.hasMember = function(name) { + return (this.hasMethod(name) || this.hasProperty(name)); +} + +JSDOC.Symbol.prototype.addMember = function(symbol) { + if (symbol.is("FUNCTION")) { this.addMethod(symbol); } + else if (symbol.is("OBJECT")) { this.addProperty(symbol); } +} + +JSDOC.Symbol.prototype.hasMethod = function(name) { + var thisMethods = this.methods; + for (var i = 0, l = thisMethods.length; i < l; i++) { + if (thisMethods[i].name == name) return true; + if (thisMethods[i].alias == name) return true; + } + return false; +} + +JSDOC.Symbol.prototype.addMethod = function(symbol) { + var methodAlias = symbol.alias; + var thisMethods = this.methods; + for (var i = 0, l = thisMethods.length; i < l; i++) { + if (thisMethods[i].alias == methodAlias) { + thisMethods[i] = symbol; // overwriting previous method + return; + } + } + thisMethods.push(symbol); // new method with this alias +} + +JSDOC.Symbol.prototype.hasProperty = function(name) { + var thisProperties = this.properties; + for (var i = 0, l = thisProperties.length; i < l; i++) { + if (thisProperties[i].name == name) return true; + if (thisProperties[i].alias == name) return true; + } + return false; +} + +JSDOC.Symbol.prototype.addProperty = function(symbol) { + var propertyAlias = symbol.alias; + var thisProperties = this.properties; + for (var i = 0, l = thisProperties.length; i < l; i++) { + if (thisProperties[i].alias == propertyAlias) { + thisProperties[i] = symbol; // overwriting previous property + return; + } + } + + thisProperties.push(symbol); // new property with this alias +} + +JSDOC.Symbol.srcFile = ""; //running reference to the current file being parsed diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/SymbolSet.js b/doc/jsdoc-toolkit/app/lib/JSDOC/SymbolSet.js new file mode 100644 index 000000000..82657e44e --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/SymbolSet.js @@ -0,0 +1,241 @@ +/** @constructor */ +JSDOC.SymbolSet = function() { + this.init(); +} + +JSDOC.SymbolSet.prototype.init = function() { + this._index = new Hash(); +} + +JSDOC.SymbolSet.prototype.keys = function() { + return this._index.keys(); +} + +JSDOC.SymbolSet.prototype.hasSymbol = function(alias) { + return this._index.hasKey(alias); +} + +JSDOC.SymbolSet.prototype.addSymbol = function(symbol) { + if (this.hasSymbol(symbol.alias)) { + LOG.warn("Overwriting symbol documentation for: "+symbol.alias + "."); + } + this._index.set(symbol.alias, symbol); +} + +JSDOC.SymbolSet.prototype.getSymbol = function(alias) { + if (this.hasSymbol(alias)) return this._index.get(alias); +} + +JSDOC.SymbolSet.prototype.getSymbolByName = function(name) { + for (var p = this._index.first(); p; p = this._index.next()) { + var symbol = p.value; + if (symbol.name == name) return symbol; + } +} + +JSDOC.SymbolSet.prototype.toArray = function() { + return this._index.values(); +} + +JSDOC.SymbolSet.prototype.deleteSymbol = function(alias) { + if (!this.hasSymbol(alias)) return; + this._index.drop(alias); +} + +JSDOC.SymbolSet.prototype.renameSymbol = function(oldName, newName) { + // todo: should check if oldname or newname already exist + this._index.replace(oldName, newName); + this._index.get(newName).alias = newName; + return newName; +} + +JSDOC.SymbolSet.prototype.relate = function() { + this.resolveBorrows(); + this.resolveMemberOf(); + this.resolveAugments(); +} + +JSDOC.SymbolSet.prototype.resolveBorrows = function() { + for (var p = this._index.first(); p; p = this._index.next()) { + var symbol = p.value; + if (symbol.is("FILE") || symbol.is("GLOBAL")) continue; + + var borrows = symbol.inherits; + for (var i = 0; i < borrows.length; i++) { + +if (/#$/.test(borrows[i].alias)) { + LOG.warn("Attempted to borrow entire instance of "+borrows[i].alias+" but that feature is not yet implemented."); + return; +} + var borrowed = this.getSymbol(borrows[i].alias); + + if (!borrowed) { + LOG.warn("Can't borrow undocumented "+borrows[i].alias+"."); + continue; + } + + if (borrows[i].as == borrowed.alias) { + var assumedName = borrowed.name.split(/([#.-])/).pop(); + borrows[i].as = symbol.name+RegExp.$1+assumedName; + LOG.inform("Assuming borrowed as name is "+borrows[i].as+" but that feature is experimental."); + } + + var borrowAsName = borrows[i].as; + var borrowAsAlias = borrowAsName; + if (!borrowAsName) { + LOG.warn("Malformed @borrow, 'as' is required."); + continue; + } + + if (borrowAsName.length > symbol.alias.length && borrowAsName.indexOf(symbol.alias) == 0) { + borrowAsName = borrowAsName.replace(borrowed.alias, "") + } + else { + var joiner = ""; + if (borrowAsName.charAt(0) != "#") joiner = "."; + borrowAsAlias = borrowed.alias + joiner + borrowAsName; + } + + borrowAsName = borrowAsName.replace(/^[#.]/, ""); + + if (this.hasSymbol(borrowAsAlias)) continue; + + var clone = borrowed.clone(); + clone.name = borrowAsName; + clone.alias = borrowAsAlias; + this.addSymbol(clone); + } + } +} + +JSDOC.SymbolSet.prototype.resolveMemberOf = function() { + for (var p = this._index.first(); p; p = this._index.next()) { + var symbol = p.value; + if (symbol.is("FILE") || symbol.is("GLOBAL")) continue; + + // the memberOf value was provided in the @memberOf tag + else if (symbol.memberOf) { + + // like foo.bar is a memberOf foo + if (symbol.alias.indexOf(symbol.memberOf) == 0) { + var memberMatch = new RegExp("^("+symbol.memberOf+")[.#-]?(.+)$"); + var aliasParts = symbol.alias.match(memberMatch); + + if (aliasParts) { + symbol.memberOf = aliasParts[1]; + symbol.name = aliasParts[2]; + } + + var nameParts = symbol.name.match(memberMatch); + + if (nameParts) { + symbol.name = nameParts[2]; + } + } + // like bar is a memberOf foo + else { + var joiner = symbol.memberOf.charAt(symbol.memberOf.length-1); + if (!/[.#-]/.test(joiner)) symbol.memberOf += "."; + this.renameSymbol(symbol.alias, symbol.memberOf + symbol.name); + } + } + // the memberOf must be calculated + else { + var parts = symbol.alias.match(/^(.*[.#-])([^.#-]+)$/); + if (parts) { + symbol.memberOf = parts[1]; + symbol.name = parts[2]; + } + } + + // set isStatic, isInner + if (symbol.memberOf) { + switch (symbol.memberOf.charAt(symbol.memberOf.length-1)) { + case '#' : + symbol.isStatic = false; + symbol.isInner = false; + break; + case '.' : + symbol.isStatic = true; + symbol.isInner = false; + break; + case '-' : + symbol.isStatic = false; + symbol.isInner = true; + break; + default: // memberOf ends in none of the above + symbol.isStatic = true; + break; + } + } + + // unowned methods and fields belong to the global object + if (!symbol.is("CONSTRUCTOR") && !symbol.isNamespace && symbol.memberOf == "") { + symbol.memberOf = "_global_"; + } + + // clean up + if (symbol.memberOf.match(/[.#-]$/)) { + symbol.memberOf = symbol.memberOf.substr(0, symbol.memberOf.length-1); + } + // add to parent's methods or properties list + if (symbol.memberOf) { + + var container = this.getSymbol(symbol.memberOf); + if (!container) { + if (JSDOC.Lang.isBuiltin(symbol.memberOf)) container = JSDOC.Parser.addBuiltin(symbol.memberOf); + else { + LOG.warn("Trying to document "+symbol.name +" as a member of undocumented symbol "+symbol.memberOf+"."); + } + } + + if (container) container.addMember(symbol); + } + } +} + +JSDOC.SymbolSet.prototype.resolveAugments = function() { + for (var p = this._index.first(); p; p = this._index.next()) { + var symbol = p.value; + + if (symbol.alias == "_global_" || symbol.is("FILE")) continue; + JSDOC.SymbolSet.prototype.walk.apply(this, [symbol]); + } +} + +JSDOC.SymbolSet.prototype.walk = function(symbol) { + var augments = symbol.augments; + for(var i = 0; i < augments.length; i++) { + var contributer = this.getSymbol(augments[i]); + if (!contributer && JSDOC.Lang.isBuiltin(''+augments[i])) { + contributer = new JSDOC.Symbol("_global_."+augments[i], [], augments[i], new JSDOC.DocComment("Built in.")); + contributer.isNamespace = true; + contributer.srcFile = ""; + contributer.isPrivate = false; + JSDOC.Parser.addSymbol(contributer); + } + + if (contributer) { + if (contributer.augments.length) { + JSDOC.SymbolSet.prototype.walk.apply(this, [contributer]); + } + + symbol.inheritsFrom.push(contributer.alias); + //if (!isUnique(symbol.inheritsFrom)) { + // LOG.warn("Can't resolve augments: Circular reference: "+symbol.alias+" inherits from "+contributer.alias+" more than once."); + //} + //else { + var cmethods = contributer.methods; + var cproperties = contributer.properties; + + for (var ci = 0, cl = cmethods.length; ci < cl; ci++) { + if (!cmethods[ci].isStatic) symbol.inherit(cmethods[ci]); + } + for (var ci = 0, cl = cproperties.length; ci < cl; ci++) { + if (!cproperties[ci].isStatic) symbol.inherit(cproperties[ci]); + } + //} + } + else LOG.warn("Can't augment contributer: "+augments[i]+", not found."); + } +} diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/TextStream.js b/doc/jsdoc-toolkit/app/lib/JSDOC/TextStream.js new file mode 100644 index 000000000..ccc48a87d --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/TextStream.js @@ -0,0 +1,41 @@ + +/** + @constructor +*/ +JSDOC.TextStream = function(text) { + if (typeof(text) == "undefined") text = ""; + text = ""+text; + this.text = text; + this.cursor = 0; +} + +JSDOC.TextStream.prototype.look = function(n) { + if (typeof n == "undefined") n = 0; + + if (this.cursor+n < 0 || this.cursor+n >= this.text.length) { + var result = new String(""); + result.eof = true; + return result; + } + return this.text.charAt(this.cursor+n); +} + +JSDOC.TextStream.prototype.next = function(n) { + if (typeof n == "undefined") n = 1; + if (n < 1) return null; + + var pulled = ""; + for (var i = 0; i < n; i++) { + if (this.cursor+i < this.text.length) { + pulled += this.text.charAt(this.cursor+i); + } + else { + var result = new String(""); + result.eof = true; + return result; + } + } + + this.cursor += n; + return pulled; +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/Token.js b/doc/jsdoc-toolkit/app/lib/JSDOC/Token.js new file mode 100644 index 000000000..fb7f9d949 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/Token.js @@ -0,0 +1,18 @@ +if (typeof JSDOC == "undefined") JSDOC = {}; + +/** + @constructor +*/ +JSDOC.Token = function(data, type, name) { + this.data = data; + this.type = type; + this.name = name; +} + +JSDOC.Token.prototype.toString = function() { + return "<"+this.type+" name=\""+this.name+"\">"+this.data+""; +} + +JSDOC.Token.prototype.is = function(what) { + return this.name === what || this.type === what; +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/TokenReader.js b/doc/jsdoc-toolkit/app/lib/JSDOC/TokenReader.js new file mode 100644 index 000000000..9f658fb9f --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/TokenReader.js @@ -0,0 +1,332 @@ +if (typeof JSDOC == "undefined") JSDOC = {}; + +/** + @class Search a {@link JSDOC.TextStream} for language tokens. +*/ +JSDOC.TokenReader = function() { + this.keepDocs = true; + this.keepWhite = false; + this.keepComments = false; +} + +/** + @type {JSDOC.Token[]} + */ +JSDOC.TokenReader.prototype.tokenize = function(/**JSDOC.TextStream*/stream) { + var tokens = []; + /**@ignore*/ tokens.last = function() { return tokens[tokens.length-1]; } + /**@ignore*/ tokens.lastSym = function() { + for (var i = tokens.length-1; i >= 0; i--) { + if (!(tokens[i].is("WHIT") || tokens[i].is("COMM"))) return tokens[i]; + } + } + + while (!stream.look().eof) { + if (this.read_mlcomment(stream, tokens)) continue; + if (this.read_slcomment(stream, tokens)) continue; + if (this.read_dbquote(stream, tokens)) continue; + if (this.read_snquote(stream, tokens)) continue; + if (this.read_regx(stream, tokens)) continue; + if (this.read_numb(stream, tokens)) continue; + if (this.read_punc(stream, tokens)) continue; + if (this.read_newline(stream, tokens)) continue; + if (this.read_space(stream, tokens)) continue; + if (this.read_word(stream, tokens)) continue; + + // if execution reaches here then an error has happened + tokens.push(new JSDOC.Token(stream.next(), "TOKN", "UNKNOWN_TOKEN")); + } + return tokens; +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_word = function(/**JSDOC.TokenStream*/stream, tokens) { + var found = ""; + while (!stream.look().eof && JSDOC.Lang.isWordChar(stream.look())) { + found += stream.next(); + } + + if (found === "") { + return false; + } + else { + var name; + if ((name = JSDOC.Lang.keyword(found))) tokens.push(new JSDOC.Token(found, "KEYW", name)); + else tokens.push(new JSDOC.Token(found, "NAME", "NAME")); + return true; + } +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_punc = function(/**JSDOC.TokenStream*/stream, tokens) { + var found = ""; + var name; + while (!stream.look().eof && JSDOC.Lang.punc(found+stream.look())) { + found += stream.next(); + } + + if (found === "") { + return false; + } + else { + tokens.push(new JSDOC.Token(found, "PUNC", JSDOC.Lang.punc(found))); + return true; + } +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_space = function(/**JSDOC.TokenStream*/stream, tokens) { + var found = ""; + + while (!stream.look().eof && JSDOC.Lang.isSpace(stream.look())) { + found += stream.next(); + } + + if (found === "") { + return false; + } + else { + if (this.collapseWhite) found = " "; + if (this.keepWhite) tokens.push(new JSDOC.Token(found, "WHIT", "SPACE")); + return true; + } +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_newline = function(/**JSDOC.TokenStream*/stream, tokens) { + var found = ""; + + while (!stream.look().eof && JSDOC.Lang.isNewline(stream.look())) { + found += stream.next(); + } + + if (found === "") { + return false; + } + else { + if (this.collapseWhite) found = "\n"; + if (this.keepWhite) tokens.push(new JSDOC.Token(found, "WHIT", "NEWLINE")); + return true; + } +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_mlcomment = function(/**JSDOC.TokenStream*/stream, tokens) { + if (stream.look() == "/" && stream.look(1) == "*") { + var found = stream.next(2); + + while (!stream.look().eof && !(stream.look(-1) == "/" && stream.look(-2) == "*")) { + found += stream.next(); + } + + // to start doclet we allow /** or /*** but not /**/ or /**** + if (/^\/\*\*([^\/]|\*[^*])/.test(found) && this.keepDocs) tokens.push(new JSDOC.Token(found, "COMM", "JSDOC")); + else if (this.keepComments) tokens.push(new JSDOC.Token(found, "COMM", "MULTI_LINE_COMM")); + return true; + } + return false; +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_slcomment = function(/**JSDOC.TokenStream*/stream, tokens) { + var found; + if ( + (stream.look() == "/" && stream.look(1) == "/" && (found=stream.next(2))) + || + (stream.look() == "<" && stream.look(1) == "!" && stream.look(2) == "-" && stream.look(3) == "-" && (found=stream.next(4))) + ) { + + while (!stream.look().eof && !JSDOC.Lang.isNewline(stream.look())) { + found += stream.next(); + } + + if (this.keepComments) { + tokens.push(new JSDOC.Token(found, "COMM", "SINGLE_LINE_COMM")); + } + return true; + } + return false; +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_dbquote = function(/**JSDOC.TokenStream*/stream, tokens) { + if (stream.look() == "\"") { + // find terminator + var string = stream.next(); + + while (!stream.look().eof) { + if (stream.look() == "\\") { + if (JSDOC.Lang.isNewline(stream.look(1))) { + do { + stream.next(); + } while (!stream.look().eof && JSDOC.Lang.isNewline(stream.look())); + string += "\\\n"; + } + else { + string += stream.next(2); + } + } + else if (stream.look() == "\"") { + string += stream.next(); + tokens.push(new JSDOC.Token(string, "STRN", "DOUBLE_QUOTE")); + return true; + } + else { + string += stream.next(); + } + } + } + return false; // error! unterminated string +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_snquote = function(/**JSDOC.TokenStream*/stream, tokens) { + if (stream.look() == "'") { + // find terminator + var string = stream.next(); + + while (!stream.look().eof) { + if (stream.look() == "\\") { // escape sequence + string += stream.next(2); + } + else if (stream.look() == "'") { + string += stream.next(); + tokens.push(new JSDOC.Token(string, "STRN", "SINGLE_QUOTE")); + return true; + } + else { + string += stream.next(); + } + } + } + return false; // error! unterminated string +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_numb = function(/**JSDOC.TokenStream*/stream, tokens) { + if (stream.look() === "0" && stream.look(1) == "x") { + return this.read_hex(stream, tokens); + } + + var found = ""; + + while (!stream.look().eof && JSDOC.Lang.isNumber(found+stream.look())){ + found += stream.next(); + } + + if (found === "") { + return false; + } + else { + if (/^0[0-7]/.test(found)) tokens.push(new JSDOC.Token(found, "NUMB", "OCTAL")); + else tokens.push(new JSDOC.Token(found, "NUMB", "DECIMAL")); + return true; + } +} +/*t: + requires("../lib/JSDOC/TextStream.js"); + requires("../lib/JSDOC/Token.js"); + requires("../lib/JSDOC/Lang.js"); + + plan(3, "testing JSDOC.TokenReader.prototype.read_numb"); + + //// setup + var src = "function foo(num){while (num+8.0 >= 0x20 && num < 0777){}}"; + var tr = new JSDOC.TokenReader(); + var tokens = tr.tokenize(new JSDOC.TextStream(src)); + + var hexToken, octToken, decToken; + for (var i = 0; i < tokens.length; i++) { + if (tokens[i].name == "HEX_DEC") hexToken = tokens[i]; + if (tokens[i].name == "OCTAL") octToken = tokens[i]; + if (tokens[i].name == "DECIMAL") decToken = tokens[i]; + } + //// + + is(decToken.data, "8.0", "decimal number is found in source."); + is(hexToken.data, "0x20", "hexdec number is found in source (issue #99)."); + is(octToken.data, "0777", "octal number is found in source."); +*/ + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_hex = function(/**JSDOC.TokenStream*/stream, tokens) { + var found = stream.next(2); + + while (!stream.look().eof) { + if (JSDOC.Lang.isHexDec(found) && !JSDOC.Lang.isHexDec(found+stream.look())) { // done + tokens.push(new JSDOC.Token(found, "NUMB", "HEX_DEC")); + return true; + } + else { + found += stream.next(); + } + } + return false; +} + +/** + @returns {Boolean} Was the token found? + */ +JSDOC.TokenReader.prototype.read_regx = function(/**JSDOC.TokenStream*/stream, tokens) { + var last; + if ( + stream.look() == "/" + && + ( + + ( + !(last = tokens.lastSym()) // there is no last, the regex is the first symbol + || + ( + !last.is("NUMB") + && !last.is("NAME") + && !last.is("RIGHT_PAREN") + && !last.is("RIGHT_BRACKET") + ) + ) + ) + ) { + var regex = stream.next(); + + while (!stream.look().eof) { + if (stream.look() == "\\") { // escape sequence + regex += stream.next(2); + } + else if (stream.look() == "/") { + regex += stream.next(); + + while (/[gmi]/.test(stream.look())) { + regex += stream.next(); + } + + tokens.push(new JSDOC.Token(regex, "REGX", "REGX")); + return true; + } + else { + regex += stream.next(); + } + } + // error: unterminated regex + } + return false; +} diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/TokenStream.js b/doc/jsdoc-toolkit/app/lib/JSDOC/TokenStream.js new file mode 100644 index 000000000..1eeb44cbb --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/TokenStream.js @@ -0,0 +1,133 @@ +if (typeof JSDOC == "undefined") JSDOC = {}; + +/** + @constructor +*/ +JSDOC.TokenStream = function(tokens) { + this.tokens = (tokens || []); + this.rewind(); +} + +/** + @constructor + @private +*/ +function VoidToken(/**String*/type) { + this.toString = function() {return ""}; + this.is = function(){return false;} +} + +JSDOC.TokenStream.prototype.rewind = function() { + this.cursor = -1; +} + +/** + @type JSDOC.Token +*/ +JSDOC.TokenStream.prototype.look = function(/**Number*/n, /**Boolean*/considerWhitespace) { + if (typeof n == "undefined") n = 0; + + if (considerWhitespace == true) { + if (this.cursor+n < 0 || this.cursor+n > this.tokens.length) return {}; + return this.tokens[this.cursor+n]; + } + else { + var count = 0; + var i = this.cursor; + + while (true) { + if (i < 0) return new JSDOC.Token("", "VOID", "START_OF_STREAM"); + else if (i > this.tokens.length) return new JSDOC.Token("", "VOID", "END_OF_STREAM"); + + if (i != this.cursor && (this.tokens[i] === undefined || this.tokens[i].is("WHIT"))) { + if (n < 0) i--; else i++; + continue; + } + + if (count == Math.abs(n)) { + return this.tokens[i]; + } + count++; + (n < 0)? i-- : i++; + } + + return new JSDOC.Token("", "VOID", "STREAM_ERROR"); // because null isn't an object and caller always expects an object + } +} + +/** + @type JSDOC.Token|JSDOC.Token[] +*/ +JSDOC.TokenStream.prototype.next = function(/**Number*/howMany) { + if (typeof howMany == "undefined") howMany = 1; + if (howMany < 1) return null; + var got = []; + + for (var i = 1; i <= howMany; i++) { + if (this.cursor+i >= this.tokens.length) { + return null; + } + got.push(this.tokens[this.cursor+i]); + } + this.cursor += howMany; + + if (howMany == 1) { + return got[0]; + } + else return got; +} + +/** + @type JSDOC.Token[] +*/ +JSDOC.TokenStream.prototype.balance = function(/**String*/start, /**String*/stop) { + if (!stop) stop = JSDOC.Lang.matching(start); + + var depth = 0; + var got = []; + var started = false; + + while ((token = this.look())) { + if (token.is(start)) { + depth++; + started = true; + } + + if (started) { + got.push(token); + } + + if (token.is(stop)) { + depth--; + if (depth == 0) return got; + } + if (!this.next()) break; + } +} + +JSDOC.TokenStream.prototype.getMatchingToken = function(/**String*/start, /**String*/stop) { + var depth = 0; + var cursor = this.cursor; + + if (!start) { + start = JSDOC.Lang.matching(stop); + depth = 1; + } + if (!stop) stop = JSDOC.Lang.matching(start); + + while ((token = this.tokens[cursor])) { + if (token.is(start)) { + depth++; + } + + if (token.is(stop) && cursor) { + depth--; + if (depth == 0) return this.tokens[cursor]; + } + cursor++; + } +} + +JSDOC.TokenStream.prototype.insertAhead = function(/**JSDOC.Token*/token) { + this.tokens.splice(this.cursor+1, 0, token); +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/Util.js b/doc/jsdoc-toolkit/app/lib/JSDOC/Util.js new file mode 100644 index 000000000..6d7edb368 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/Util.js @@ -0,0 +1,32 @@ +/** + * @namespace + * @deprecated Use {@link FilePath} instead. + */ +JSDOC.Util = { +} + +/** + * @deprecated Use {@link FilePath.fileName} instead. + */ +JSDOC.Util.fileName = function(path) { + LOG.warn("JSDOC.Util.fileName is deprecated. Use FilePath.fileName instead."); + var nameStart = Math.max(path.lastIndexOf("/")+1, path.lastIndexOf("\\")+1, 0); + return path.substring(nameStart); +} + +/** + * @deprecated Use {@link FilePath.fileExtension} instead. + */ +JSDOC.Util.fileExtension = function(filename) { + LOG.warn("JSDOC.Util.fileExtension is deprecated. Use FilePath.fileExtension instead."); + return filename.split(".").pop().toLowerCase(); +}; + +/** + * @deprecated Use {@link FilePath.dir} instead. + */ +JSDOC.Util.dir = function(path) { + LOG.warn("JSDOC.Util.dir is deprecated. Use FilePath.dir instead."); + var nameStart = Math.max(path.lastIndexOf("/")+1, path.lastIndexOf("\\")+1, 0); + return path.substring(0, nameStart-1); +} diff --git a/doc/jsdoc-toolkit/app/lib/JSDOC/Walker.js b/doc/jsdoc-toolkit/app/lib/JSDOC/Walker.js new file mode 100644 index 000000000..befec4d35 --- /dev/null +++ b/doc/jsdoc-toolkit/app/lib/JSDOC/Walker.js @@ -0,0 +1,474 @@ +if (typeof JSDOC == "undefined") JSDOC = {}; + +/** @constructor */ +JSDOC.Walker = function(/**JSDOC.TokenStream*/ts) { + this.init(); + if (typeof ts != "undefined") { + this.walk(ts); + } +} + +JSDOC.Walker.prototype.init = function() { + this.ts = null; + + var globalSymbol = new JSDOC.Symbol("_global_", [], "GLOBAL", new JSDOC.DocComment("")); + globalSymbol.isNamespace = true; + globalSymbol.srcFile = ""; + globalSymbol.isPrivate = false; + JSDOC.Parser.addSymbol(globalSymbol); + this.lastDoc = null; + this.token = null; + + /** + The chain of symbols under which we are currently nested. + @type Array + */ + this.namescope = [globalSymbol]; + this.namescope.last = function(n){ if (!n) n = 0; return this[this.length-(1+n)] || "" }; +} + +JSDOC.Walker.prototype.walk = function(/**JSDOC.TokenStream*/ts) { + this.ts = ts; + while (this.token = this.ts.look()) { + if (this.token.popNamescope) { + + var symbol = this.namescope.pop(); + if (symbol.is("FUNCTION")) { + if (this.ts.look(1).is("LEFT_PAREN") && symbol.comment.getTag("function").length == 0) { + symbol.isa = "OBJECT"; + } + } + } + this.step(); + if (!this.ts.next()) break; + } +} + +JSDOC.Walker.prototype.step = function() { + if (this.token.is("JSDOC")) { // it's a doc comment + + var doc = new JSDOC.DocComment(this.token.data); + + + if (doc.getTag("exports").length > 0) { + var exports = doc.getTag("exports")[0]; + + exports.desc.match(/(\S+) as (\S+)/i); + var n1 = RegExp.$1; + var n2 = RegExp.$2; + + if (!n1 && n2) throw "@exports tag requires a value like: 'name as ns.name'"; + + JSDOC.Parser.rename = (JSDOC.Parser.rename || {}); + JSDOC.Parser.rename[n1] = n2 + } + + if (doc.getTag("lends").length > 0) { + var lends = doc.getTag("lends")[0]; + + var name = lends.desc + if (!name) throw "@lends tag requires a value."; + + var symbol = new JSDOC.Symbol(name, [], "OBJECT", doc); + + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken("LEFT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + + this.lastDoc = null; + return true; + } + else if (doc.getTag("name").length > 0 && doc.getTag("overview").length == 0) { // it's a virtual symbol + var virtualName = doc.getTag("name")[0].desc; + if (!virtualName) throw "@name tag requires a value."; + + var symbol = new JSDOC.Symbol(virtualName, [], "VIRTUAL", doc); + + JSDOC.Parser.addSymbol(symbol); + + this.lastDoc = null; + return true; + } + else if (doc.meta) { // it's a meta doclet + if (doc.meta == "@+") JSDOC.DocComment.shared = doc.src; + else if (doc.meta == "@-") JSDOC.DocComment.shared = ""; + else if (doc.meta == "nocode+") JSDOC.Parser.conf.ignoreCode = true; + else if (doc.meta == "nocode-") JSDOC.Parser.conf.ignoreCode = JSDOC.opt.n; + else throw "Unrecognized meta comment: "+doc.meta; + + this.lastDoc = null; + return true; + } + else if (doc.getTag("overview").length > 0) { // it's a file overview + symbol = new JSDOC.Symbol("", [], "FILE", doc); + + JSDOC.Parser.addSymbol(symbol); + + this.lastDoc = null; + return true; + } + else { + this.lastDoc = doc; + return false; + } + } + else if (!JSDOC.Parser.conf.ignoreCode) { // it's code + if (this.token.is("NAME")) { // it's the name of something + var symbol; + var name = this.token.data; + var doc = null; if (this.lastDoc) doc = this.lastDoc; + var params = []; + + // it's inside an anonymous object + if (this.ts.look(1).is("COLON") && this.ts.look(-1).is("LEFT_CURLY") && !(this.ts.look(-2).is("JSDOC") || this.namescope.last().comment.getTag("lends").length || this.ts.look(-2).is("ASSIGN") || this.ts.look(-2).is("COLON"))) { + name = "$anonymous"; + name = this.namescope.last().alias+"-"+name + + params = []; + + symbol = new JSDOC.Symbol(name, params, "OBJECT", doc); + + JSDOC.Parser.addSymbol(symbol); + + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken(null, "RIGHT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + } + // function foo() {} + else if (this.ts.look(-1).is("FUNCTION") && this.ts.look(1).is("LEFT_PAREN")) { + var isInner; + + if (this.lastDoc) doc = this.lastDoc; + name = this.namescope.last().alias+"-"+name; + if (!this.namescope.last().is("GLOBAL")) isInner = true; + + params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN")); + + symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc); + if (isInner) symbol.isInner = true; + + if (this.ts.look(1).is("JSDOC")) { + var inlineReturn = ""+this.ts.look(1).data; + inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, ""); + symbol.type = inlineReturn; + } + + JSDOC.Parser.addSymbol(symbol); + + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken("LEFT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + } + // foo = function() {} + else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("FUNCTION")) { + var isInner; + if (this.ts.look(-1).is("VAR") || this.isInner) { + name = this.namescope.last().alias+"-"+name + if (!this.namescope.last().is("GLOBAL")) isInner = true; + } + else if (name.indexOf("this.") == 0) { + name = this.resolveThis(name); + } + + if (this.lastDoc) doc = this.lastDoc; + params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN")); + + symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc); + if (isInner) symbol.isInner = true; + + if (this.ts.look(1).is("JSDOC")) { + var inlineReturn = ""+this.ts.look(1).data; + inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, ""); + symbol.type = inlineReturn; + } + + JSDOC.Parser.addSymbol(symbol); + + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken("LEFT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + } + // foo = new function() {} or foo = (function() {} + else if (this.ts.look(1).is("ASSIGN") && (this.ts.look(2).is("NEW") || this.ts.look(2).is("LEFT_PAREN")) && this.ts.look(3).is("FUNCTION")) { + var isInner; + if (this.ts.look(-1).is("VAR") || this.isInner) { + name = this.namescope.last().alias+"-"+name + if (!this.namescope.last().is("GLOBAL")) isInner = true; + } + else if (name.indexOf("this.") == 0) { + name = this.resolveThis(name); + } + + this.ts.next(3); // advance past the "new" or "(" + + if (this.lastDoc) doc = this.lastDoc; + params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN")); + + symbol = new JSDOC.Symbol(name, params, "OBJECT", doc); + if (isInner) symbol.isInner = true; + + if (this.ts.look(1).is("JSDOC")) { + var inlineReturn = ""+this.ts.look(1).data; + inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, ""); + symbol.type = inlineReturn; + } + + JSDOC.Parser.addSymbol(symbol); + + symbol.scopeType = "INSTANCE"; + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken("LEFT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + } + // foo: function() {} + else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("FUNCTION")) { + name = (this.namescope.last().alias+"."+name).replace("#.", "#"); + + if (this.lastDoc) doc = this.lastDoc; + params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN")); + + if (doc && doc.getTag("constructs").length) { + name = name.replace(/\.prototype(\.|$)/, "#"); + + if (name.indexOf("#") > -1) name = name.match(/(^[^#]+)/)[0]; + else name = this.namescope.last().alias; + + symbol = new JSDOC.Symbol(name, params, "CONSTRUCTOR", doc); + } + else { + symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc); + } + + if (this.ts.look(1).is("JSDOC")) { + var inlineReturn = ""+this.ts.look(1).data; + inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, ""); + symbol.type = inlineReturn; + } + + JSDOC.Parser.addSymbol(symbol); + + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken("LEFT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + } + // foo = {} + else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("LEFT_CURLY")) { + var isInner; + if (this.ts.look(-1).is("VAR") || this.isInner) { + name = this.namescope.last().alias+"-"+name + if (!this.namescope.last().is("GLOBAL")) isInner = true; + } + else if (name.indexOf("this.") == 0) { + name = this.resolveThis(name); + } + + if (this.lastDoc) doc = this.lastDoc; + + symbol = new JSDOC.Symbol(name, params, "OBJECT", doc); + if (isInner) symbol.isInner = true; + + + if (doc) JSDOC.Parser.addSymbol(symbol); + + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken("LEFT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + } + // var foo; + else if (this.ts.look(1).is("SEMICOLON")) { + var isInner; + + if (this.ts.look(-1).is("VAR") || this.isInner) { + name = this.namescope.last().alias+"-"+name + if (!this.namescope.last().is("GLOBAL")) isInner = true; + + if (this.lastDoc) doc = this.lastDoc; + + symbol = new JSDOC.Symbol(name, params, "OBJECT", doc); + if (isInner) symbol.isInner = true; + + + if (doc) JSDOC.Parser.addSymbol(symbol); + } + } + // foo = x + else if (this.ts.look(1).is("ASSIGN")) { + var isInner; + if (this.ts.look(-1).is("VAR") || this.isInner) { + name = this.namescope.last().alias+"-"+name + if (!this.namescope.last().is("GLOBAL")) isInner = true; + } + else if (name.indexOf("this.") == 0) { + name = this.resolveThis(name); + } + + if (this.lastDoc) doc = this.lastDoc; + + symbol = new JSDOC.Symbol(name, params, "OBJECT", doc); + if (isInner) symbol.isInner = true; + + + if (doc) JSDOC.Parser.addSymbol(symbol); + } + // foo: {} + else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("LEFT_CURLY")) { + name = (this.namescope.last().alias+"."+name).replace("#.", "#"); + + if (this.lastDoc) doc = this.lastDoc; + + symbol = new JSDOC.Symbol(name, params, "OBJECT", doc); + + + if (doc) JSDOC.Parser.addSymbol(symbol); + + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken("LEFT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + } + // foo: x + else if (this.ts.look(1).is("COLON")) { + name = (this.namescope.last().alias+"."+name).replace("#.", "#");; + + if (this.lastDoc) doc = this.lastDoc; + + symbol = new JSDOC.Symbol(name, params, "OBJECT", doc); + + + if (doc) JSDOC.Parser.addSymbol(symbol); + } + // foo(...) + else if (this.ts.look(1).is("LEFT_PAREN")) { + if (typeof JSDOC.PluginManager != "undefined") { + var functionCall = {name: name}; + + var cursor = this.ts.cursor; + params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN")); + this.ts.cursor = cursor; + + for (var i = 0; i < params.length; i++) + functionCall["arg" + (i + 1)] = params[i].name; + + JSDOC.PluginManager.run("onFunctionCall", functionCall); + if (functionCall.doc) { + this.ts.insertAhead(new JSDOC.Token(functionCall.doc, "COMM", "JSDOC")); + } + } + } + this.lastDoc = null; + } + else if (this.token.is("FUNCTION")) { // it's an anonymous function + if ( + (!this.ts.look(-1).is("COLON") || !this.ts.look(-1).is("ASSIGN")) + && !this.ts.look(1).is("NAME") + ) { + if (this.lastDoc) doc = this.lastDoc; + + name = "$anonymous"; + name = this.namescope.last().alias+"-"+name + + params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN")); + + symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc); + + JSDOC.Parser.addSymbol(symbol); + + this.namescope.push(symbol); + + var matching = this.ts.getMatchingToken("LEFT_CURLY"); + if (matching) matching.popNamescope = name; + else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + "."); + } + } + } + return true; +} + +/** + Resolves what "this." means when it appears in a name. + @param name The name that starts with "this.". + @returns The name with "this." resolved. + */ +JSDOC.Walker.prototype.resolveThis = function(name) { + name.match(/^this\.(.+)$/) + var nameFragment = RegExp.$1; + if (!nameFragment) return name; + + var symbol = this.namescope.last(); + var scopeType = symbol.scopeType || symbol.isa; + + // if we are in a constructor function, `this` means the instance + if (scopeType == "CONSTRUCTOR") { + name = symbol.alias+"#"+nameFragment; + } + + // if we are in an anonymous constructor function, `this` means the instance + else if (scopeType == "INSTANCE") { + name = symbol.alias+"."+nameFragment; + } + + // if we are in a function, `this` means the container (possibly the global) + else if (scopeType == "FUNCTION") { + // in a method of a prototype, so `this` means the constructor + if (symbol.alias.match(/(^.*)[#.-][^#.-]+/)) { + var parentName = RegExp.$1; + var parent = JSDOC.Parser.symbols.getSymbol(parentName); + + if (!parent) { + if (JSDOC.Lang.isBuiltin(parentName)) parent = JSDOC.Parser.addBuiltin(parentName); + else { + if (symbol.alias.indexOf("$anonymous") < 0) // these will be ignored eventually + LOG.warn("Trying to document "+symbol.alias+" without first documenting "+parentName+"."); + } + } + if (parent) name = parentName+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment; + } + else { + parent = this.namescope.last(1); + name = parent.alias+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment; + } + } + // otherwise it means the global + else { + name = nameFragment; + } + + return name; +} + +JSDOC.Walker.onParamList = function(/**Array*/paramTokens) { + if (!paramTokens) { + LOG.warn("Malformed parameter list. Can't parse code."); + return []; + } + var params = []; + for (var i = 0, l = paramTokens.length; i < l; i++) { + if (paramTokens[i].is("JSDOC")) { + var paramType = paramTokens[i].data.replace(/(^\/\*\* *| *\*\/$)/g, ""); + + if (paramTokens[i+1] && paramTokens[i+1].is("NAME")) { + i++; + params.push({type: paramType, name: paramTokens[i].data}); + } + } + else if (paramTokens[i].is("NAME")) { + params.push({name: paramTokens[i].data}); + } + } + return params; +} diff --git a/doc/jsdoc-toolkit/app/main.js b/doc/jsdoc-toolkit/app/main.js new file mode 100644 index 000000000..9b78182c1 --- /dev/null +++ b/doc/jsdoc-toolkit/app/main.js @@ -0,0 +1,111 @@ +/** + * @version $Id: main.js 795 2009-06-19 07:03:22Z micmath $ + */ + +function main() { + IO.include("lib/JSDOC.js"); + IO.includeDir("plugins/"); + + // process the options + + // the -c option: options are defined in a configuration file + if (JSDOC.opt.c) { + eval("JSDOC.conf = " + IO.readFile(JSDOC.opt.c)); + + LOG.inform("Using configuration file at '"+JSDOC.opt.c+"'."); + + for (var c in JSDOC.conf) { + if (c !== "D" && !defined(JSDOC.opt[c])) { // commandline overrules config file + JSDOC.opt[c] = JSDOC.conf[c]; + } + } + + if (typeof JSDOC.conf["_"] != "undefined") { + JSDOC.opt["_"] = JSDOC.opt["_"].concat(JSDOC.conf["_"]); + } + + LOG.inform("With configuration: "); + for (var o in JSDOC.opt) { + LOG.inform(" "+o+": "+JSDOC.opt[o]); + } + } + + // be verbose + if (JSDOC.opt.v) LOG.verbose = true; + + // send log messages to a file + if (JSDOC.opt.o) LOG.out = IO.open(JSDOC.opt.o); + + // run the unit tests + if (JSDOC.opt.T) { + LOG.inform("JsDoc Toolkit running in test mode at "+new Date()+"."); + IO.include("frame/Testrun.js"); + IO.include("test.js"); + } + else { + // a template must be defined and must be a directory path + if (!JSDOC.opt.t && System.getProperty("jsdoc.template.dir")) { + JSDOC.opt.t = System.getProperty("jsdoc.template.dir"); + } + if (JSDOC.opt.t && !JSDOC.opt.t.charAt(JSDOC.opt.t.length-1).match(/[\\\/]/)) { + JSDOC.opt.t += SYS.slash; + } + + // verbose messages about the options we were given + LOG.inform("JsDoc Toolkit main() running at "+new Date()+"."); + LOG.inform("With options: "); + for (var o in JSDOC.opt) { + LOG.inform(" "+o+": "+JSDOC.opt[o]); + } + + // initialize and build a symbolSet from your code + JSDOC.JsDoc(); + + // debugger's option: dump the entire symbolSet produced from your code + if (JSDOC.opt.Z) { + LOG.warn("So you want to see the data structure, eh? This might hang if you have circular refs..."); + IO.include("frame/Dumper.js"); + var symbols = JSDOC.JsDoc.symbolSet.toArray(); + for (var i = 0, l = symbols.length; i < l; i++) { + var symbol = symbols[i]; + print("// symbol: " + symbol.alias); + print(symbol.serialize()); + } + } + else { + if (typeof JSDOC.opt.t != "undefined") { + try { + // a file named "publish.js" must exist in the template directory + load(JSDOC.opt.t+"publish.js"); + + // and must define a function named "publish" + if (!publish) { + LOG.warn("No publish() function is defined in that template so nothing to do."); + } + else { + // which will be called with the symbolSet produced from your code + publish(JSDOC.JsDoc.symbolSet); + } + } + catch(e) { + LOG.warn("Sorry, that doesn't seem to be a valid template: "+JSDOC.opt.t+"publish.js : "+e); + } + } + else { + LOG.warn("No template given. Might as well read the usage notes."); + JSDOC.usage(); + } + } + } + + // notify of any warnings + if (!JSDOC.opt.q && LOG.warnings.length) { + print(LOG.warnings.length+" warning"+(LOG.warnings.length != 1? "s":"")+"."); + } + + // stop sending log messages to a file + if (LOG.out) { + LOG.out.flush(); + LOG.out.close(); + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/plugins/commentSrcJson.js b/doc/jsdoc-toolkit/app/plugins/commentSrcJson.js new file mode 100644 index 000000000..e826b5722 --- /dev/null +++ b/doc/jsdoc-toolkit/app/plugins/commentSrcJson.js @@ -0,0 +1,20 @@ +JSDOC.PluginManager.registerPlugin( + "JSDOC.commentSrcJson", + { + onDocCommentSrc: function(comment) { + var json; + if (/^\s*@json\b/.test(comment)) { + comment.src = new String(comment.src).replace("@json", ""); + + eval("json = "+comment.src); + var tagged = ""; + for (var i in json) { + var tag = json[i]; + // todo handle cases where tag is an object + tagged += "@"+i+" "+tag+"\n"; + } + comment.src = tagged; + } + } + } +); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/plugins/frameworkPrototype.js b/doc/jsdoc-toolkit/app/plugins/frameworkPrototype.js new file mode 100644 index 000000000..9c417518b --- /dev/null +++ b/doc/jsdoc-toolkit/app/plugins/frameworkPrototype.js @@ -0,0 +1,16 @@ +JSDOC.PluginManager.registerPlugin( + "JSDOC.frameworkPrototype", + { + onPrototypeClassCreate: function(classCreator) { + var desc = ""; + if (classCreator.comment) { + desc = classCreator.comment; + } + var insert = desc+"/** @name "+classCreator.name+"\n@constructor\n@scope "+classCreator.name+".prototype */" + + insert = insert.replace(/\*\/\/\*\*/g, "\n"); + /*DEBUG*///print("insert is "+insert); + classCreator.addComment.data = insert; + } + } +); diff --git a/doc/jsdoc-toolkit/app/plugins/functionCall.js b/doc/jsdoc-toolkit/app/plugins/functionCall.js new file mode 100644 index 000000000..6f87705ee --- /dev/null +++ b/doc/jsdoc-toolkit/app/plugins/functionCall.js @@ -0,0 +1,10 @@ +JSDOC.PluginManager.registerPlugin( + "JSDOC.functionCall", + { + onFunctionCall: function(functionCall) { + if (functionCall.name == "dojo.define" && functionCall.arg1) { + functionCall.doc = "/** @lends "+eval(functionCall.arg1)+".prototype */"; + } + } + } +); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/plugins/publishSrcHilite.js b/doc/jsdoc-toolkit/app/plugins/publishSrcHilite.js new file mode 100644 index 000000000..65514f2c9 --- /dev/null +++ b/doc/jsdoc-toolkit/app/plugins/publishSrcHilite.js @@ -0,0 +1,62 @@ +JSDOC.PluginManager.registerPlugin( + "JSDOC.publishSrcHilite", + { + onPublishSrc: function(src) { + if (src.path in JsHilite.cache) { + return; // already generated src code + } + else JsHilite.cache[src.path] = true; + + try { + var sourceCode = IO.readFile(src.path); + } + catch(e) { + print(e.message); + quit(); + } + + var hiliter = new JsHilite(sourceCode, src.charset); + src.hilited = hiliter.hilite(); + } + } +); + +function JsHilite(src, charset) { + + var tr = new JSDOC.TokenReader(); + + tr.keepComments = true; + tr.keepDocs = true; + tr.keepWhite = true; + + this.tokens = tr.tokenize(new JSDOC.TextStream(src)); + + // TODO is redefining toString() the best way? + JSDOC.Token.prototype.toString = function() { + return ""+this.data.replace(/"; + } + + if (!charset) charset = "utf-8"; + + this.header = ' '+ + "

";
+	this.footer = "
"; + this.showLinenumbers = true; +} + +JsHilite.cache = {}; + +JsHilite.prototype.hilite = function() { + var hilited = this.tokens.join(""); + var line = 1; + if (this.showLinenumbers) hilited = hilited.replace(/(^|\n)/g, function(m){return m+""+((line<10)? " ":"")+((line<100)? " ":"")+(line++)+" "}); + + return this.header+hilited+this.footer; +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/plugins/symbolLink.js b/doc/jsdoc-toolkit/app/plugins/symbolLink.js new file mode 100644 index 000000000..c87f1ca7b --- /dev/null +++ b/doc/jsdoc-toolkit/app/plugins/symbolLink.js @@ -0,0 +1,10 @@ +JSDOC.PluginManager.registerPlugin( + "JSDOC.symbolLink", + { + onSymbolLink: function(link) { + // modify link.linkPath (the href part of the link) + // or link.linkText (the text displayed) + // or link.linkInner (the #name part of the link) + } + } +); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/plugins/tagParamConfig.js b/doc/jsdoc-toolkit/app/plugins/tagParamConfig.js new file mode 100644 index 000000000..3ea8a1be2 --- /dev/null +++ b/doc/jsdoc-toolkit/app/plugins/tagParamConfig.js @@ -0,0 +1,31 @@ +JSDOC.PluginManager.registerPlugin( + "JSDOC.tagParamConfig", + { + onDocCommentTags: function(comment) { + var currentParam = null; + var tags = comment.tags; + for (var i = 0, l = tags.length; i < l; i++) { + + if (tags[i].title == "param") { + if (tags[i].name.indexOf(".") == -1) { + currentParam = i; + } + } + else if (tags[i].title == "config") { + tags[i].title = "param"; + if (currentParam == null) { + tags[i].name = "arguments"+"."+tags[i].name; + } + else if (tags[i].name.indexOf(tags[currentParam].name+".") != 0) { + tags[i].name = tags[currentParam].name+"."+tags[i].name; + } + currentParam != null + //tags[currentParam].properties.push(tags[i]); + } + else { + currentParam = null; + } + } + } + } +); diff --git a/doc/jsdoc-toolkit/app/plugins/tagSynonyms.js b/doc/jsdoc-toolkit/app/plugins/tagSynonyms.js new file mode 100644 index 000000000..49a874f19 --- /dev/null +++ b/doc/jsdoc-toolkit/app/plugins/tagSynonyms.js @@ -0,0 +1,43 @@ +JSDOC.PluginManager.registerPlugin( + "JSDOC.tagSynonyms", + { + onDocCommentSrc: function(comment) { + comment.src = comment.src.replace(/@methodOf\b/i, "@function\n@memberOf"); + comment.src = comment.src.replace(/@fieldOf\b/i, "@field\n@memberOf"); + }, + + onDocCommentTags: function(comment) { + for (var i = 0, l = comment.tags.length; i < l; i++) { + var title = comment.tags[i].title.toLowerCase(); + var syn; + if ((syn = JSDOC.tagSynonyms.synonyms["="+title])) { + comment.tags[i].title = syn; + } + } + } + } +); + +new Namespace( + "JSDOC.tagSynonyms", + function() { + JSDOC.tagSynonyms.synonyms = { + "=member": "memberOf", + "=memberof": "memberOf", + "=description": "desc", + "=exception": "throws", + "=argument": "param", + "=returns": "return", + "=classdescription": "class", + "=fileoverview": "overview", + "=extends": "augments", + "=base": "augments", + "=projectdescription": "overview", + "=classdescription": "class", + "=link": "see", + "=borrows": "inherits", + "=scope": "lends", + "=construct": "constructor" + } + } +); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/run.js b/doc/jsdoc-toolkit/app/run.js new file mode 100644 index 000000000..1f875cdbb --- /dev/null +++ b/doc/jsdoc-toolkit/app/run.js @@ -0,0 +1,348 @@ +/** + * @fileOverview + * A bootstrap script that creates some basic required objects + * for loading other scripts. + * @author Michael Mathews, micmath@gmail.com + * @version $Id: run.js 756 2009-01-07 21:32:58Z micmath $ + */ + +/** + * @namespace Keep track of any messages from the running script. + */ +LOG = { + warn: function(msg, e) { + if (JSDOC.opt.q) return; + if (e) msg = e.fileName+", line "+e.lineNumber+": "+msg; + + msg = ">> WARNING: "+msg; + LOG.warnings.push(msg); + if (LOG.out) LOG.out.write(msg+"\n"); + else print(msg); + }, + + inform: function(msg) { + if (JSDOC.opt.q) return; + msg = " > "+msg; + if (LOG.out) LOG.out.write(msg+"\n"); + else if (typeof LOG.verbose != "undefined" && LOG.verbose) print(msg); + } +}; +LOG.warnings = []; +LOG.verbose = false +LOG.out = undefined; + +/** + * @class Manipulate a filepath. + */ +function FilePath(absPath, separator) { + this.slash = separator || "/"; + this.root = this.slash; + this.path = []; + this.file = ""; + + var parts = absPath.split(/[\\\/]/); + if (parts) { + if (parts.length) this.root = parts.shift() + this.slash; + if (parts.length) this.file = parts.pop() + if (parts.length) this.path = parts; + } + + this.path = this.resolvePath(); +} + +/** Collapse any dot-dot or dot items in a filepath. */ +FilePath.prototype.resolvePath = function() { + var resolvedPath = []; + for (var i = 0; i < this.path.length; i++) { + if (this.path[i] == "..") resolvedPath.pop(); + else if (this.path[i] != ".") resolvedPath.push(this.path[i]); + } + return resolvedPath; +} + +/** Trim off the filename. */ +FilePath.prototype.toDir = function() { + if (this.file) this.file = ""; + return this; +} + +/** Go up a directory. */ +FilePath.prototype.upDir = function() { + this.toDir(); + if (this.path.length) this.path.pop(); + return this; +} + +FilePath.prototype.toString = function() { + return this.root + + this.path.join(this.slash) + + ((this.path.length > 0)? this.slash : "") + + this.file; +} + +/** + * Turn a path into just the name of the file. + */ +FilePath.fileName = function(path) { + var nameStart = Math.max(path.lastIndexOf("/")+1, path.lastIndexOf("\\")+1, 0); + return path.substring(nameStart); +} + +/** + * Get the extension of a filename + */ +FilePath.fileExtension = function(filename) { + return filename.split(".").pop().toLowerCase(); +}; + +/** + * Turn a path into just the directory part. + */ +FilePath.dir = function(path) { + var nameStart = Math.max(path.lastIndexOf("/")+1, path.lastIndexOf("\\")+1, 0); + return path.substring(0, nameStart-1); +} + + +importClass(java.lang.System); + +/** + * @namespace A collection of information about your system. + */ +SYS = { + /** + * Information about your operating system: arch, name, version. + * @type string + */ + os: [ + new String(System.getProperty("os.arch")), + new String(System.getProperty("os.name")), + new String(System.getProperty("os.version")) + ].join(", "), + + /** + * Which way does your slash lean. + * @type string + */ + slash: System.getProperty("file.separator")||"/", + + /** + * The path to the working directory where you ran java. + * @type string + */ + userDir: new String(System.getProperty("user.dir")), + + /** + * Where is Java's home folder. + * @type string + */ + javaHome: new String(System.getProperty("java.home")), + + /** + * The absolute path to the directory containing this script. + * @type string + */ + pwd: undefined +}; + +// jsrun appends an argument, with the path to here. +if (arguments[arguments.length-1].match(/^-j=(.+)/)) { + if (RegExp.$1.charAt(0) == SYS.slash || RegExp.$1.charAt(1) == ":") { // absolute path to here + SYS.pwd = new FilePath(RegExp.$1).toDir().toString(); + } + else { // relative path to here + SYS.pwd = new FilePath(SYS.userDir + SYS.slash + RegExp.$1).toDir().toString(); + } + arguments.pop(); +} +else { + print("The run.js script requires you use jsrun.jar."); + quit(); +} + +// shortcut +var File = Packages.java.io.File; + +/** + * @namespace A collection of functions that deal with reading a writing to disk. + */ +IO = { + + /** + * Create a new file in the given directory, with the given name and contents. + */ + saveFile: function(/**string*/ outDir, /**string*/ fileName, /**string*/ content) { + var out = new Packages.java.io.PrintWriter( + new Packages.java.io.OutputStreamWriter( + new Packages.java.io.FileOutputStream(outDir+SYS.slash+fileName), + IO.encoding + ) + ); + out.write(content); + out.flush(); + out.close(); + }, + + /** + * @type string + */ + readFile: function(/**string*/ path) { + if (!IO.exists(path)) { + throw "File doesn't exist there: "+path; + } + return readFile(path, IO.encoding); + }, + + /** + * @param inFile + * @param outDir + * @param [fileName=The original filename] + */ + copyFile: function(/**string*/ inFile, /**string*/ outDir, /**string*/ fileName) { + if (fileName == null) fileName = FilePath.fileName(inFile); + + var inFile = new File(inFile); + var outFile = new File(outDir+SYS.slash+fileName); + + var bis = new Packages.java.io.BufferedInputStream(new Packages.java.io.FileInputStream(inFile), 4096); + var bos = new Packages.java.io.BufferedOutputStream(new Packages.java.io.FileOutputStream(outFile), 4096); + var theChar; + while ((theChar = bis.read()) != -1) { + bos.write(theChar); + } + bos.close(); + bis.close(); + }, + + /** + * Creates a series of nested directories. + */ + mkPath: function(/**Array*/ path) { + if (path.constructor != Array) path = path.split(/[\\\/]/); + var make = ""; + for (var i = 0, l = path.length; i < l; i++) { + make += path[i] + SYS.slash; + if (! IO.exists(make)) { + IO.makeDir(make); + } + } + }, + + /** + * Creates a directory at the given path. + */ + makeDir: function(/**string*/ path) { + (new File(path)).mkdir(); + }, + + /** + * @type string[] + * @param dir The starting directory to look in. + * @param [recurse=1] How many levels deep to scan. + * @returns An array of all the paths to files in the given dir. + */ + ls: function(/**string*/ dir, /**number*/ recurse, _allFiles, _path) { + if (_path === undefined) { // initially + var _allFiles = []; + var _path = [dir]; + } + if (_path.length == 0) return _allFiles; + if (recurse === undefined) recurse = 1; + + dir = new File(dir); + if (!dir.directory) return [String(dir)]; + var files = dir.list(); + + for (var f = 0; f < files.length; f++) { + var file = String(files[f]); + if (file.match(/^\.[^\.\/\\]/)) continue; // skip dot files + + if ((new File(_path.join(SYS.slash)+SYS.slash+file)).list()) { // it's a directory + _path.push(file); + if (_path.length-1 < recurse) IO.ls(_path.join(SYS.slash), recurse, _allFiles, _path); + _path.pop(); + } + else { + _allFiles.push((_path.join(SYS.slash)+SYS.slash+file).replace(SYS.slash+SYS.slash, SYS.slash)); + } + } + + return _allFiles; + }, + + /** + * @type boolean + */ + exists: function(/**string*/ path) { + file = new File(path); + + if (file.isDirectory()){ + return true; + } + if (!file.exists()){ + return false; + } + if (!file.canRead()){ + return false; + } + return true; + }, + + /** + * + */ + open: function(/**string*/ path, /**string*/ append) { + var append = true; + var outFile = new File(path); + var out = new Packages.java.io.PrintWriter( + new Packages.java.io.OutputStreamWriter( + new Packages.java.io.FileOutputStream(outFile, append), + IO.encoding + ) + ); + return out; + }, + + /** + * Sets {@link IO.encoding}. + * Encoding is used when reading and writing text to files, + * and in the meta tags of HTML output. + */ + setEncoding: function(/**string*/ encoding) { + if (/ISO-8859-([0-9]+)/i.test(encoding)) { + IO.encoding = "ISO8859_"+RegExp.$1; + } + else { + IO.encoding = encoding; + } + }, + + /** + * @default "utf-8" + * @private + */ + encoding: "utf-8", + + /** + * Load the given script. + */ + include: function(relativePath) { + load(SYS.pwd+relativePath); + }, + + /** + * Loads all scripts from the given directory path. + */ + includeDir: function(path) { + if (!path) return; + + for (var lib = IO.ls(SYS.pwd+path), i = 0; i < lib.length; i++) + if (/\.js$/i.test(lib[i])) load(lib[i]); + } +} + +// now run the application +IO.include("frame.js"); +IO.include("main.js"); + +main(); diff --git a/doc/jsdoc-toolkit/app/t/TestDoc.js b/doc/jsdoc-toolkit/app/t/TestDoc.js new file mode 100644 index 000000000..c0768b71c --- /dev/null +++ b/doc/jsdoc-toolkit/app/t/TestDoc.js @@ -0,0 +1,144 @@ +var TestDoc = { + fails: 0, + plans: 0, + passes: 0, + results: [] +}; + +TestDoc.record = function(result) { + TestDoc.results.push(result); + if (typeof result.verdict == "boolean") { + if (result.verdict === false) TestDoc.fails++; + if (result.verdict === true) TestDoc.passes++; + } +} + +TestDoc.prove = function(filePath) { + if (typeof document != "undefined" && typeof document.write != "undefined") { + if (TestDoc.console) print = function(s) { TestDoc.console.appendChild(document.createTextNode(s+"\n")); } + else print = function(s) { document.write(s+"
"); } + } + TestDoc.run(TestDoc.readFile(filePath)); +} + +TestDoc.run = function(src) { + try { eval(src); } catch(e) { print("# ERROR! "+e); } + + var chunks = src.split(/\/\*t:/); + + var run = function(chunk) { + // local shortcuts + var is = TestDoc.assertEquals; + var isnt = TestDoc.assertNotEquals; + var plan = TestDoc.plan; + var requires = TestDoc.requires; + + try { eval(chunk); } catch(e) { print("# ERROR! "+e); } + } + for (var start = -1, end = 0; (start = src.indexOf("/*t:", end)) > end; start = end) { + run( + src.substring( + start+4, + (end = src.indexOf("*/", start)) + ) + ); + } +} + +TestDoc.Result = function(verdict, message) { + this.verdict = verdict; + this.message = message; +} + +TestDoc.Result.prototype.toString = function() { + if (typeof this.verdict == "boolean") { + return (this.verdict? "ok" : "not ok") + " " + (++TestDoc.report.counter) + " - " + this.message; + } + + return "# " + this.message; +} + +TestDoc.requires = function(file) { + if (!TestDoc.requires.loaded[file]) { + load(file); + TestDoc.requires.loaded[file] = true; + } +} +TestDoc.requires.loaded = {}; + +TestDoc.report = function() { + TestDoc.report.counter = 0; + print("1.."+TestDoc.plans); + for (var i = 0; i < TestDoc.results.length; i++) { + print(TestDoc.results[i]); + } + print("----------------------------------------"); + if (TestDoc.fails == 0 && TestDoc.passes == TestDoc.plans) { + print("All tests successful."); + } + else { + print("Failed " + TestDoc.fails + "/" + TestDoc.plans + " tests, "+((TestDoc.plans == 0)? 0 : Math.round(TestDoc.passes/(TestDoc.passes+TestDoc.fails)*10000)/100)+"% okay. Planned to run "+TestDoc.plans+", did run "+(TestDoc.passes+TestDoc.fails)+".") + } +} + +TestDoc.plan = function(n, message) { + TestDoc.plans += n; + TestDoc.record(new TestDoc.Result(null, message+" ("+n+" tests)")); +} + +TestDoc.assertEquals = function(a, b, message) { + var result = (a == b); + if (!result) message += "\n#\n# " + a + " does not equal " + b + "\n#"; + TestDoc.record(new TestDoc.Result(result, message)); +} + +TestDoc.assertNotEquals = function(a, b, message) { + var result = (a != b); + if (!result) message += "\n#\n# " + a + " equals " + b + "\n#"; + TestDoc.record(new TestDoc.Result(result, message)); +} + +TestDoc.readFile = (function(){ + // rhino + if (typeof readFile == "function") { + return function(url) { + var text = readFile(url); + return text || ""; + } + } + + // a web browser + else { + return function(url) { + var httpRequest; + + if (window.XMLHttpRequest) { // Mozilla, Safari, etc + httpRequest = new XMLHttpRequest(); + } + else if (window.ActiveXObject) { // IE + try { + httpRequest = new ActiveXObject("Msxml2.XMLHTTP"); + } + catch (e) { + try { + httpRequest = new ActiveXObject("Microsoft.XMLHTTP"); + } + catch (e) { + } + } + } + + if (!httpRequest) { throw "Cannot create HTTP Request."; } + + httpRequest.open('GET', url, false); + httpRequest.send(''); + if (httpRequest.readyState == 4) { + if (httpRequest.status >= 400) { + throw "The HTTP Request returned an error code: "+httpRequest.status; + } + } + + return httpRequest.responseText || ""; + } + } +})(); diff --git a/doc/jsdoc-toolkit/app/t/runner.js b/doc/jsdoc-toolkit/app/t/runner.js new file mode 100644 index 000000000..3f9fb4c96 --- /dev/null +++ b/doc/jsdoc-toolkit/app/t/runner.js @@ -0,0 +1,13 @@ +// try: java -jar ../../jsrun.jar runner.js + +load("TestDoc.js"); + +TestDoc.prove("../frame/Opt.js"); +TestDoc.prove("../lib/JSDOC.js"); +TestDoc.prove("../frame/String.js"); +TestDoc.prove("../lib/JSDOC/DocTag.js"); +TestDoc.prove("../lib/JSDOC/DocComment.js"); +TestDoc.prove("../lib/JSDOC/TokenReader.js"); +TestDoc.prove("../lib/JSDOC/Symbol.js"); + +TestDoc.report(); diff --git a/doc/jsdoc-toolkit/app/test.js b/doc/jsdoc-toolkit/app/test.js new file mode 100644 index 000000000..787d84f72 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test.js @@ -0,0 +1,325 @@ +load("app/frame/Dumper.js"); +function symbolize(opt) { + symbols = null; + JSDOC.JsDoc(opt); + symbols = JSDOC.JsDoc.symbolSet; +} + +var testCases = [ + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/overview.js"]}); + //print(Dumper.dump(symbols)); + is('symbols.getSymbolByName("My Cool Library").name', 'My Cool Library', 'File overview can be found by alias.'); + } + , + function() { + symbolize({_: [SYS.pwd+"test/name.js"]}); + + is('symbols.getSymbol("Response").name', "Response", 'Virtual class name is found.'); + is('symbols.getSymbol("Response#text").alias', "Response#text", 'Virtual method name is found.'); + is('symbols.getSymbol("Response#text").memberOf', "Response", 'Virtual method parent name is found.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/prototype.js"]}); + + is('symbols.getSymbol("Article").name', "Article", 'Function set to constructor prototype with inner constructor name is found.'); + is('symbols.getSymbol("Article").hasMethod("init")', true, 'The initializer method name of prototype function is correct.'); + is('symbols.getSymbol("Article").hasMember("counter")', true, 'A static property set in the prototype definition is found.'); + is('symbols.getSymbol("Article").hasMember("title")', true, 'An instance property set in the prototype is found.'); + is('symbols.getSymbol("Article#title").isStatic', false, 'An instance property has isStatic set to false.'); + is('symbols.getSymbol("Article.counter").name', "counter", 'A static property set in the initializer has the name set correctly.'); + is('symbols.getSymbol("Article.counter").memberOf', "Article", 'A static property set in the initializer has the memberOf set correctly.'); + is('symbols.getSymbol("Article.counter").isStatic', true, 'A static property set in the initializer has isStatic set to true.'); + } + , + function() { + symbolize({a:true, _: [SYS.pwd+"test/prototype_oblit.js"]}); + + is('symbols.getSymbol("Article").name', "Article", 'Oblit set to constructor prototype name is found.'); + is('typeof symbols.getSymbol("Article.prototype")', "undefined", 'The prototype oblit is not a symbol.'); + is('symbols.getSymbol("Article#getTitle").name', "getTitle", 'The nonstatic method name of prototype oblit is correct.'); + is('symbols.getSymbol("Article#getTitle").alias', "Article#getTitle", 'The alias of non-static method of prototype oblit is correct.'); + is('symbols.getSymbol("Article#getTitle").isStatic', false, 'The isStatic of a nonstatic method of prototype oblit is correct.'); + is('symbols.getSymbol("Article.getTitle").name', "getTitle", 'The static method name of prototype oblit is correct.'); + is('symbols.getSymbol("Article.getTitle").isStatic', true, 'The isStatic of a static method of prototype oblit is correct.'); + is('symbols.getSymbol("Article#getTitle").isa', "FUNCTION", 'The isa of non-static method of prototype oblit is correct.'); + is('symbols.getSymbol("Article.getTitle").alias', "Article.getTitle", 'The alias of a static method of prototype oblit is correct.'); + is('symbols.getSymbol("Article.getTitle").isa', "FUNCTION", 'The isa of static method of prototype oblit is correct.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/prototype_oblit_constructor.js"]}); + + is('symbols.getSymbol("Article").name', "Article", 'Oblit set to constructor prototype with inner constructor name is found.'); + is('symbols.getSymbol("Article#init").name', "init", 'The initializer method name of prototype oblit is correct.'); + is('symbols.getSymbol("Article").hasMember("pages")', true, 'Property set by initializer method "this" is on the outer constructor.'); + is('symbols.getSymbol("Article#Title").name', "Title", 'Name of the inner constructor name is found.'); + is('symbols.getSymbol("Article#Title").memberOf', "Article", 'The memberOf of the inner constructor name is found.'); + is('symbols.getSymbol("Article#Title").isa', "CONSTRUCTOR", 'The isa of the inner constructor name is constructor.'); + is('symbols.getSymbol("Article#Title").hasMember("title")', true, 'A property set on the inner constructor "this" is on the inner constructor.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/inner.js"]}); + + is('symbols.getSymbol("Outer").name', "Outer", 'Outer constructor prototype name is found.'); + is('symbols.getSymbol("Outer").methods.length', 1, 'Inner function doesnt appear as a method of the outer.'); + is('symbols.getSymbol("Outer").hasMethod("open")', true, 'Outer constructors methods arent affected by inner function.'); + is('symbols.getSymbol("Outer-Inner").alias', "Outer-Inner", 'Alias of inner function is found.'); + is('symbols.getSymbol("Outer-Inner").isa', "CONSTRUCTOR", 'isa of inner function constructor is found.'); + is('symbols.getSymbol("Outer-Inner").memberOf', "Outer", 'The memberOf of inner function is found.'); + is('symbols.getSymbol("Outer-Inner").name', "Inner", 'The name of inner function is found.'); + is('symbols.getSymbol("Outer-Inner#name").name', "name", 'A member of the inner function constructor, attached to "this" is found on inner.'); + is('symbols.getSymbol("Outer-Inner#name").memberOf', "Outer-Inner", 'The memberOf of an inner function member is found.'); + } + , + function() { + symbolize({a:true, _: [SYS.pwd+"test/prototype_nested.js"]}); + + is('symbols.getSymbol("Word").name', "Word", 'Base constructor name is found.'); + is('symbols.getSymbol("Word").hasMethod("reverse")', true, 'Base constructor method is found.'); + is('symbols.getSymbol("Word").methods.length', 1, 'Base constructor has only one method.'); + is('symbols.getSymbol("Word").memberOf', "", 'Base constructor memberOf is empty.'); + is('symbols.getSymbol("Word#reverse").name', "reverse", 'Member of constructor prototype name is found.'); + is('symbols.getSymbol("Word#reverse").memberOf', "Word", 'Member of constructor prototype memberOf is found.'); + is('symbols.getSymbol("Word#reverse.utf8").name', "utf8", 'Member of constructor prototype method name is found.'); + is('symbols.getSymbol("Word#reverse.utf8").memberOf', "Word#reverse", 'Static nested member memberOf is found.'); + } + , + function() { + symbolize({a:true, _: [SYS.pwd+"test/namespace_nested.js"]}); + + is('symbols.getSymbol("ns1").name', "ns1", 'Base namespace name is found.'); + is('symbols.getSymbol("ns1").memberOf', "", 'Base namespace memberOf is empty (its a constructor).'); + is('symbols.getSymbol("ns1.ns2").name', "ns2", 'Nested namespace name is found.'); + is('symbols.getSymbol("ns1.ns2").alias', "ns1.ns2", 'Nested namespace alias is found.'); + is('symbols.getSymbol("ns1.ns2").memberOf', "ns1", 'Nested namespace memberOf is found.'); + is('symbols.getSymbol("ns1.ns2.Function1").name', "Function1", 'Method of nested namespace name is found.'); + is('symbols.getSymbol("ns1.ns2.Function1").memberOf', "ns1.ns2", 'Constructor of nested namespace memberOf is found.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/functions_nested.js"]}); + + is('symbols.getSymbol("Zop").name', "Zop", 'Any constructor name is found.'); + is('symbols.getSymbol("Zop").isa', "CONSTRUCTOR", 'It isa constructor.'); + is('symbols.getSymbol("Zop").hasMethod("zap")', true, 'Its method name, set later, is in methods array.'); + is('symbols.getSymbol("Foo").name', "Foo", 'The containing constructor name is found.'); + is('symbols.getSymbol("Foo").hasMethod("methodOne")', true, 'Its method name is found.'); + is('symbols.getSymbol("Foo").hasMethod("methodTwo")', true, 'Its second method name is found.'); + is('symbols.getSymbol("Foo#methodOne").alias', "Foo#methodOne", 'A methods alias is found.'); + is('symbols.getSymbol("Foo#methodOne").isStatic', false, 'A methods is not static.'); + is('symbols.getSymbol("Bar").name', "Bar", 'A global function declared inside another function is found.'); + is('symbols.getSymbol("Bar").isa', "FUNCTION", 'It isa function.'); + is('symbols.getSymbol("Bar").memberOf', "_global_", 'It is global.'); + is('symbols.getSymbol("Foo-inner").name', "inner", 'An inner functions name is found.'); + is('symbols.getSymbol("Foo-inner").memberOf', "Foo", 'It is member of the outer function.'); + is('symbols.getSymbol("Foo-inner").isInner', true, 'It is an inner function.'); + } + , + function() { + symbolize({a:true, _: [SYS.pwd+"test/memberof_constructor.js"]}); + + is('symbols.getSymbol("Circle#Tangent").name', "Tangent", 'Constructor set on prototype using @member has correct name.'); + is('symbols.getSymbol("Circle#Tangent").memberOf', "Circle", 'Constructor set on prototype using @member has correct memberOf.'); + is('symbols.getSymbol("Circle#Tangent").alias', "Circle#Tangent", 'Constructor set on prototype using @member has correct alias.'); + is('symbols.getSymbol("Circle#Tangent").isa', "CONSTRUCTOR", 'Constructor set on prototype using @member has correct isa.'); + is('symbols.getSymbol("Circle#Tangent").isStatic', false, 'Constructor set on prototype using @member is not static.'); + is('symbols.getSymbol("Circle#Tangent#getDiameter").name', "getDiameter", 'Method set on prototype using @member has correct name.'); + is('symbols.getSymbol("Circle#Tangent#getDiameter").memberOf', "Circle#Tangent", 'Method set on prototype using @member has correct memberOf.'); + is('symbols.getSymbol("Circle#Tangent#getDiameter").alias', "Circle#Tangent#getDiameter", 'Method set on prototype using @member has correct alias.'); + is('symbols.getSymbol("Circle#Tangent#getDiameter").isa', "FUNCTION", 'Method set on prototype using @member has correct isa.'); + is('symbols.getSymbol("Circle#Tangent#getDiameter").isStatic', false, 'Method set on prototype using @member is not static.'); + } + , + function() { + symbolize({a:true, p: true, _: [SYS.pwd+"test/memberof.js"]}); + + is('symbols.getSymbol("pack.install").alias', "pack.install", 'Using @memberOf sets alias, when parent name is in memberOf tag.'); + is('symbols.getSymbol("pack.install.overwrite").name', "install.overwrite", 'Using @memberOf sets name, even if the name is dotted.'); + is('symbols.getSymbol("pack.install.overwrite").memberOf', "pack", 'Using @memberOf sets memberOf.'); + is('symbols.getSymbol("pack.install.overwrite").isStatic', true, 'Using @memberOf with value not ending in octothorp sets isStatic to true.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/borrows.js"]}); + + is('symbols.getSymbol("Layout").name', "Layout", 'Constructor can be found.'); + is('symbols.getSymbol("Layout").hasMethod("init")', true, 'Constructor method name can be found.'); + is('symbols.getSymbol("Layout").hasMember("orientation")', true, 'Constructor property name can be found.'); + + is('symbols.getSymbol("Page").hasMethod("reset")', true, 'Second constructor method name can be found.'); + is('symbols.getSymbol("Page").hasMember("orientation")', true, 'Second constructor borrowed property name can be found in properties.'); + is('symbols.getSymbol("Page#orientation").memberOf', "Page", 'Second constructor borrowed property memberOf can be found.'); + is('symbols.getSymbol("Page-getInnerElements").alias', "Page-getInnerElements", 'Can borrow an inner function and it is still inner.'); + is('symbols.getSymbol("Page.units").alias', "Page.units", 'Can borrow a static function and it is still static.'); + + is('symbols.getSymbol("ThreeColumnPage#init").alias', "ThreeColumnPage#init", 'Third constructor method can be found even though method with same name is borrowed.'); + is('symbols.getSymbol("ThreeColumnPage#reset").alias', "ThreeColumnPage#reset", 'Borrowed method can be found.'); + is('symbols.getSymbol("ThreeColumnPage#orientation").alias', "ThreeColumnPage#orientation", 'Twice borrowed method can be found.'); + + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/borrows2.js"]}); + + is('symbols.getSymbol("Foo").hasMethod("my_zop")', true, 'Borrowed method can be found.'); + is('symbols.getSymbol("Bar").hasMethod("my_zip")', true, 'Second borrowed method can be found.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/constructs.js"]}); + + is('symbols.getSymbol("Person").hasMethod("say")', true, 'The constructs tag creates a class that lends can add a method to.'); + } + , + function() { + symbolize({a: true, _: [SYS.pwd+"test/augments.js", SYS.pwd+"test/augments2.js"]}); + + is('symbols.getSymbol("Page").augments[0]', "Layout", 'An augmented class can be found.'); + is('symbols.getSymbol("Page#reset").alias', "Page#reset", 'Method of augmenter can be found.'); + is('symbols.getSymbol("Page").hasMethod("Layout#init")', true, 'Method from augmented can be found.'); + is('symbols.getSymbol("Page").hasMember("Layout#orientation")', true, 'Property from augmented can be found.'); + is('symbols.getSymbol("Page").methods.length', 3, 'Methods of augmented class are included in methods array.'); + + is('symbols.getSymbol("ThreeColumnPage").augments[0]', "Page", 'The extends tag is a synonym for augments.'); + is('symbols.getSymbol("ThreeColumnPage").hasMethod("ThreeColumnPage#init")', true, 'Local method overrides augmented method of same name.'); + is('symbols.getSymbol("ThreeColumnPage").methods.length', 3, 'Local method count is right.'); + + is('symbols.getSymbol("NewsletterPage").augments[0]', "ThreeColumnPage", 'Can augment across file boundaries.'); + is('symbols.getSymbol("NewsletterPage").augments.length', 2, 'Multiple augments are supported.'); + is('symbols.getSymbol("NewsletterPage").inherits[0].alias', "Junkmail#annoy", 'Inherited method with augments.'); + is('symbols.getSymbol("NewsletterPage").methods.length', 6, 'Methods of augmented class are included in methods array across files.'); + is('symbols.getSymbol("NewsletterPage").properties.length', 1, 'Properties of augmented class are included in properties array across files.'); + } + , + function() { + symbolize({a:true, _: [SYS.pwd+"test/static_this.js"]}); + + is('symbols.getSymbol("box.holder").name', "holder", 'Static namespace name can be found.'); + is('symbols.getSymbol("box.holder.foo").name', "foo", 'Static namespace method name can be found.'); + is('symbols.getSymbol("box.holder").isStatic', true, 'Static namespace method is static.'); + + is('symbols.getSymbol("box.holder.counter").name', "counter", 'Instance namespace property name set on "this" can be found.'); + is('symbols.getSymbol("box.holder.counter").alias', "box.holder.counter", 'Instance namespace property alias set on "this" can be found.'); + is('symbols.getSymbol("box.holder.counter").memberOf', "box.holder", 'Static namespace property memberOf set on "this" can be found.'); + } + , + function() { + symbolize({a:true, p: true, _: [SYS.pwd+"test/lend.js"]}); + + is('symbols.getSymbol("Person").name', "Person", 'Class defined in lend comment is found.'); + is('symbols.getSymbol("Person").hasMethod("initialize")', true, 'Lent instance method name can be found.'); + is('symbols.getSymbol("Person").hasMethod("say")', true, 'Second instance method can be found.'); + is('symbols.getSymbol("Person#sing").isStatic', false, 'Instance method is known to be not static.'); + + is('symbols.getSymbol("Person.getCount").name', "getCount", 'Static method name from second lend comment can be found.'); + is('symbols.getSymbol("Person.getCount").isStatic', true, 'Static method from second lend comment is known to be static.'); + + is('LOG.warnings.filter(function($){if($.indexOf("notok") > -1) return $}).length', 1, 'A warning is emitted when lending to an undocumented parent.'); + } + , + function() { + symbolize({a:true, _: [SYS.pwd+"test/param_inline.js"]}); + + is('symbols.getSymbol("Layout").params[0].type', "int", 'Inline param name is set.'); + is('symbols.getSymbol("Layout").params[0].desc', "The number of columns.", 'Inline param desc is set from comment.'); + is('symbols.getSymbol("Layout#getElement").params[0].name', "id", 'User defined param documentation takes precedence over parser defined.'); + is('symbols.getSymbol("Layout#getElement").params[0].isOptional', true, 'Default for param is to not be optional.'); + is('symbols.getSymbol("Layout#getElement").params[1].isOptional', false, 'Can mark a param as being optional.'); + is('symbols.getSymbol("Layout#getElement").params[1].type', "number|string", 'Type of inline param doc can have multiple values.'); + is('symbols.getSymbol("Layout#Canvas").params[0].type', "", 'Type can be not defined for some params.'); + is('symbols.getSymbol("Layout#Canvas").params[2].type', "int", 'Type can be defined inline for only some params.'); + is('symbols.getSymbol("Layout#rotate").params.length', 0, 'Docomments inside function sig is ignored without a param.'); + is('symbols.getSymbol("Layout#init").params[2].type', "zoppler", 'Doc comment type overrides inline type for param with same name.'); + } + , + function() { + symbolize({a: true, _: [SYS.pwd+"test/shared.js", SYS.pwd+"test/shared2.js"]}); + + is('symbols.getSymbol("Array#some").name', 'some', 'The name of a symbol in a shared section is found.'); + is('symbols.getSymbol("Array#some").alias', 'Array#some', 'The alias of a symbol in a shared section is found.'); + is('symbols.getSymbol("Array#some").desc', "Extension to builtin array.", 'A description can be shared.'); + is('symbols.getSymbol("Array#filter").desc', "Extension to builtin array.\nChange every element of an array.", 'A shared description is appended.'); + is('symbols.getSymbol("Queue").desc', "A first in, first out data structure.", 'A description is not shared when outside a shared section.'); + is('symbols.getSymbol("Queue.rewind").alias', "Queue.rewind", 'Second shared tag can be started.'); + is('symbols.getSymbol("startOver").alias', "startOver", 'Shared tag doesnt cross over files.'); + } + , + function() { + symbolize({a: true, _: [SYS.pwd+"test/config.js"]}); + is('symbols.getSymbol("Contact").params[0].name', 'person', 'The name of a param is found.'); + is('symbols.getSymbol("Contact").params[1].name', 'person.name', 'The name of a param set with a dot name is found.'); + is('symbols.getSymbol("Contact").params[2].name', 'person.age', 'The name of a second param set with a dot name is found.'); + is('symbols.getSymbol("Contact").params[4].name', 'connection', 'The name of a param after config is found.'); + + is('symbols.getSymbol("Family").params[0].name', 'persons', 'Another name of a param is found.'); + is('symbols.getSymbol("Family").params[1].name', 'persons.Father', 'The name of a param+config is found.'); + is('symbols.getSymbol("Family").params[2].name', 'persons.Mother', 'The name of a second param+config is found.'); + is('symbols.getSymbol("Family").params[3].name', 'persons.Children', 'The name of a third param+config is found.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/ignore.js"]}); + is('LOG.warnings.filter(function($){if($.indexOf("undocumented symbol Ignored") > -1) return $}).length', 1, 'A warning is emitted when documenting members of an ignored parent.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/functions_anon.js"]}); + is('symbols.getSymbol("a.b").alias', 'a.b', 'In anonymous constructor this is found to be the container object.'); + is('symbols.getSymbol("a.f").alias', 'a.f', 'In anonymous constructor this can have a method.'); + is('symbols.getSymbol("a.c").alias', 'a.c', 'In anonymous constructor method this is found to be the container object.'); + is('symbols.getSymbol("g").alias', 'g', 'In anonymous function executed inline this is the global.'); + is('symbols.getSymbol("bar2.p").alias', 'bar2.p', 'In named constructor executed inline this is the container object.'); + is('symbols.getSymbol("module.pub").alias', 'module.pub', 'In parenthesized anonymous function executed inline function scoped variables arent documented.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/oblit_anon.js"]}); + is('symbols.getSymbol("opt").name', 'opt', 'Anonymous object properties are created.'); + is('symbols.getSymbol("opt.conf.keep").alias', 'opt.conf.keep', 'Anonymous object first property is assigned to $anonymous.'); + is('symbols.getSymbol("opt.conf.base").alias', 'opt.conf.base', 'Anonymous object second property is assigned to $anonymous.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/params_optional.js"]}); + is('symbols.getSymbol("Document").params.length', 3, 'Correct number of params are found when optional param syntax is used.'); + is('symbols.getSymbol("Document").params[1].name', "id", 'Name of optional param is found.'); + is('symbols.getSymbol("Document").params[1].isOptional', true, 'Optional param is marked isOptional.'); + is('symbols.getSymbol("Document").params[2].name', "title", 'Name of optional param with default value is found.'); + is('symbols.getSymbol("Document").params[2].isOptional', true, 'Optional param with default value is marked isOptional.'); + is('symbols.getSymbol("Document").params[2].defaultValue', " This is untitled.", 'Optional param default value is found.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/synonyms.js"]}); + is('symbols.getSymbol("myObject.myFunc").type', 'function', 'Type can be set to function.'); + } + , + function() { + symbolize({a:true, p:true, _: [SYS.pwd+"test/event.js"]}); + is('symbols.getSymbol("Kitchen#event:cakeEaten").isEvent', true, 'Function with event prefix is an event.'); + is('symbols.getSymbol("Kitchen#cakeEaten").isa', "FUNCTION", 'Function with same name as event isa function.'); + } + , + function() { + symbolize({x:"js", a:true, _: [SYS.pwd+"test/scripts/"]}); + is('JSDOC.JsDoc.srcFiles.length', 1, 'Only js files are scanned when -x=js.'); + } + , + function() { + symbolize({x:"js", a:true, _: [SYS.pwd+"test/exports.js"]}); + is('symbols.getSymbol("mxn.Map#doThings").name', 'doThings', 'Exports creates a documentation alias that can have methods.'); + } + , + function() { + symbolize({p:true, a:true, _: [SYS.pwd+"test/module.js"]}); + is('symbols.getSymbol("myProject.myModule.myPublicMethod").name', 'myPublicMethod', 'A function wrapped in parens can be recognized.'); + is('symbols.getSymbol("myProject.myModule-myPrivateMethod").name', 'myPrivateMethod', 'A private method in the scope of a function wrapped in parens can be recognized.'); + is('symbols.getSymbol("myProject.myModule-myPrivateVar").name', 'myPrivateVar', 'A private member in the scope of a function wrapped in parens can be recognized.'); + } +]; + +//// run and print results +print(testrun(testCases)); diff --git a/doc/jsdoc-toolkit/app/test/addon.js b/doc/jsdoc-toolkit/app/test/addon.js new file mode 100644 index 000000000..888620533 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/addon.js @@ -0,0 +1,24 @@ +String.prototype.reverse = function() { +} + +String.prototype.reverse.utf8 = function() { +} + +Function.count = function() { +} + +/** @memberOf Function */ +Function.count.reset = function() { +} + +/** @memberOf Function */ +count.getValue = function() { +} + +/** @memberOf Function.prototype */ +getSig = function() { +} + +/** @memberOf Function.prototype */ +Function.prototype.getProps = function() { +} diff --git a/doc/jsdoc-toolkit/app/test/anon_inner.js b/doc/jsdoc-toolkit/app/test/anon_inner.js new file mode 100644 index 000000000..227eeee55 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/anon_inner.js @@ -0,0 +1,14 @@ +/** + * @name bar + * @namespace + */ + +new function() { + /** + * @name bar-foo + * @function + * @param {number} x + */ + function foo(x) { + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/augments.js b/doc/jsdoc-toolkit/app/test/augments.js new file mode 100644 index 000000000..12e706ebc --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/augments.js @@ -0,0 +1,31 @@ +/** +@constructor +*/ +function Layout(p) { + this.init = function(p) { + } + + this.getId = function() { + } + + /** @type Page */ + this.orientation = "landscape"; +} + +/** +@constructor +@augments Layout +*/ +function Page() { + this.reset = function(b) { + } +} + +/** +@extends Page +@constructor +*/ +function ThreeColumnPage() { + this.init = function(resetCode) { + } +} diff --git a/doc/jsdoc-toolkit/app/test/augments2.js b/doc/jsdoc-toolkit/app/test/augments2.js new file mode 100644 index 000000000..e8388f0f8 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/augments2.js @@ -0,0 +1,26 @@ +/** +@constructor +*/ +function LibraryItem() { + this.reserve = function() { + } +} + +/** +@constructor +*/ +function Junkmail() { + this.annoy = function() { + } +} + +/** +@inherits Junkmail.prototype.annoy as pester +@augments ThreeColumnPage +@augments LibraryItem +@constructor +*/ +function NewsletterPage() { + this.getHeadline = function() { + } +} diff --git a/doc/jsdoc-toolkit/app/test/borrows.js b/doc/jsdoc-toolkit/app/test/borrows.js new file mode 100644 index 000000000..a5d8ea4aa --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/borrows.js @@ -0,0 +1,46 @@ +/** +@constructor +*/ +function Layout(p) { + /** initilize 1 */ + this.init = function(p) { + } + + /** get the id */ + this.getId = function() { + } + + /** @type string */ + this.orientation = "landscape"; + + function getInnerElements(elementSecretId){ + } +} + +/** A static method. */ +Layout.units = function() { +} + +/** +@constructor +@borrows Layout#orientation +@borrows Layout-getInnerElements +@borrows Layout.units +*/ +function Page() { + /** reset the page */ + this.reset = function(b) { + } +} + +/** +@constructor +@borrows Layout.prototype.orientation as this.orientation +@borrows Layout.prototype.init as #init +@inherits Page.prototype.reset as #reset +*/ +function ThreeColumnPage() { + /** initilize 2 */ + this.init = function(p) { + } +} diff --git a/doc/jsdoc-toolkit/app/test/borrows2.js b/doc/jsdoc-toolkit/app/test/borrows2.js new file mode 100644 index 000000000..c0d5ea21f --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/borrows2.js @@ -0,0 +1,23 @@ +// testing circular borrows + +/** + @class + @borrows Bar#zop as this.my_zop +*/ +function Foo() { + /** this is a zip. */ + this.zip = function() {} + + this.my_zop = new Bar().zop; +} + +/** + @class + @borrows Foo#zip as this.my_zip +*/ +function Bar() { + /** this is a zop. */ + this.zop = function() {} + + this.my_zip = new Foo().zip; +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/config.js b/doc/jsdoc-toolkit/app/test/config.js new file mode 100644 index 000000000..0748a210a --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/config.js @@ -0,0 +1,22 @@ +/** + * @constructor + * @param person The person. + * @param {string} person.name The person's name. + * @config {integer} age The person's age. + * @config [id=1] Optional id number to use. + * @param connection + */ +function Contact(person, connection) { + +} + +/** + * @constructor + * @param persons + * @config {string} Father The paternal person. + * @config {string} Mother The maternal person. + * @config {string[]} Children And the rest. + */ +function Family(/**Object*/persons) { + +} diff --git a/doc/jsdoc-toolkit/app/test/constructs.js b/doc/jsdoc-toolkit/app/test/constructs.js new file mode 100644 index 000000000..cca5dbd38 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/constructs.js @@ -0,0 +1,18 @@ +var Person = makeClass( + /** + @scope Person + */ + { + /** + This is just another way to define a constructor. + @constructs + @param {string} name The name of the person. + */ + initialize: function(name) { + this.name = name; + }, + say: function(message) { + return this.name + " says: " + message; + } + } +); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/encoding.js b/doc/jsdoc-toolkit/app/test/encoding.js new file mode 100644 index 000000000..ba6421931 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/encoding.js @@ -0,0 +1,10 @@ + +/** + * @Constructor + * @desc é…置文件 + * @class 什么也ä¸è¿”回 + */ +function Test(conf) { + // do something; +} + diff --git a/doc/jsdoc-toolkit/app/test/encoding_other.js b/doc/jsdoc-toolkit/app/test/encoding_other.js new file mode 100644 index 000000000..b144da4cf --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/encoding_other.js @@ -0,0 +1,12 @@ + +/** + * @Constructor + * @desc ðïîÛ + * @class ßàáâãäåæçèçìëêíîï °±²³´µ¡¶·¸¹ + */ +function Test(conf) { + // do something; +} + +// run with commanline option -e=iso-8859-5 + diff --git a/doc/jsdoc-toolkit/app/test/event.js b/doc/jsdoc-toolkit/app/test/event.js new file mode 100644 index 000000000..7e41d6f63 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/event.js @@ -0,0 +1,54 @@ +/** + * @name Kitchen + * @constructor + * @fires Bakery#event:donutOrdered + */ + +/** + * Fired when some cake is eaten. + * @name Kitchen#event:cakeEaten + * @function + * @param {Number} pieces The number of pieces eaten. + */ + +/** + * Find out if cake was eaten. + * @name Kitchen#cakeEaten + * @function + * @param {Boolean} wasEaten + */ + +/** + * @name getDesert + * @function + * @fires Kitchen#event:cakeEaten + */ + +/** + * @name Bakery + * @constructor + * @extends Kitchen + */ + +/** + * Fired when a donut order is made. + * @name Bakery#event:donutOrdered + * @event + * @param {Event} e The event object. + * @param {String} [e.topping] Optional sprinkles. + */ + +/** + * @constructor + * @borrows Bakery#event:donutOrdered as this.event:cakeOrdered + */ +function CakeShop() { +} + +/** @event */ +CakeShop.prototype.icingReady = function(isPink) { +} + +/** @event */ +function amHungry(/**Boolean*/enoughToEatAHorse) { +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/exports.js b/doc/jsdoc-toolkit/app/test/exports.js new file mode 100644 index 000000000..63a87cb4d --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/exports.js @@ -0,0 +1,14 @@ +/** @namespace */ +var mxn = {}; + +(function(){ + /** @exports Map as mxn.Map */ + var Map = + /** @constructor */ + mxn.Map = function() { + }; + + /** A method. */ + Map.prototype.doThings = function() { + }; +})(); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/functions_anon.js b/doc/jsdoc-toolkit/app/test/functions_anon.js new file mode 100644 index 000000000..e9dd6c1be --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/functions_anon.js @@ -0,0 +1,39 @@ +/** an anonymous constructor executed inline */ +a = new function() { + /** a.b*/ + this.b = 1; + /** a.f */ + this.f = function() { + /** a.c */ + this.c = 2; + } +} + + +/** + named function executed inline +*/ +bar1 = function Zoola1() { + /** property of global */ + this.g = 1; +}(); + +/** + named constructor executed inline +*/ +bar2 = new function Zoola2() { + /** property of bar */ + this.p = 1; +}; + +/** module pattern */ +module = (function () { + /** won't appear in documentation */ + var priv = 1; + + /** @scope module */ + return { + /** will appear as a property of module */ + pub: 1 + } +})(); diff --git a/doc/jsdoc-toolkit/app/test/functions_nested.js b/doc/jsdoc-toolkit/app/test/functions_nested.js new file mode 100644 index 000000000..f044fafee --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/functions_nested.js @@ -0,0 +1,33 @@ +/** @constructor */ +function Zop() { +} + +/** + @class +*/ +Foo = function(id) { + // this is a bit twisted, but if you call Foo() you will then + // modify Foo(). This is kinda, sorta non-insane, because you + // would have to call Foo() 100% of the time to use Foo's methods + Foo.prototype.methodOne = function(bar) { + alert(bar); + }; + + // same again + Foo.prototype.methodTwo = function(bar2) { + alert(bar2); + }; + + // and these are only executed if the enclosing function is actually called + // and who knows if that will ever happen? + Bar = function(pez) { + alert(pez); + }; + Zop.prototype.zap = function(p){ + alert(p); + }; + + // but this is only visible inside Foo + function inner() { + } +}; diff --git a/doc/jsdoc-toolkit/app/test/global.js b/doc/jsdoc-toolkit/app/test/global.js new file mode 100644 index 000000000..5ea489496 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/global.js @@ -0,0 +1,13 @@ +/** ecks */ +var x = [1, 2, 4]; + +var y = { + foo: function(){ + } +} + +bar = function() { +} + +function zop() { +} diff --git a/doc/jsdoc-toolkit/app/test/globals.js b/doc/jsdoc-toolkit/app/test/globals.js new file mode 100644 index 000000000..3f83fb1f9 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/globals.js @@ -0,0 +1,25 @@ +function example(/**Circle*/a, b) { + /** a global defined in function */ + var number = a; + + var hideNumber = function(){ + } + + setNumber = function(){ + } + alert('You have chosen: ' + b); +} + +function initPage() { + var supported = document.createElement && document.getElementsByTagName; + if (!supported) return; + // start of DOM script + var x = document.getElementById('writeroot'); + // etc. +} + +/** an example var */ +var document = new Document(x, y); + +var getNumber = function(){ +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/ignore.js b/doc/jsdoc-toolkit/app/test/ignore.js new file mode 100644 index 000000000..d3fac9ed5 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/ignore.js @@ -0,0 +1,10 @@ +/** + * A test constructor. + * @constructor + * @ignore + */ +function Ignored() { + /** a method */ + this.bar = function() { + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/inner.js b/doc/jsdoc-toolkit/app/test/inner.js new file mode 100644 index 000000000..37cfa9dc2 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/inner.js @@ -0,0 +1,16 @@ +/** + * @constructor + */ +function Outer() { + /** + * @constructor + */ + function Inner(name) { + /** The name of this. */ + this.name = name; + } + + this.open = function(name) { + return (new Inner(name)); + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/jsdoc_test.js b/doc/jsdoc-toolkit/app/test/jsdoc_test.js new file mode 100644 index 000000000..081771285 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/jsdoc_test.js @@ -0,0 +1,477 @@ +/** + * @fileoverview This file is to be used for testing the JSDoc parser + * It is not intended to be an example of good JavaScript OO-programming, + * nor is it intended to fulfill any specific purpose apart from + * demonstrating the functionality of the + * JSDoc parser + * + * @author Gabriel Reid gab_reid@users.sourceforge.net + * @version 0.1 + */ + + +/** + * Construct a new Shape object. + * @class This is the basic Shape class. + * It can be considered an abstract class, even though no such thing + * really existing in JavaScript + * @constructor + * @throws MemoryException if there is no more memory + * @throws GeneralShapeException rarely (if ever) + * @return {Shape|Coordinate} A new shape. + */ +function Shape(){ + + /** + * This is an example of a function that is not given as a property + * of a prototype, but instead it is assigned within a constructor. + * For inner functions like this to be picked up by the parser, the + * function that acts as a constructor must be denoted with + * the @constructor tag in its comment. + * @type String + */ + this.getClassName = function(){ + return "Shape"; + } + + /** + * This is an inner method, just used here as an example + * @since version 0.5 + * @author Sue Smart + */ + function addReference(){ + // Do nothing... + } + +} + +/** + * Create a new Hexagon instance. + * @extends Shape + * @class Hexagon is a class that is a logical sublcass of + * {@link Shape} (thanks to the @extends tag), but in + * reality it is completely unrelated to Shape. + * @param {int} sideLength The length of one side for the new Hexagon + * @example + * var h = new Hexagon(2); + * @example + * if (hasHex) { + * hex = new Hexagon(5); + * color = hex.getColor(); + * } + */ +function Hexagon(sideLength) { +} + + +/** + * This is an unattached (static) function that adds two integers together. + * @param {int} One The first number to add + * @param {int} Two The second number to add + * @author Gabriel Reid + * @deprecated So you shouldn't use it anymore! Use {@link Shape#getClassName} instead. + */ +function Add(One, Two){ + return One + Two; +} + + +/** + * The color of this shape + * @type Color + */ +Shape.prototype.color = null; + +/** + * The border of this shape. + * @field + * @type int + */ +Shape.prototype.border = function(){return border;}; + +/* + * These are all the instance method implementations for Shape + */ + +/** + * Get the coordinates of this shape. It is assumed that we're always talking + * about shapes in a 2D location here. + * @requires The {@link Shape} class + * @returns A Coordinate object representing the location of this Shape + * @type Coordinate[] + */ +Shape.prototype.getCoords = function(){ + return this.coords; +} + +/** + * Get the color of this shape. + * @see #setColor + * @see The Color library. + * @link Shape + * @type Color + */ +Shape.prototype.getColor = function(){ + return this.color; +} + +/** + * Set the coordinates for this Shape + * @param {Coordinate} coordinates The coordinates to set for this Shape + */ +Shape.prototype.setCoords = function(coordinates){ + this.coords = coordinates; +} + +/** + * Set the color for this Shape + * @param {Color} color The color to set for this Shape + * @param other There is no other param, but it can still be documented if + * optional parameters are used + * @throws NonExistantColorException (no, not really!) + * @see #getColor + */ +Shape.prototype.setColor = function(color){ + this.color = color; +} + +/** + * Clone this shape + * @returns A copy of this shape + * @type Shape + * @author Gabriel Reid + */ +Shape.prototype.clone = function(){ + return new Shape(); +} + +/** + * Create a new Rectangle instance. + * @class A basic rectangle class, inherits from Shape. + * This class could be considered a concrete implementation class + * @constructor + * @param {int} width The optional width for this Rectangle + * @param {int} height Thie optional height for this Rectangle + * @author Gabriel Reid + * @see Shape is the base class for this + * @augments Shape + * @hilited + */ +function Rectangle(width, // This is the width + height // This is the height + ){ + if (width){ + this.width = width; + if (height){ + this.height = height; + } + } +} + + +/* Inherit from Shape */ +Rectangle.prototype = new Shape(); + +/** + * Value to represent the width of the Rectangle. + *
Text in bold and italic and a + * link to SourceForge + * @private + * @type int + */ +Rectangle.prototype.width = 0; + +/** + * Value to represent the height of the Rectangle + * @private + * @type int + */ +Rectangle.prototype.height = 0; + +/** + * Get the type of this object. + * @type String + */ +Rectangle.prototype.getClassName= function(){ + return "Rectangle"; +} + +/** + * Get the value of the width for the Rectangle + * @type int + * @see Rectangle#setWidth + */ +Rectangle.prototype.getWidth = function(){ + return this.width; +} + +/** + * Get the value of the height for the Rectangle. + * Another getter is the {@link Shape#getColor} method in the + * {@link Shape} base class. + * @return The height of this Rectangle + * @type int + * @see Rectangle#setHeight + */ +Rectangle.prototype.getHeight = function(){ + return this.height; +} + +/** + * Set the width value for this Rectangle. + * @param {int} width The width value to be set + * @see #setWidth + */ +Rectangle.prototype.setWidth = function(width){ + this.width = width; +} + +/** + * Set the height value for this Rectangle. + * @param {int} height The height value to be set + * @see #getHeight + */ +Rectangle.prototype.setHeight = function(height){ + this.height = height; +} + +/** + * Get the value for the total area of this Rectangle + * @return total area of this Rectangle + * @type int + */ +Rectangle.prototype.getArea = function(){ + return width * height; +} + + +/** + * Create a new Square instance. + * @class A Square is a subclass of {@link Rectangle} + * @param {int} width The optional width for this Rectangle + * @param {int} height The optional height for this Rectangle + * @augments Rectangle + */ +function Square(width, height){ + if (width){ + this.width = width; + if (height){ + this.height = height; + } + } + +} + +/* Square is a subclass of Rectangle */ +Square.prototype = new Rectangle(); + +/** + * Set the width value for this Shape. + * @param {int} width The width value to be set + * @see #getWidth + */ +Square.prototype.setWidth = function(width){ + this.width = this.height = width; +} + +/** + * Set the height value for this Shape + * Sets the {@link Rectangle#height} attribute in the Rectangle. + * @param {int} height The height value to be set + */ +Square.prototype.setHeight = function(height){ + this.height = this.width = height; +} + + +/** + * Create a new Circle instance based on a radius. + * @class Circle class is another subclass of Shape + * @extends Shape + * @param {int} radius The optional radius of this {@link Circle } + * @mixin Square.prototype.setWidth as this.setDiameter + */ +function Circle(radius){ + if (radius) { + /** The radius of the this Circle. */ + this.radius = radius; + } +} + +/* Circle inherits from {@link Shape} */ +Circle.prototype = new Shape(); + +/** + * The radius value for this Circle + * @private + * @type int + */ +Circle.prototype.radius = 0; + +/** + * A very simple class (static) field that is also a constant + * @final + * @type float + */ +Circle.PI = 3.14; + +/** + * Get the radius value for this Circle + * @type int + * @see #setRadius + */ +Circle.prototype.getRadius = function(){ + return this.radius; +} + +/** + * Set the radius value for this Circle + * @param {int} radius The {@link Circle#radius} value to set + * @see #getRadius + */ +Circle.prototype.setRadius = function(radius){ + this.radius = radius; +} + +/** + * An example of a class (static) method that acts as a factory for Circle + * objects. Given a radius value, this method creates a new Circle. + * @param {int} radius The radius value to use for the new Circle. + * @type Circle + */ +Circle.createCircle = function(radius){ + return new Circle(radius); +} + + +/** + * Create a new Coordinate instance based on x and y grid data. + * @class Coordinate is a class that can encapsulate location information. + * @param {int} [x=0] The optional x portion of the Coordinate + * @param {int} [y=0] The optinal y portion of the Coordinate + */ +function Coordinate(x, y){ + if (x){ + this.x = x; + if (y){ + this.y = y; + } + } +} + +/** + * The x portion of the Coordinate + * @type int + * @see #getX + * @see #setX + */ +Coordinate.prototype.x = 0; + +/** + * The y portion of the Coordinate + * @type int + * @see #getY + * @see #setY + */ +Coordinate.prototype.y = 0; + +/** + * Gets the x portion of the Coordinate. + * @type int + * @see #setX + */ +Coordinate.prototype.getX = function(){ + return this.x; +} + +/** + * Get the y portion of the Coordinate. + * @type int + * @see #setY + */ +Coordinate.prototype.getY = function(){ + return this.y; +} + +/** + * Sets the x portion of the Coordinate. + * @param {int} x The x value to set + * @see #getX + */ +Coordinate.prototype.setX = function(x){ + this.x = x; +} + +/** + * Sets the y portion of the Coordinate. + * @param {int} y The y value to set + * @see #getY + */ +Coordinate.prototype.setY = function(y){ + this.y = y; +} + +/** + * @class This class exists to demonstrate the assignment of a class prototype + * as an anonymous block. + */ +function ShapeFactory(){ +} + +ShapeFactory.prototype = { + /** + * Creates a new {@link Shape} instance. + * @return A new {@link Shape} + * @type Shape + */ + createShape: function(){ + return new Shape(); + } +} + +/** + * An example of a singleton class + * @param ... Arguments represent {@link coordinate}s in the shape. + * @constructor + */ +MySingletonShapeFactory = function(){ + + /** + * Get the next {@link Shape} + * @type Shape + * @return A new {@link Shape} + */ + this.getShape = function(){ + return null; + } + +} + + +/** + * Create a new Foo instance. + * @class This is the Foo class. It exists to demonstrate 'nested' classes. + * @constructor + * @see Foo.Bar + */ +function Foo(){} + +/** + * Creates a new instance of Bar. + * @class This class exists to demonstrate 'nested' classes. + * @constructor + * @see Foo.Bar + */ +function Bar(){} + +/** + * Nested class + * @constructor + */ +Foo.Bar = function(){ + /** The x. */ this.x = 2; +} + +Foo.Bar.prototype = new Bar(); +/** The y. */ +Foo.Bar.prototype.y = '3'; diff --git a/doc/jsdoc-toolkit/app/test/lend.js b/doc/jsdoc-toolkit/app/test/lend.js new file mode 100644 index 000000000..92b15d5ad --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/lend.js @@ -0,0 +1,33 @@ + /** @class */ +var Person = Class.create( + /** + @lends Person.prototype + */ + { + initialize: function(name) { + this.name = name; + }, + say: function(message) { + return this.name + ': ' + message; + } + } + ); + +/** @lends Person.prototype */ +{ + /** like say but more musical */ + sing: function(song) { + } +} + +/** @lends Person */ +{ + getCount: function() { + } +} + +/** @lends Unknown.prototype */ +{ + notok: function() { + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/memberof.js b/doc/jsdoc-toolkit/app/test/memberof.js new file mode 100644 index 000000000..883bbdeb0 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/memberof.js @@ -0,0 +1,19 @@ +/** @constructor */ +pack = function() { + this.init = function(){} + function config(){} +} + + pack.build = function(task) {}; + +/** @memberOf pack */ +pack.install = function() {} + +/** @memberOf pack */ +pack.install.overwrite = function() {} + +/** @memberOf pack */ +clean = function() {} + +/** @memberOf pack-config */ +install = function() {}; diff --git a/doc/jsdoc-toolkit/app/test/memberof_constructor.js b/doc/jsdoc-toolkit/app/test/memberof_constructor.js new file mode 100644 index 000000000..80fde735c --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/memberof_constructor.js @@ -0,0 +1,17 @@ +/** @constructor */ +function Circle(){} + +/** + @constructor + @memberOf Circle# + */ +Circle.prototype.Tangent = function(){}; + +// renaming Circle#Tangent to Circle#Circle#Tangent + +/** + @memberOf Circle#Tangent# + */ +Circle.prototype.Tangent.prototype.getDiameter = function(){}; + + diff --git a/doc/jsdoc-toolkit/app/test/module.js b/doc/jsdoc-toolkit/app/test/module.js new file mode 100644 index 000000000..5b3fe42c5 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/module.js @@ -0,0 +1,17 @@ +/** @namespace */ +myProject = myProject || {}; + +/** @namespace */ +myProject.myModule = (function () { + /** describe myPrivateVar here */ + var myPrivateVar = ""; + + var myPrivateMethod = function () { + } + + /** @scope myProject.myModule */ + return { + myPublicMethod: function () { + } + }; +})(); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/name.js b/doc/jsdoc-toolkit/app/test/name.js new file mode 100644 index 000000000..e88a51a71 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/name.js @@ -0,0 +1,19 @@ +/** + @name Response + @class +*/ + +Response.prototype = { + /** + @name Response#text + @function + @description + Gets the body of the response as plain text + @returns {String} + Response as text + */ + + text: function() { + return this.nativeResponse.responseText; + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/namespace_nested.js b/doc/jsdoc-toolkit/app/test/namespace_nested.js new file mode 100644 index 000000000..46cafa2fa --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/namespace_nested.js @@ -0,0 +1,23 @@ +/** + @namespace This is the first namespace. +*/ +ns1 = {}; + +/** + This is the second namespace. + @namespace +*/ +ns1.ns2 = {}; + +/** + This part of ns1.ns2 + @constructor +*/ +ns1.ns2.Function1 = function() { +}; + +ns1.staticFunction = function() { +}; + +/** A static field in a namespace. */ +ns1.ns2.staticField = 1; diff --git a/doc/jsdoc-toolkit/app/test/nocode.js b/doc/jsdoc-toolkit/app/test/nocode.js new file mode 100644 index 000000000..1cf99bc98 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/nocode.js @@ -0,0 +1,13 @@ +/**#nocode+*/ + /** + @name star + @function + */ + function blahblah() { + + } +/**#nocode-*/ + +function yaddayadda() { + +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/oblit_anon.js b/doc/jsdoc-toolkit/app/test/oblit_anon.js new file mode 100644 index 000000000..8d9e9413e --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/oblit_anon.js @@ -0,0 +1,20 @@ +/** the options */ +opt = Opt.get( + arguments, + { + d: "directory", + c: "conf", + "D[]": "define" + } +); + +/** configuration */ +opt.conf = { + /** keep */ + keep: true, + /** base */ + base: getBase(this, {p: properties}) +} + + + diff --git a/doc/jsdoc-toolkit/app/test/overview.js b/doc/jsdoc-toolkit/app/test/overview.js new file mode 100644 index 000000000..1dfc09b1f --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/overview.js @@ -0,0 +1,20 @@ +/** + * @overview This "library" contains a + * lot of classes and functions. + * @example +
+	var x (x < 1);
+	alert("This 'is' \"code\"");
+ 
+ * @name My Cool Library + * @author Joe Smith jsmith@company.com + * @version 0.1 + */ + +/** + * Gets the current foo + * @param {String} fooId The unique identifier for the foo. + * @return {Object} Returns the current foo. + */ +function getFoo(fooID){ +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/param_inline.js b/doc/jsdoc-toolkit/app/test/param_inline.js new file mode 100644 index 000000000..09845b283 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/param_inline.js @@ -0,0 +1,37 @@ +/** + @constructor + @param columns The number of columns. +*/ +function Layout(/**int*/columns){ + /** + @param [id] The id of the element. + @param elName The name of the element. + */ + this.getElement = function( + /** string */ elName, + /** number|string */ id + ) { + }; + + /** + @constructor + */ + this.Canvas = function(top, left, /**int*/width, height) { + /** Is it initiated yet? */ + this.initiated = true; + } + + this.rotate = function(/**nothing*/) { + } + + /** + @param x + @param y + @param {zoppler} z*/ + this.init = function(x, y, /**abbler*/z) { + /** The xyz. */ + this.xyz = x+y+z; + this.getXyz = function() { + } + } +} diff --git a/doc/jsdoc-toolkit/app/test/params_optional.js b/doc/jsdoc-toolkit/app/test/params_optional.js new file mode 100644 index 000000000..18bf59829 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/params_optional.js @@ -0,0 +1,8 @@ + +/** + * @param {Page[]} pages + * @param {number} [id] Specifies the id, if applicable. + * @param {String} [title = This is untitled.] Specifies the title. + */ +function Document(pages, id, title){ +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/prototype.js b/doc/jsdoc-toolkit/app/test/prototype.js new file mode 100644 index 000000000..114700837 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/prototype.js @@ -0,0 +1,17 @@ +/** @constructor */ +function Article() { +} + +Article.prototype.init = function(title) { + /** the instance title */ + this.title = title; + + /** the static counter */ + Article.counter = 1; +} + +a = new Article(); +a.Init("my title"); + +print(a.title); +print(Article.counter); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/prototype_nested.js b/doc/jsdoc-toolkit/app/test/prototype_nested.js new file mode 100644 index 000000000..e8ca1ced2 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/prototype_nested.js @@ -0,0 +1,9 @@ +/** @constructor */ +function Word() { +} + +Word.prototype.reverse = function() { +} + +Word.prototype.reverse.utf8 = function() { +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/prototype_oblit.js b/doc/jsdoc-toolkit/app/test/prototype_oblit.js new file mode 100644 index 000000000..6cfc39caf --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/prototype_oblit.js @@ -0,0 +1,13 @@ +/** @constructor */ +function Article() { +} + +Article.prototype = { + /** instance get title */ + getTitle: function(){ + } +} + +/** static get title */ +Article.getTitle = function(){ +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/prototype_oblit_constructor.js b/doc/jsdoc-toolkit/app/test/prototype_oblit_constructor.js new file mode 100644 index 000000000..924824864 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/prototype_oblit_constructor.js @@ -0,0 +1,24 @@ +/** @constructor */ +function Article() { +} + +Article.prototype = { + /** @constructor */ + Title: function(title) { + /** the value of the Title instance */ + this.title = title; + }, + + init: function(pages) { + /** the value of the pages of the Article instance */ + this.pages = pages; + } +} + +f = new Article(); +f.init("one two three"); + +t = new f.Title("my title"); + +print(f.pages); +print(t.title); \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/public.js b/doc/jsdoc-toolkit/app/test/public.js new file mode 100644 index 000000000..35d34f6f0 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/public.js @@ -0,0 +1,10 @@ +/**@constructor*/ +function Foo() { + /** + @public + @static + @field + */ + var bar = function(x) { + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/scripts/code.js b/doc/jsdoc-toolkit/app/test/scripts/code.js new file mode 100644 index 000000000..e9d7ed2ec --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/scripts/code.js @@ -0,0 +1,5 @@ +/** + @class + */ +function thisiscode() { +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/scripts/notcode.txt b/doc/jsdoc-toolkit/app/test/scripts/notcode.txt new file mode 100644 index 000000000..fcd737e77 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/scripts/notcode.txt @@ -0,0 +1,5 @@ +(This is not code) +function foo(){{{{ +( +! +@ \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/shared.js b/doc/jsdoc-toolkit/app/test/shared.js new file mode 100644 index 000000000..e1c277a6f --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/shared.js @@ -0,0 +1,42 @@ + +/** + * Builtin object. + * @class + * @name Array + */ + +/**#@+ + * Extension to builtin array. + * @memberOf Array + * @method + */ + +/** + * @returns Boolen if some array members... + */ +Array.prototype.some = function(){}; + +/** + * Change every element of an array. + * @returns Filtered array copy. + */ +Array.prototype.filter = function(){}; + +/**#@-*/ + + +/** + * A first in, first out data structure. + * @constructor + */ +Queue = function(){}; + +/**#@+ + * Extension to Queue. + * @memberOf Queue + */ + +rewind = function(){ +} + +// should close automatically here. \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/shared2.js b/doc/jsdoc-toolkit/app/test/shared2.js new file mode 100644 index 000000000..3f7736a77 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/shared2.js @@ -0,0 +1,2 @@ +startOver = function(){ +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/shortcuts.js b/doc/jsdoc-toolkit/app/test/shortcuts.js new file mode 100644 index 000000000..f738f1e1c --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/shortcuts.js @@ -0,0 +1,22 @@ +// /**#=+ +// * { +// * 'D': 'Date.prototype', +// * '$N': 'Number' +// * } +// */ +// var D = Date.prototype, +// $N = Number; +// +// D.locale = function(){ +// }; +// +// /** +// @return {string} The cardinal number string. +// */ +// $N.nth = function(n){ +// }; +// +// LOAD.file = function(){ +// } +// +// /**#=-*/ \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/static_this.js b/doc/jsdoc-toolkit/app/test/static_this.js new file mode 100644 index 000000000..9407b20f3 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/static_this.js @@ -0,0 +1,13 @@ +/** the parent */ +var box = {}; + +/** @namespace */ +box.holder = {} + +box.holder.foo = function() { + /** the counter */ + this.counter = 1; +} + +box.holder.foo(); +print(box.holder.counter); diff --git a/doc/jsdoc-toolkit/app/test/synonyms.js b/doc/jsdoc-toolkit/app/test/synonyms.js new file mode 100644 index 000000000..09066b98f --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/synonyms.js @@ -0,0 +1,31 @@ +/** + @class + @inherits Bar#zop as #my_zop +*/ +function Foo() { + /** this is a zip. */ + this.zip = function() {} + + /** from Bar */ + this.my_zop = new Bar().zop; +} + +/** + @class + @borrows Foo#zip as this.my_zip +*/ +function Bar() { + /** this is a zop. */ + this.zop = function() {} + + /** from Foo */ + this.my_zip = new Foo().zip; +} + +/** @namespace */ +var myObject = { + /** + @type function + */ + myFunc: getFunction() +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/tosource.js b/doc/jsdoc-toolkit/app/test/tosource.js new file mode 100644 index 000000000..706d47652 --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/tosource.js @@ -0,0 +1,23 @@ +/** + * @param {Object} object + * @return {string} + */ +function valueOf(object) {} + +/** + * @param {Object} object + * @return {string} + */ +function toString(object) {} + +/** + * @param {Object} object + * @return {string} + */ +function toSource(object) {} + +/** + * @param {Object} object + * @return {string} + */ +function constructor(object) {} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/app/test/variable_redefine.js b/doc/jsdoc-toolkit/app/test/variable_redefine.js new file mode 100644 index 000000000..2c07da09a --- /dev/null +++ b/doc/jsdoc-toolkit/app/test/variable_redefine.js @@ -0,0 +1,14 @@ +/** @constructor */ +function Foo() { + var bar = 1; + bar = 2; // redefining a private + + this.baz = 1; + baz = 2; // global + + /** a private */ + var blap = { + /** in here */ + tada: 1 + } +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/changes.txt b/doc/jsdoc-toolkit/changes.txt new file mode 100644 index 000000000..d664750f3 --- /dev/null +++ b/doc/jsdoc-toolkit/changes.txt @@ -0,0 +1,107 @@ +== 2.3.2 == + + * Minor update to the usage notes and corrected the version number displayed in the output. + +== 2.3.1 == + + * Fixed HTML typo in allfiles template. (issue #228) + * Modified template to display version information for classes. + * Modified template to better support multiple methods with the same name. + * Fixed bug that caused template to error when backtick characters appeared around class names. + +== 2.3.0 == + + * Added option -u, --unique to avoid bug that causes multiple symbols with names that differ only by case to overwrite each others output on case-insensitive filesystems. ( issue #162 ) + * Fixed bug where {@links} in @deprecated tags did not resolve. ( issue #220 ) + * Fixed bug that caused parens around a function to make it to be unrecognized. ( issue #213 ) + * Fixed bug prevented explicit links to named anchors from working (thanks katgao.pku). ( issue #215 ) + * Fixed bug that prevented full description from appearing in file overview. ( issue #224 ) + +== 2.2.1 == + + * Fixed bug with class template, where sorting of methods was accidentally removed (thanks dezfowler). + * Added missing test files for the @exports unit tests. + +== 2.2.0 == + + * Fixed bug that caused exception when given a folder containing non-js files, even with the x commandline option set to "js". ( issue #193 ) + * Fixed typo in index template [patch submitted by olle]. ( issue #198 ) + * Modified @borrows tag experimentally to allow for missing "as ..." clause. + * Added support for the @exports tag, to allow one symbol to be documented as another. + * Added support for the -S option to document code following the Secure Modules pattern. + +== 2.1.0 == + + * Added support for the @event tag. + * Fixed bug that prevented the : character from appearing in symbol names. + * Fixed bug that prevented underscored symbols marked with @public being tagged as private. (issue #184 ) + * Fixed bug that randomly affected the @memberOf tag when the name of the symbol did not include the parent name. + * Fixed bug that prevented templates that were not in the jsdoc-toolkit folder from being found. ( issue #176 ) + * Added ability to check for trailing slash on template path. ( issue #177 ) + * Modified classDesc so that it no longer is appended with the constructor desc. + * Fixed call to plugin onDocCommentSrc. + * Added missing support for inline doc comments for function return types. ( issue #189 ) + * Added command line option -q, --quiet. + * Added command line option -E, --exclude. ( issue #143 ) + * Added 2 more hooks for plugins. ( issue #163 ) + * Added support for extending built-ins. ( issue #160 ) + * Added "compact" option to JSDOC.JsPlate.prototype.process. ( issue #159 ) + * @augments no longer documents static members as inherited. ( issue #138 ) + * @link to a class now goes to the page for that class, not the constructor. ( issue #178 ) + * Warnings of mismatched curly brace now include filename. ( issue #166 ) + * Fixed bug affecting template paths loaded via a configuration file when the trailing slash is missing. ( issue #191 ) + * Minor optimizations. + +== 2.0.2 == + + * Fixed bug that sometimes caused an example of division in the source code to be interpretted as a regex by the JsDoc Toolkit analyzer. ( issue #158 ) + * Fixed a bug that prevented private variables marked as @public from appearing in the documentation. ( issue #161 ) + * Fixed bug that prevented variable names with underscored properties from appearing in summaries. ( issue #173 ) + +== 2.0.1 == + + * Fixed bug that prevented @fileOverview tag from being recognized. + * Added support for @fieldOf as a synonym for @field plus @memberOf. + * Added support for @name tag in a @fileOverview comment to control the displayed name of the file. + * Added support for multiple @example tags. ( issue #152 ) + * Modified style sheet of jsdoc template to make more readable. ( issue #151 ) + * Fixed bug that prevented @since documentation from displaying correctly when it appeared in a class. ( issue #150 ) + * Fixed bug that caused inhertited properties to sometimes not resolve correctly. ( issue #144 ) + * Modified so that trailing whitespace in @example is always trimmed. ( issue #153 ) + * Added support for elseif to JsPlate. (hat tip to fredck) + * Added support for @location urls in the @overview comment to the jsdoc template. + +== Changes From Versions 1.4.0 to 2.0.0 == + + * Upgraded included version of Rhino from 1.6 to 1.7R1. + * Removed circular references in parsed documentation objects. + * Improved inheritance handling, now properties and events can be inherited same as methods. + * Improved handling of cross-file relationships, now having two related objects in separate files is not a problem. + * Improved ability to recognize membership of previously defined objects. + * Added ability to redefine parsing behavior with plugins. + * @methodOf is a synonym for @function and @memberOf. + * Added @default to document default values of members that are objects. + * Added ability to parse and refer to inner functions. + * Fixed bug that appeared when calling a method to set properties of the instance referred to by "this". + * Added ability to automatically create links to other symbols. + * New "jsdoc" template now produces fully W3C valid XHTML. + * Inline parameter type hint comments are now documented. + * Fixed error: Locally scoped variables (declared with var) no longer appear as global. + * It is now possible to run JsDoc Toolkit from any directory. + * Added support for inline {@link ...} tags. + * Added support for the -H command-line option to allow for custom content handlers. + * Tag names @inherits and @scope changed to @borrows and @lends. + ? Combining @constructor in a doclet with @lends now supported. + * Multiple @lend tags now supported. + * Added support for the @constructs tag, used inside a @lends block. + * Added support for the @constant tag. + * Fixed bug that prevented the use of [] as a default value. + * Added support for the @field tag. + * Added support for the @public tag (applied to inner functions). + * @namespace tag can now be applied to functions, not just object literals. + * Added support for the -s command line option to suppress source code output. + * Added new unit test framework. + * Underscored symbols are now treated as if they have a @private tag by default. + * Improved support for anonymous constructors. + * Added support for the nocode meta tag. + \ No newline at end of file diff --git a/doc/jsdoc-toolkit/conf/sample.conf b/doc/jsdoc-toolkit/conf/sample.conf new file mode 100644 index 000000000..ad0f08e65 --- /dev/null +++ b/doc/jsdoc-toolkit/conf/sample.conf @@ -0,0 +1,31 @@ +/* + This is an example of one way you could set up a configuration file to more + conveniently define some commandline options. You might like to do this if + you frequently reuse the same options. Note that you don't need to define + every option in this file, you can combine a configuration file with + additional options on the commandline if your wish. + + You would include this configuration file by running JsDoc Toolkit like so: + java -jar jsrun.jar app/run.js -c=conf/sample.conf + +*/ + +{ + // source files to use + _: ['app/test/jsdoc_test.js'], + + // document all functions, even uncommented ones + a: true, + + // including those marked @private + p: true, + + // some extra variables I want to include + D: {generatedBy: "Michael Mathews", copyright: "2008"}, + + // use this directory as the output directory + d: "docs", + + // use this template + t: "templates/jsdoc" +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/java/build.xml b/doc/jsdoc-toolkit/java/build.xml new file mode 100644 index 000000000..bb845ce3a --- /dev/null +++ b/doc/jsdoc-toolkit/java/build.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/jsdoc-toolkit/java/build_1.4.xml b/doc/jsdoc-toolkit/java/build_1.4.xml new file mode 100644 index 000000000..ab408a4c4 --- /dev/null +++ b/doc/jsdoc-toolkit/java/build_1.4.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/jsdoc-toolkit/java/classes/js.jar b/doc/jsdoc-toolkit/java/classes/js.jar new file mode 100644 index 000000000..0352cb180 Binary files /dev/null and b/doc/jsdoc-toolkit/java/classes/js.jar differ diff --git a/doc/jsdoc-toolkit/java/src/JsDebugRun.java b/doc/jsdoc-toolkit/java/src/JsDebugRun.java new file mode 100755 index 000000000..319a5c67b --- /dev/null +++ b/doc/jsdoc-toolkit/java/src/JsDebugRun.java @@ -0,0 +1,21 @@ +/** + * A trivial bootstrap class that simply adds the path to the + * .js file as an argument to the Rhino call. This little hack + * allows the code in the .js file to have access to it's own + * path via the Rhino arguments object. This is necessary to + * allow the .js code to find resource files in a location + * relative to itself. + * + * USAGE: java -jar jsdebug.jar path/to/file.js + */ +public class JsDebugRun { + public static void main(String[] args) { + String[] jsargs = {"-j="+args[0]}; + + String[] allArgs = new String[jsargs.length + args.length]; + System.arraycopy(args, 0, allArgs, 0, args.length); + System.arraycopy(jsargs, 0, allArgs, args.length ,jsargs.length); + + org.mozilla.javascript.tools.debugger.Main.main(allArgs); + } +} diff --git a/doc/jsdoc-toolkit/java/src/JsRun.java b/doc/jsdoc-toolkit/java/src/JsRun.java new file mode 100644 index 000000000..25f519a95 --- /dev/null +++ b/doc/jsdoc-toolkit/java/src/JsRun.java @@ -0,0 +1,21 @@ +/** + * A trivial bootstrap class that simply adds the path to the + * .js file as an argument to the Rhino call. This little hack + * allows the code in the .js file to have access to it's own + * path via the Rhino arguments object. This is necessary to + * allow the .js code to find resource files in a location + * relative to itself. + * + * USAGE: java -jar jsrun.jar path/to/file.js + */ +public class JsRun { + public static void main(String[] args) { + String[] jsargs = {"-j="+args[0]}; + + String[] allArgs = new String[jsargs.length + args.length]; + System.arraycopy(args, 0, allArgs, 0, args.length); + System.arraycopy(jsargs, 0, allArgs, args.length ,jsargs.length); + + org.mozilla.javascript.tools.shell.Main.main(allArgs); + } +} diff --git a/doc/jsdoc-toolkit/jsdebug.jar b/doc/jsdoc-toolkit/jsdebug.jar new file mode 100644 index 000000000..a0ac7daa6 Binary files /dev/null and b/doc/jsdoc-toolkit/jsdebug.jar differ diff --git a/doc/jsdoc-toolkit/jsrun.jar b/doc/jsdoc-toolkit/jsrun.jar new file mode 100644 index 000000000..49c03f4c6 Binary files /dev/null and b/doc/jsdoc-toolkit/jsrun.jar differ diff --git a/doc/jsdoc-toolkit/jsrun.sh b/doc/jsdoc-toolkit/jsrun.sh new file mode 100644 index 000000000..74ca79c05 --- /dev/null +++ b/doc/jsdoc-toolkit/jsrun.sh @@ -0,0 +1,52 @@ +#!/bin/ksh + +# launcher script for jsdoc +# Author: Avi Deitcher +# +# This program is released under the MIT License as follows: + +# Copyright (c) 2008-2009 Atomic Inc +# +#Permission is hereby granted, free of charge, to any person +#obtaining a copy of this software and associated documentation +#files (the "Software"), to deal in the Software without +#restriction, including without limitation the rights to use, +#copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the +#Software is furnished to do so, subject to the following +#conditions: +## +#The above copyright notice and this permission notice shall be +#included in all copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +#EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +#OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +#NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +#HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +#WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +#FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +#OTHER DEALINGS IN THE SOFTWARE. +# + + +if [[ -n "$JSDOCDIR" ]]; then + _DOCDIR="-Djsdoc.dir=$JSDOCDIR" + _APPDIR="$JSDOCDIR/app" + _BASEDIR="$JSDOCDIR" +else + _DOCDIR="" + _APPDIR="./app" + _BASEDIR="." +fi + +if [[ -n "$JSDOCTEMPLATEDIR" ]]; then + _TDIR="-Djsdoc.template.dir=$JSDOCTEMPLATEDIR" +else + _TDIR="" +fi + +CMD="java $_DOCDIR $_TDIR -jar $_BASEDIR/jsrun.jar $_APPDIR/run.js $@" +echo $CMD +$CMD + diff --git a/doc/jsdoc-toolkit/templates/jsdoc/allclasses.tmpl b/doc/jsdoc-toolkit/templates/jsdoc/allclasses.tmpl new file mode 100644 index 000000000..32f43582f --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/allclasses.tmpl @@ -0,0 +1,17 @@ +
{+new Link().toFile("index.html").withText("Class Index")+} +| {+new Link().toFile("files.html").withText("File Index")+}
+
+

Classes

+
    + +
  • {! + if (thisClass.alias == "_global_") { + output += ""+new Link().toClass(thisClass.alias)+""; + } + else { + output += new Link().toClass(thisClass.alias); + } + !}
  • +
    +
+
\ No newline at end of file diff --git a/doc/jsdoc-toolkit/templates/jsdoc/allfiles.tmpl b/doc/jsdoc-toolkit/templates/jsdoc/allfiles.tmpl new file mode 100644 index 000000000..4c7de1cd5 --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/allfiles.tmpl @@ -0,0 +1,56 @@ + + + + + {! Link.base = ""; /* all generated links will be relative to this */ !} + JsDoc Reference - File Index + + + + + + + {+include("static/header.html")+} + +
+ {+publish.classesIndex+} +
+ +
+

File Index

+ + +
+

{+new Link().toSrc(item.alias).withText(item.name)+}

+ {+resolveLinks(item.desc)+} +
+ +
Author:
+
{+item.author+}
+
+ +
Version:
+
{+item.version+}
+
+ {! var locations = item.comment.getTag('location').map(function($){return $.toString().replace(/(^\$ ?| ?\$$)/g, '').replace(/^HeadURL: https:/g, 'http:');}) !} + +
Location:
+ +
{+location+}
+
+
+
+
+
+
+ +
+
+ ©{+JSDOC.opt.D.copyright+}
+ Documentation generated by JsDoc Toolkit {+JSDOC.VERSION+} on {+new Date()+} +
+ + \ No newline at end of file diff --git a/doc/jsdoc-toolkit/templates/jsdoc/class.tmpl b/doc/jsdoc-toolkit/templates/jsdoc/class.tmpl new file mode 100644 index 000000000..181ed1188 --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/class.tmpl @@ -0,0 +1,649 @@ + + + + + + {! Link.base = "../"; /* all generated links will be relative to this */ !} + JsDoc Reference - {+data.alias+} + + + + + + + + {+include("static/header.html")+} + + + +
+ + {+publish.classesIndex+} + +
+ +
+ +

+ {! + var classType = ""; + + if (data.isBuiltin()) { + classType += "Built-In "; + } + + if (data.isNamespace) { + if (data.is('FUNCTION')) { + classType += "Function "; + } + classType += "Namespace "; + } + else { + classType += "Class "; + } + !} + {+classType+}{+data.alias+} +

+ + +

+
Version + {+ data.version +}.
+
+
Extends + {+ + data.augments + .sort() + .map( + function($) { return new Link().toSymbol($); } + ) + .join(", ") + +}.
+
+ + {+resolveLinks(data.classDesc)+} + + {# isn't defined in any file #} +
Defined in: {+new Link().toSrc(data.srcFile)+}. +
+

+ + + + + + + + + + + + + + + + + +
{+classType+}Summary
Constructor AttributesConstructor Name and Description
{! + if (data.isPrivate) output += "<private> "; + if (data.isInner) output += "<inner> "; + !}  +
+ {+ new Link().toSymbol(data.alias).inner('constructor')+}{+ makeSignature(data.params) +} +
+
{+resolveLinks(summarize(data.desc))+}
+
+
+ + + + {! var ownProperties = data.properties.filter(function($){return $.memberOf == data.alias && !$.isNamespace}).sort(makeSortby("name")); !} + + + + + + + + + + + + + + + + + +
Field Summary
Field AttributesField Name and Description
{! + if (member.isPrivate) output += "<private> "; + if (member.isInner) output += "<inner> "; + if (member.isStatic) output += "<static> "; + if (member.isConstant) output += "<constant> "; + !}  +
+ {+member.memberOf+}.{+new Link().toSymbol(member.alias).withText(member.name)+} +
+
{+resolveLinks(summarize(member.desc))+}
+
+
+ + +
+ {! + var borrowedMembers = data.properties.filter(function($) {return $.memberOf != data.alias}); + + var contributers = []; + borrowedMembers.map(function($) {if (contributers.indexOf($.memberOf) < 0) contributers.push($.memberOf)}); + for (var i = 0, l = contributers.length; i < l; i++) { + output += + "
Fields borrowed from class "+new Link().toSymbol(contributers[i])+":
" + + + "
" + + borrowedMembers + .filter( + function($) { return $.memberOf == contributers[i] } + ) + .sort(makeSortby("name")) + .map( + function($) { return new Link().toSymbol($.alias).withText($.name) } + ) + .join(", ") + + + "
"; + } + !} +
+
+
+ + + + {! var ownMethods = data.methods.filter(function($){return $.memberOf == data.alias && !$.isNamespace}).sort(makeSortby("name")); !} + + + + + + + + + + + + + + + + + +
Method Summary
Method AttributesMethod Name and Description
{! + if (member.isPrivate) output += "<private> "; + if (member.isInner) output += "<inner> "; + if (member.isStatic) output += "<static> "; + !}  +
{+member.memberOf+}.{+new Link().toSymbol(member.alias).withText(member.name.replace(/\^\d+$/, ''))+}{+makeSignature(member.params)+} +
+
{+resolveLinks(summarize(member.desc))+}
+
+
+ + +
+ {! + var borrowedMembers = data.methods.filter(function($) {return $.memberOf != data.alias}); + var contributers = []; + borrowedMembers.map(function($) {if (contributers.indexOf($.memberOf) < 0) contributers.push($.memberOf)}); + for (var i = 0, l = contributers.length; i < l; i++) { + output += + "
Methods borrowed from class "+new Link().toSymbol(contributers[i])+":
" + + + "
" + + borrowedMembers + .filter( + function($) { return $.memberOf == contributers[i] } + ) + .sort(makeSortby("name")) + .map( + function($) { return new Link().toSymbol($.alias).withText($.name) } + ) + .join(", ") + + + "
"; + } + + !} +
+
+
+ + + {! var ownEvents = data.events.filter(function($){return $.memberOf == data.alias && !$.isNamespace}).sort(makeSortby("name")); !} + + + + + + + + + + + + + + + + + +
Event Summary
Event AttributesEvent Name and Description
{! + if (member.isPrivate) output += "<private> "; + if (member.isInner) output += "<inner> "; + if (member.isStatic) output += "<static> "; + !}  +
{+member.memberOf+}.{+new Link().toSymbol(member.alias).withText(member.name)+}{+makeSignature(member.params)+} +
+
{+resolveLinks(summarize(member.desc))+}
+
+
+ + +
+ {! + var borrowedMembers = data.events.filter(function($) {return $.memberOf != data.alias}); + var contributers = []; + borrowedMembers.map(function($) {if (contributers.indexOf($.memberOf) < 0) contributers.push($.memberOf)}); + for (var i = 0, l = contributers.length; i < l; i++) { + output += + "
Events borrowed from class "+new Link().toSymbol(contributers[i])+":
" + + + "
" + + borrowedMembers + .filter( + function($) { return $.memberOf == contributers[i] } + ) + .sort(makeSortby("name")) + .map( + function($) { return new Link().toSymbol($.alias).withText($.name) } + ) + .join(", ") + + + "
"; + } + + !} +
+
+
+ + + +
+
+ {+classType+}Detail +
+ +
{! + if (data.isPrivate) output += "<private> "; + if (data.isInner) output += "<inner> "; + !} + {+ data.alias +}{+ makeSignature(data.params) +} +
+ +
+ {+resolveLinks(data.desc)+} +
Author: {+data.author+}.
+
+ + + +
{+example+}
+
+
+ + + +
+
Parameters:
+ +
+ {+((item.type)?""+("{"+(new Link().toSymbol(item.type)+"} ")) : "")+} {+item.name+} + Optional, Default: {+item.defaultValue+} +
+
{+resolveLinks(item.desc)+}
+
+
+
+ +
+
Deprecated:
+
+ {+resolveLinks(data.deprecated)+} +
+
+
+ +
+
Since:
+
{+ data.since +}
+
+
+ +
+
Throws:
+ +
+ {+((item.type)?"{"+(new Link().toSymbol(item.type))+"} " : "")+} {+item.name+} +
+
{+resolveLinks(item.desc)+}
+
+
+
+ +
+
Returns:
+ +
{+((item.type)?"{"+(new Link().toSymbol(item.type))+"} " : "")+}{+resolveLinks(item.desc)+}
+
+
+
+ +
+
Requires:
+ +
{+ resolveLinks(item) +}
+
+
+
+ +
+
See:
+ +
{+ new Link().toSymbol(item) +}
+
+
+
+ +
+
+ + + +
+ Field Detail +
+ + +
{! + if (member.isPrivate) output += "<private> "; + if (member.isInner) output += "<inner> "; + if (member.isStatic) output += "<static> "; + if (member.isConstant) output += "<constant> "; + !} + + {{+new Link().toSymbol(member.type)+}} + {+member.memberOf+}.{+member.name+} + +
+
+ {+resolveLinks(member.desc)+} + +
+ Defined in: {+new Link().toSrc(member.srcFile)+}. +
+
Author: {+member.author+}.
+
+ + + +
{+example+}
+
+
+ + +
+
Deprecated:
+
+ {+ resolveLinks(member.deprecated) +} +
+
+
+ +
+
Since:
+
{+ member.since +}
+
+
+ +
+
See:
+ +
{+ new Link().toSymbol(item) +}
+
+
+
+ +
+
Default Value:
+
+ {+resolveLinks(member.defaultValue)+} +
+
+
+ +
+
+
+ + + +
+ Method Detail +
+ + +
{! + if (member.isPrivate) output += "<private> "; + if (member.isInner) output += "<inner> "; + if (member.isStatic) output += "<static> "; + !} + + {{+new Link().toSymbol(member.type)+}} + {+member.memberOf+}.{+member.name.replace(/\^\d+$/, '')+}{+makeSignature(member.params)+} + +
+
+ {+resolveLinks(member.desc)+} + +
+ Defined in: {+new Link().toSrc(member.srcFile)+}. +
+
Author: {+member.author+}.
+
+ + + +
{+example+}
+
+
+ + +
+
Parameters:
+ +
+ {+((item.type)?"{"+(new Link().toSymbol(item.type))+"} " : "")+}{+item.name+} + Optional, Default: {+item.defaultValue+} +
+
{+resolveLinks(item.desc)+}
+
+
+
+ +
+
Deprecated:
+
+ {+ resolveLinks(member.deprecated) +} +
+
+
+ +
+
Since:
+
{+ member.since +}
+
+ +
+ +
+
Throws:
+ +
+ {+((item.type)?"{"+(new Link().toSymbol(item.type))+"} " : "")+} {+item.name+} +
+
{+resolveLinks(item.desc)+}
+
+
+
+ +
+
Returns:
+ +
{+((item.type)?"{"+(new Link().toSymbol(item.type))+"} " : "")+}{+resolveLinks(item.desc)+}
+
+
+
+ +
+
Requires:
+ +
{+ resolveLinks(item) +}
+
+
+
+ +
+
See:
+ +
{+ new Link().toSymbol(item) +}
+
+
+
+ +
+
+
+ + + +
+ Event Detail +
+ + +
{! + if (member.isPrivate) output += "<private> "; + if (member.isInner) output += "<inner> "; + if (member.isStatic) output += "<static> "; + !} + + {{+new Link().toSymbol(member.type)+}} + {+member.memberOf+}.{+member.name+}{+makeSignature(member.params)+} + +
+
+ {+resolveLinks(member.desc)+} + +
+ Defined in: {+new Link().toSrc(member.srcFile)+}. +
+
Author: {+member.author+}.
+
+ + + +
{+example+}
+
+
+ + +
+
Parameters:
+ +
+ {+((item.type)?"{"+(new Link().toSymbol(item.type))+"} " : "")+}{+item.name+} + Optional, Default: {+item.defaultValue+} +
+
{+ resolveLinks(item.desc) +}
+
+
+
+ +
+
Deprecated:
+
+ {+ resolveLinks(member.deprecated) +} +
+
+
+ +
+
Since:
+
{+ member.since +}
+
+ +
+ +
+
Throws:
+ +
+ {+((item.type)?"{"+(new Link().toSymbol(item.type))+"} " : "")+} {+item.name+} +
+
{+ resolveLinks(item.desc) +}
+
+
+
+ +
+
Returns:
+ +
{+((item.type)?"{"+(new Link().toSymbol(item.type))+"} " : "")+}{+resolveLinks(item.desc)+}
+
+
+
+ +
+
Requires:
+ +
{+ resolveLinks(item) +}
+
+
+
+ +
+
See:
+ +
{+ new Link().toSymbol(item) +}
+
+
+
+ +
+
+
+ +
+
+ + + +
+ ©{+JSDOC.opt.D.copyright+}
+ Documentation generated by JsDoc Toolkit {+JSDOC.VERSION+} on {+new Date()+} +
+ + diff --git a/doc/jsdoc-toolkit/templates/jsdoc/index.tmpl b/doc/jsdoc-toolkit/templates/jsdoc/index.tmpl new file mode 100644 index 000000000..a5dc1158e --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/index.tmpl @@ -0,0 +1,39 @@ + + + + + + JsDoc Reference - Index + + + + + + + {+include("static/header.html")+} + +
+ {+publish.classesIndex+} +
+ +
+

Class Index

+ + +
+

{+(new Link().toSymbol(thisClass.alias))+}

+ {+resolveLinks(summarize(thisClass.classDesc))+} +
+
+
+ +
+
+ ©{+JSDOC.opt.D.copyright+}
+ Documentation generated by JsDoc Toolkit {+JSDOC.VERSION+} on {+new Date()+} +
+ + \ No newline at end of file diff --git a/doc/jsdoc-toolkit/templates/jsdoc/publish.js b/doc/jsdoc-toolkit/templates/jsdoc/publish.js new file mode 100644 index 000000000..446c92b13 --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/publish.js @@ -0,0 +1,200 @@ +/** Called automatically by JsDoc Toolkit. */ +function publish(symbolSet) { + publish.conf = { // trailing slash expected for dirs + ext: ".html", + outDir: JSDOC.opt.d || SYS.pwd+"../out/jsdoc/", + templatesDir: JSDOC.opt.t || SYS.pwd+"../templates/jsdoc/", + symbolsDir: "symbols/", + srcDir: "symbols/src/" + }; + + // is source output is suppressed, just display the links to the source file + if (JSDOC.opt.s && defined(Link) && Link.prototype._makeSrcLink) { + Link.prototype._makeSrcLink = function(srcFilePath) { + return "<"+srcFilePath+">"; + } + } + + // create the folders and subfolders to hold the output + IO.mkPath((publish.conf.outDir+"symbols/src").split("/")); + + // used to allow Link to check the details of things being linked to + Link.symbolSet = symbolSet; + + // create the required templates + try { + var classTemplate = new JSDOC.JsPlate(publish.conf.templatesDir+"class.tmpl"); + var classesTemplate = new JSDOC.JsPlate(publish.conf.templatesDir+"allclasses.tmpl"); + } + catch(e) { + print("Couldn't create the required templates: "+e); + quit(); + } + + // some ustility filters + function hasNoParent($) {return ($.memberOf == "")} + function isaFile($) {return ($.is("FILE"))} + function isaClass($) {return ($.is("CONSTRUCTOR") || $.isNamespace)} + + // get an array version of the symbolset, useful for filtering + var symbols = symbolSet.toArray(); + + // create the hilited source code files + var files = JSDOC.opt.srcFiles; + for (var i = 0, l = files.length; i < l; i++) { + var file = files[i]; + var srcDir = publish.conf.outDir + "symbols/src/"; + makeSrcFile(file, srcDir); + } + + // get a list of all the classes in the symbolset + var classes = symbols.filter(isaClass).sort(makeSortby("alias")); + + // create a filemap in which outfiles must be to be named uniquely, ignoring case + if (JSDOC.opt.u) { + var filemapCounts = {}; + Link.filemap = {}; + for (var i = 0, l = classes.length; i < l; i++) { + var lcAlias = classes[i].alias.toLowerCase(); + + if (!filemapCounts[lcAlias]) filemapCounts[lcAlias] = 1; + else filemapCounts[lcAlias]++; + + Link.filemap[classes[i].alias] = + (filemapCounts[lcAlias] > 1)? + lcAlias+"_"+filemapCounts[lcAlias] : lcAlias; + } + } + + // create a class index, displayed in the left-hand column of every class page + Link.base = "../"; + publish.classesIndex = classesTemplate.process(classes); // kept in memory + + // create each of the class pages + for (var i = 0, l = classes.length; i < l; i++) { + var symbol = classes[i]; + + symbol.events = symbol.getEvents(); // 1 order matters + symbol.methods = symbol.getMethods(); // 2 + + var output = ""; + output = classTemplate.process(symbol); + + IO.saveFile(publish.conf.outDir+"symbols/", ((JSDOC.opt.u)? Link.filemap[symbol.alias] : symbol.alias) + publish.conf.ext, output); + } + + // regenerate the index with different relative links, used in the index pages + Link.base = ""; + publish.classesIndex = classesTemplate.process(classes); + + // create the class index page + try { + var classesindexTemplate = new JSDOC.JsPlate(publish.conf.templatesDir+"index.tmpl"); + } + catch(e) { print(e.message); quit(); } + + var classesIndex = classesindexTemplate.process(classes); + IO.saveFile(publish.conf.outDir, "index"+publish.conf.ext, classesIndex); + classesindexTemplate = classesIndex = classes = null; + + // create the file index page + try { + var fileindexTemplate = new JSDOC.JsPlate(publish.conf.templatesDir+"allfiles.tmpl"); + } + catch(e) { print(e.message); quit(); } + + var documentedFiles = symbols.filter(isaFile); // files that have file-level docs + var allFiles = []; // not all files have file-level docs, but we need to list every one + + for (var i = 0; i < files.length; i++) { + allFiles.push(new JSDOC.Symbol(files[i], [], "FILE", new JSDOC.DocComment("/** */"))); + } + + for (var i = 0; i < documentedFiles.length; i++) { + var offset = files.indexOf(documentedFiles[i].alias); + allFiles[offset] = documentedFiles[i]; + } + + allFiles = allFiles.sort(makeSortby("name")); + + // output the file index page + var filesIndex = fileindexTemplate.process(allFiles); + IO.saveFile(publish.conf.outDir, "files"+publish.conf.ext, filesIndex); + fileindexTemplate = filesIndex = files = null; +} + + +/** Just the first sentence (up to a full stop). Should not break on dotted variable names. */ +function summarize(desc) { + if (typeof desc != "undefined") + return desc.match(/([\w\W]+?\.)[^a-z0-9_$]/i)? RegExp.$1 : desc; +} + +/** Make a symbol sorter by some attribute. */ +function makeSortby(attribute) { + return function(a, b) { + if (a[attribute] != undefined && b[attribute] != undefined) { + a = a[attribute].toLowerCase(); + b = b[attribute].toLowerCase(); + if (a < b) return -1; + if (a > b) return 1; + return 0; + } + } +} + +/** Pull in the contents of an external file at the given path. */ +function include(path) { + var path = publish.conf.templatesDir+path; + return IO.readFile(path); +} + +/** Turn a raw source file into a code-hilited page in the docs. */ +function makeSrcFile(path, srcDir, name) { + if (JSDOC.opt.s) return; + + if (!name) { + name = path.replace(/\.\.?[\\\/]/g, "").replace(/[\\\/]/g, "_"); + name = name.replace(/\:/g, "_"); + } + + var src = {path: path, name:name, charset: IO.encoding, hilited: ""}; + + if (defined(JSDOC.PluginManager)) { + JSDOC.PluginManager.run("onPublishSrc", src); + } + + if (src.hilited) { + IO.saveFile(srcDir, name+publish.conf.ext, src.hilited); + } +} + +/** Build output for displaying function parameters. */ +function makeSignature(params) { + if (!params) return "()"; + var signature = "(" + + + params.filter( + function($) { + return $.name.indexOf(".") == -1; // don't show config params in signature + } + ).map( + function($) { + return $.name; + } + ).join(", ") + + + ")"; + return signature; +} + +/** Find symbol {@link ...} strings in text and turn into html links */ +function resolveLinks(str, from) { + str = str.replace(/\{@link ([^} ]+) ?\}/gi, + function(match, symbolName) { + return new Link().toSymbol(symbolName); + } + ); + + return str; +} diff --git a/doc/jsdoc-toolkit/templates/jsdoc/static/default.css b/doc/jsdoc-toolkit/templates/jsdoc/static/default.css new file mode 100644 index 000000000..97e021ef6 --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/static/default.css @@ -0,0 +1,162 @@ +/* default.css */ +body +{ + font: 12px "Lucida Grande", Tahoma, Arial, Helvetica, sans-serif; + width: 800px; +} + +.header +{ + clear: both; + background-color: #ccc; + padding: 8px; +} + +h1 +{ + font-size: 150%; + font-weight: bold; + padding: 0; + margin: 1em 0 0 .3em; +} + +hr +{ + border: none 0; + border-top: 1px solid #7F8FB1; + height: 1px; +} + +pre.code +{ + display: block; + padding: 8px; + border: 1px dashed #ccc; +} + +#index +{ + margin-top: 24px; + float: left; + width: 160px; + position: absolute; + left: 8px; + background-color: #F3F3F3; + padding: 8px; +} + +#content +{ + margin-left: 190px; + width: 600px; +} + +.classList +{ + list-style-type: none; + padding: 0; + margin: 0 0 0 8px; + font-family: arial, sans-serif; + font-size: 1em; + overflow: auto; +} + +.classList li +{ + padding: 0; + margin: 0 0 8px 0; +} + +.summaryTable { width: 100%; } + +h1.classTitle +{ + font-size:170%; + line-height:130%; +} + +h2 { font-size: 110%; } +caption, div.sectionTitle +{ + background-color: #7F8FB1; + color: #fff; + font-size:130%; + text-align: left; + padding: 2px 6px 2px 6px; + border: 1px #7F8FB1 solid; +} + +div.sectionTitle { margin-bottom: 8px; } +.summaryTable thead { display: none; } + +.summaryTable td +{ + vertical-align: top; + padding: 4px; + border-bottom: 1px #7F8FB1 solid; + border-right: 1px #7F8FB1 solid; +} + +/*col#summaryAttributes {}*/ +.summaryTable td.attributes +{ + border-left: 1px #7F8FB1 solid; + width: 140px; + text-align: right; +} + +td.attributes, .fixedFont +{ + line-height: 15px; + color: #002EBE; + font-family: "Courier New",Courier,monospace; + font-size: 13px; +} + +.summaryTable td.nameDescription +{ + text-align: left; + font-size: 13px; + line-height: 15px; +} + +.summaryTable td.nameDescription, .description +{ + line-height: 15px; + padding: 4px; + padding-left: 4px; +} + +.summaryTable { margin-bottom: 8px; } + +ul.inheritsList +{ + list-style: square; + margin-left: 20px; + padding-left: 0; +} + +.detailList { + margin-left: 20px; + line-height: 15px; +} +.detailList dt { margin-left: 20px; } + +.detailList .heading +{ + font-weight: bold; + padding-bottom: 6px; + margin-left: 0; +} + +.light, td.attributes, .light a:link, .light a:visited +{ + color: #777; + font-style: italic; +} + +.fineprint +{ + text-align: right; + font-size: 10px; +} \ No newline at end of file diff --git a/doc/jsdoc-toolkit/templates/jsdoc/static/header.html b/doc/jsdoc-toolkit/templates/jsdoc/static/header.html new file mode 100644 index 000000000..353b735a4 --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/static/header.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/doc/jsdoc-toolkit/templates/jsdoc/static/index.html b/doc/jsdoc-toolkit/templates/jsdoc/static/index.html new file mode 100644 index 000000000..661f6f67d --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/static/index.html @@ -0,0 +1,19 @@ + + + + + Generated Javascript Documentation + + + + + + <body> + <p> + This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. + </p> + </body> + + + \ No newline at end of file diff --git a/doc/jsdoc-toolkit/templates/jsdoc/symbol.tmpl b/doc/jsdoc-toolkit/templates/jsdoc/symbol.tmpl new file mode 100644 index 000000000..f8f4bd1f6 --- /dev/null +++ b/doc/jsdoc-toolkit/templates/jsdoc/symbol.tmpl @@ -0,0 +1,35 @@ + + {+data.name+} + {+data.memberOf+} + {+data.isStatic+} + {+data.isa+} + {+data.desc+} + {+data.classDesc+} + + + + {+method.name+} + {+method.memberOf+} + {+method.isStatic+} + {+method.desc+} + + + {+param.type+} + {+param.name+} + {+param.desc+} + {+param.defaultValue+} + + + + + + + + {+property.name+} + {+property.memberOf+} + {+property.isStatic+} + {+property.desc+} + {+property.type+} + + + diff --git a/doc/jsdoc/README b/doc/jsdoc/README new file mode 100644 index 000000000..9b7cc7534 --- /dev/null +++ b/doc/jsdoc/README @@ -0,0 +1 @@ +You can create the JSDocs with generateJsDoc.sh in the bin folder diff --git a/node/AttributePoolFactory.js b/node/AttributePoolFactory.js new file mode 100644 index 000000000..8d8a1b427 --- /dev/null +++ b/node/AttributePoolFactory.js @@ -0,0 +1,82 @@ +/** + * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka + * + * 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. + */ + +exports.createAttributePool = function () { + var p = {}; + p.numToAttrib = {}; // e.g. {0: ['foo','bar']} + p.attribToNum = {}; // e.g. {'foo,bar': 0} + p.nextNum = 0; + + p.putAttrib = function (attrib, dontAddIfAbsent) { + var str = String(attrib); + if (str in p.attribToNum) { + return p.attribToNum[str]; + } + if (dontAddIfAbsent) { + return -1; + } + var num = p.nextNum++; + p.attribToNum[str] = num; + p.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')]; + return num; + }; + + p.getAttrib = function (num) { + var pair = p.numToAttrib[num]; + if (!pair) { + return pair; + } + return [pair[0], pair[1]]; // return a mutable copy + }; + + p.getAttribKey = function (num) { + var pair = p.numToAttrib[num]; + if (!pair) return ''; + return pair[0]; + }; + + p.getAttribValue = function (num) { + var pair = p.numToAttrib[num]; + if (!pair) return ''; + return pair[1]; + }; + + p.eachAttrib = function (func) { + for (var n in p.numToAttrib) { + var pair = p.numToAttrib[n]; + func(pair[0], pair[1]); + } + }; + + p.toJsonable = function () { + return { + numToAttrib: p.numToAttrib, + nextNum: p.nextNum + }; + }; + + p.fromJsonable = function (obj) { + p.numToAttrib = obj.numToAttrib; + p.nextNum = obj.nextNum; + p.attribToNum = {}; + for (var n in p.numToAttrib) { + p.attribToNum[String(p.numToAttrib[n])] = Number(n); + } + return p; + }; + + return p; +} diff --git a/node/AuthorManager.js b/node/AuthorManager.js new file mode 100644 index 000000000..3e177291b --- /dev/null +++ b/node/AuthorManager.js @@ -0,0 +1,131 @@ +/** + * 2011 Peter 'Pita' Martischka + * + * 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. + */ + + +/** + * The AuthorManager controlls all information about the Pad authors + */ + +/** + * Saves all Authors as a assoative Array. The Key is the author id. + * Authors can have the following attributes: + * -name The Name of the Author as shown on the Pad + * -colorId The Id of Usercolor. A number between 0 and 31 + * -timestamp The timestamp on which the user was last seen + */ +var globalAuthors = {}; + +/** + * A easy key value pair. The Key is the token, the value is the authorid + */ +var token2author = {}; + +/** + * Returns the Author Id for a token. If the token is unkown, + * it creates a author for the token + * @param token The token + */ +exports.getAuthor4Token = function (token) +{ + var author; + + if(token2author[token] == null) + { + author = "g." + _randomString(16); + + while(globalAuthors[author] != null) + { + author = "g." + _randomString(16); + } + + token2author[token]=author; + + globalAuthors[author] = {}; + globalAuthors[author].colorId = Math.floor(Math.random()*32); + globalAuthors[author].name = null; + } + else + { + author = token2author[token]; + } + + globalAuthors[author].timestamp = new Date().getTime(); + + return author; +} + +/** + * Returns the color Id of the author + */ +exports.getAuthorColorId = function (author) +{ + throwExceptionIfAuthorNotExist(author); + + return globalAuthors[author].colorId; +} + +/** + * Sets the color Id of the author + */ +exports.setAuthorColorId = function (author, colorId) +{ + throwExceptionIfAuthorNotExist(author); + + globalAuthors[author].colorId = colorId; +} + +/** + * Returns the name of the author + */ +exports.getAuthorName = function (author) +{ + throwExceptionIfAuthorNotExist(author); + + return globalAuthors[author].name; +} + +/** + * Sets the name of the author + */ +exports.setAuthorName = function (author, name) +{ + throwExceptionIfAuthorNotExist(author); + + globalAuthors[author].name = name; +} + +/** + * A internal function that checks if the Author exist and throws a exception if not + */ +function throwExceptionIfAuthorNotExist(author) +{ + if(globalAuthors[author] == null) + { + throw "Author '" + author + "' is unkown!"; + } +} + +/** + * Generates a random String with the given length. Is needed to generate the Author Ids + */ +function _randomString(len) { + // use only numbers and lowercase letters + var pieces = []; + for(var i=0;i= ", oldLen, " in ", cs); + break; + case '+': + { + calcNewLen += o.chars; + numInserted += o.chars; + exports.assert(calcNewLen < newLen, calcNewLen, " >= ", newLen, " in ", cs); + break; + } + } + assem.append(o); + } + + calcNewLen += oldLen - oldPos; + charBank = charBank.substring(0, numInserted); + while (charBank.length < numInserted) { + charBank += "?"; + } + + assem.endDocument(); + var normalized = exports.pack(oldLen, calcNewLen, assem.toString(), charBank); + exports.assert(normalized == cs, normalized, ' != ', cs); + + return cs; +} + +exports.smartOpAssembler = function () { + // Like opAssembler but able to produce conforming exportss + // from slightly looser input, at the cost of speed. + // Specifically: + // - merges consecutive operations that can be merged + // - strips final "=" + // - ignores 0-length changes + // - reorders consecutive + and - (which margingOpAssembler doesn't do) + var minusAssem = exports.mergingOpAssembler(); + var plusAssem = exports.mergingOpAssembler(); + var keepAssem = exports.mergingOpAssembler(); + var assem = exports.stringAssembler(); + var lastOpcode = ''; + var lengthChange = 0; + + function flushKeeps() { + assem.append(keepAssem.toString()); + keepAssem.clear(); + } + + function flushPlusMinus() { + assem.append(minusAssem.toString()); + minusAssem.clear(); + assem.append(plusAssem.toString()); + plusAssem.clear(); + } + + function append(op) { + if (!op.opcode) return; + if (!op.chars) return; + + if (op.opcode == '-') { + if (lastOpcode == '=') { + flushKeeps(); + } + minusAssem.append(op); + lengthChange -= op.chars; + } else if (op.opcode == '+') { + if (lastOpcode == '=') { + flushKeeps(); + } + plusAssem.append(op); + lengthChange += op.chars; + } else if (op.opcode == '=') { + if (lastOpcode != '=') { + flushPlusMinus(); + } + keepAssem.append(op); + } + lastOpcode = op.opcode; + } + + function appendOpWithText(opcode, text, attribs, pool) { + var op = exports.newOp(opcode); + op.attribs = exports.makeAttribsString(opcode, attribs, pool); + var lastNewlinePos = text.lastIndexOf('\n'); + if (lastNewlinePos < 0) { + op.chars = text.length; + op.lines = 0; + append(op); + } else { + op.chars = lastNewlinePos + 1; + op.lines = text.match(/\n/g).length; + append(op); + op.chars = text.length - (lastNewlinePos + 1); + op.lines = 0; + append(op); + } + } + + function toString() { + flushPlusMinus(); + flushKeeps(); + return assem.toString(); + } + + function clear() { + minusAssem.clear(); + plusAssem.clear(); + keepAssem.clear(); + assem.clear(); + lengthChange = 0; + } + + function endDocument() { + keepAssem.endDocument(); + } + + function getLengthChange() { + return lengthChange; + } + + return { + append: append, + toString: toString, + clear: clear, + endDocument: endDocument, + appendOpWithText: appendOpWithText, + getLengthChange: getLengthChange + }; +}; + +if (_opt) { + exports.mergingOpAssembler = function () { + var assem = _opt.mergingOpAssembler(); + + function append(op) { + assem.append(op.opcode, op.chars, op.lines, op.attribs); + } + + function toString() { + return assem.toString(); + } + + function clear() { + assem.clear(); + } + + function endDocument() { + assem.endDocument(); + } + + return { + append: append, + toString: toString, + clear: clear, + endDocument: endDocument + }; + }; +} else { + exports.mergingOpAssembler = function () { + // This assembler can be used in production; it efficiently + // merges consecutive operations that are mergeable, ignores + // no-ops, and drops final pure "keeps". It does not re-order + // operations. + var assem = exports.opAssembler(); + var bufOp = exports.newOp(); + + // If we get, for example, insertions [xxx\n,yyy], those don't merge, + // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n]. + // This variable stores the length of yyy and any other newline-less + // ops immediately after it. + var bufOpAdditionalCharsAfterNewline = 0; + + function flush(isEndDocument) { + if (bufOp.opcode) { + if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs) { + // final merged keep, leave it implicit + } else { + assem.append(bufOp); + if (bufOpAdditionalCharsAfterNewline) { + bufOp.chars = bufOpAdditionalCharsAfterNewline; + bufOp.lines = 0; + assem.append(bufOp); + bufOpAdditionalCharsAfterNewline = 0; + } + } + bufOp.opcode = ''; + } + } + + function append(op) { + if (op.chars > 0) { + if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) { + if (op.lines > 0) { + // bufOp and additional chars are all mergeable into a multi-line op + bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars; + bufOp.lines += op.lines; + bufOpAdditionalCharsAfterNewline = 0; + } else if (bufOp.lines == 0) { + // both bufOp and op are in-line + bufOp.chars += op.chars; + } else { + // append in-line text to multi-line bufOp + bufOpAdditionalCharsAfterNewline += op.chars; + } + } else { + flush(); + exports.copyOp(op, bufOp); + } + } + } + + function endDocument() { + flush(true); + } + + function toString() { + flush(); + return assem.toString(); + } + + function clear() { + assem.clear(); + exports.clearOp(bufOp); + } + return { + append: append, + toString: toString, + clear: clear, + endDocument: endDocument + }; + }; +} + +if (_opt) { + exports.opAssembler = function () { + var assem = _opt.opAssembler(); + // this function allows op to be mutated later (doesn't keep a ref) + + function append(op) { + assem.append(op.opcode, op.chars, op.lines, op.attribs); + } + + function toString() { + return assem.toString(); + } + + function clear() { + assem.clear(); + } + return { + append: append, + toString: toString, + clear: clear + }; + }; +} else { + exports.opAssembler = function () { + var pieces = []; + // this function allows op to be mutated later (doesn't keep a ref) + + function append(op) { + pieces.push(op.attribs); + if (op.lines) { + pieces.push('|', exports.numToString(op.lines)); + } + pieces.push(op.opcode); + pieces.push(exports.numToString(op.chars)); + } + + function toString() { + return pieces.join(''); + } + + function clear() { + pieces.length = 0; + } + return { + append: append, + toString: toString, + clear: clear + }; + }; +} + +exports.stringIterator = function (str) { + var curIndex = 0; + + function assertRemaining(n) { + exports.assert(n <= remaining(), "!(", n, " <= ", remaining(), ")"); + } + + function take(n) { + assertRemaining(n); + var s = str.substr(curIndex, n); + curIndex += n; + return s; + } + + function peek(n) { + assertRemaining(n); + var s = str.substr(curIndex, n); + return s; + } + + function skip(n) { + assertRemaining(n); + curIndex += n; + } + + function remaining() { + return str.length - curIndex; + } + return { + take: take, + skip: skip, + remaining: remaining, + peek: peek + }; +}; + +exports.stringAssembler = function () { + var pieces = []; + + function append(x) { + pieces.push(String(x)); + } + + function toString() { + return pieces.join(''); + } + return { + append: append, + toString: toString + }; +}; + +// "lines" need not be an array as long as it supports certain calls (lines_foo inside). +exports.textLinesMutator = function (lines) { + // Mutates lines, an array of strings, in place. + // Mutation operations have the same constraints as exports operations + // with respect to newlines, but not the other additional constraints + // (i.e. ins/del ordering, forbidden no-ops, non-mergeability, final newline). + // Can be used to mutate lists of strings where the last char of each string + // is not actually a newline, but for the purposes of N and L values, + // the caller should pretend it is, and for things to work right in that case, the input + // to insert() should be a single line with no newlines. + var curSplice = [0, 0]; + var inSplice = false; + // position in document after curSplice is applied: + var curLine = 0, + curCol = 0; + // invariant: if (inSplice) then (curLine is in curSplice[0] + curSplice.length - {2,3}) && + // curLine >= curSplice[0] + // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then + // curCol == 0 + + function lines_applySplice(s) { + lines.splice.apply(lines, s); + } + + function lines_toSource() { + return lines.toSource(); + } + + function lines_get(idx) { + if (lines.get) { + return lines.get(idx); + } else { + return lines[idx]; + } + } + // can be unimplemented if removeLines's return value not needed + + function lines_slice(start, end) { + if (lines.slice) { + return lines.slice(start, end); + } else { + return []; + } + } + + function lines_length() { + if ((typeof lines.length) == "number") { + return lines.length; + } else { + return lines.length(); + } + } + + function enterSplice() { + curSplice[0] = curLine; + curSplice[1] = 0; + if (curCol > 0) { + putCurLineInSplice(); + } + inSplice = true; + } + + function leaveSplice() { + lines_applySplice(curSplice); + curSplice.length = 2; + curSplice[0] = curSplice[1] = 0; + inSplice = false; + } + + function isCurLineInSplice() { + return (curLine - curSplice[0] < (curSplice.length - 2)); + } + + function debugPrint(typ) { + print(typ + ": " + curSplice.toSource() + " / " + curLine + "," + curCol + " / " + lines_toSource()); + } + + function putCurLineInSplice() { + if (!isCurLineInSplice()) { + curSplice.push(lines_get(curSplice[0] + curSplice[1])); + curSplice[1]++; + } + return 2 + curLine - curSplice[0]; + } + + function skipLines(L, includeInSplice) { + if (L) { + if (includeInSplice) { + if (!inSplice) { + enterSplice(); + } + for (var i = 0; i < L; i++) { + curCol = 0; + putCurLineInSplice(); + curLine++; + } + } else { + if (inSplice) { + if (L > 1) { + leaveSplice(); + } else { + putCurLineInSplice(); + } + } + curLine += L; + curCol = 0; + } + //print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length); +/*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) { + print("BLAH"); + putCurLineInSplice(); + }*/ + // tests case foo in remove(), which isn't otherwise covered in current impl + } + //debugPrint("skip"); + } + + function skip(N, L, includeInSplice) { + if (N) { + if (L) { + skipLines(L, includeInSplice); + } else { + if (includeInSplice && !inSplice) { + enterSplice(); + } + if (inSplice) { + putCurLineInSplice(); + } + curCol += N; + //debugPrint("skip"); + } + } + } + + function removeLines(L) { + var removed = ''; + if (L) { + if (!inSplice) { + enterSplice(); + } + + function nextKLinesText(k) { + var m = curSplice[0] + curSplice[1]; + return lines_slice(m, m + k).join(''); + } + if (isCurLineInSplice()) { + //print(curCol); + if (curCol == 0) { + removed = curSplice[curSplice.length - 1]; + // print("FOO"); // case foo + curSplice.length--; + removed += nextKLinesText(L - 1); + curSplice[1] += L - 1; + } else { + removed = nextKLinesText(L - 1); + curSplice[1] += L - 1; + var sline = curSplice.length - 1; + removed = curSplice[sline].substring(curCol) + removed; + curSplice[sline] = curSplice[sline].substring(0, curCol) + lines_get(curSplice[0] + curSplice[1]); + curSplice[1] += 1; + } + } else { + removed = nextKLinesText(L); + curSplice[1] += L; + } + //debugPrint("remove"); + } + return removed; + } + + function remove(N, L) { + var removed = ''; + if (N) { + if (L) { + return removeLines(L); + } else { + if (!inSplice) { + enterSplice(); + } + var sline = putCurLineInSplice(); + removed = curSplice[sline].substring(curCol, curCol + N); + curSplice[sline] = curSplice[sline].substring(0, curCol) + curSplice[sline].substring(curCol + N); + //debugPrint("remove"); + } + } + return removed; + } + + function insert(text, L) { + if (text) { + if (!inSplice) { + enterSplice(); + } + if (L) { + var newLines = exports.splitTextLines(text); + if (isCurLineInSplice()) { + //if (curCol == 0) { + //curSplice.length--; + //curSplice[1]--; + //Array.prototype.push.apply(curSplice, newLines); + //curLine += newLines.length; + //} + //else { + var sline = curSplice.length - 1; + var theLine = curSplice[sline]; + var lineCol = curCol; + curSplice[sline] = theLine.substring(0, lineCol) + newLines[0]; + curLine++; + newLines.splice(0, 1); + Array.prototype.push.apply(curSplice, newLines); + curLine += newLines.length; + curSplice.push(theLine.substring(lineCol)); + curCol = 0; + //} + } else { + Array.prototype.push.apply(curSplice, newLines); + curLine += newLines.length; + } + } else { + var sline = putCurLineInSplice(); + curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol); + curCol += text.length; + } + //debugPrint("insert"); + } + } + + function hasMore() { + //print(lines.length+" / "+inSplice+" / "+(curSplice.length - 2)+" / "+curSplice[1]); + var docLines = lines_length(); + if (inSplice) { + docLines += curSplice.length - 2 - curSplice[1]; + } + return curLine < docLines; + } + + function close() { + if (inSplice) { + leaveSplice(); + } + //debugPrint("close"); + } + + var self = { + skip: skip, + remove: remove, + insert: insert, + close: close, + hasMore: hasMore, + removeLines: removeLines, + skipLines: skipLines + }; + return self; +}; + +exports.applyZip = function (in1, idx1, in2, idx2, func) { + var iter1 = exports.opIterator(in1, idx1); + var iter2 = exports.opIterator(in2, idx2); + var assem = exports.smartOpAssembler(); + var op1 = exports.newOp(); + var op2 = exports.newOp(); + var opOut = exports.newOp(); + while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) { + if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1); + if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2); + func(op1, op2, opOut); + if (opOut.opcode) { + //print(opOut.toSource()); + assem.append(opOut); + opOut.opcode = ''; + } + } + assem.endDocument(); + return assem.toString(); +}; + +exports.unpack = function (cs) { + var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/; + var headerMatch = headerRegex.exec(cs); + if ((!headerMatch) || (!headerMatch[0])) { + exports.error("Not a exports: " + cs); + } + var oldLen = exports.parseNum(headerMatch[1]); + var changeSign = (headerMatch[2] == '>') ? 1 : -1; + var changeMag = exports.parseNum(headerMatch[3]); + var newLen = oldLen + changeSign * changeMag; + var opsStart = headerMatch[0].length; + var opsEnd = cs.indexOf("$"); + if (opsEnd < 0) opsEnd = cs.length; + return { + oldLen: oldLen, + newLen: newLen, + ops: cs.substring(opsStart, opsEnd), + charBank: cs.substring(opsEnd + 1) + }; +}; + +exports.pack = function (oldLen, newLen, opsStr, bank) { + var lenDiff = newLen - oldLen; + var lenDiffStr = (lenDiff >= 0 ? '>' + exports.numToString(lenDiff) : '<' + exports.numToString(-lenDiff)); + var a = []; + a.push('Z:', exports.numToString(oldLen), lenDiffStr, opsStr, '$', bank); + return a.join(''); +}; + +exports.applyToText = function (cs, str) { + var unpacked = exports.unpack(cs); + exports.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen); + var csIter = exports.opIterator(unpacked.ops); + var bankIter = exports.stringIterator(unpacked.charBank); + var strIter = exports.stringIterator(str); + var assem = exports.stringAssembler(); + while (csIter.hasNext()) { + var op = csIter.next(); + switch (op.opcode) { + case '+': + assem.append(bankIter.take(op.chars)); + break; + case '-': + strIter.skip(op.chars); + break; + case '=': + assem.append(strIter.take(op.chars)); + break; + } + } + assem.append(strIter.take(strIter.remaining())); + return assem.toString(); +}; + +exports.mutateTextLines = function (cs, lines) { + var unpacked = exports.unpack(cs); + var csIter = exports.opIterator(unpacked.ops); + var bankIter = exports.stringIterator(unpacked.charBank); + var mut = exports.textLinesMutator(lines); + while (csIter.hasNext()) { + var op = csIter.next(); + switch (op.opcode) { + case '+': + mut.insert(bankIter.take(op.chars), op.lines); + break; + case '-': + mut.remove(op.chars, op.lines); + break; + case '=': + mut.skip(op.chars, op.lines, ( !! op.attribs)); + break; + } + } + mut.close(); +}; + +exports.composeAttributes = function (att1, att2, resultIsMutation, pool) { + // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean. + // Sometimes attribute (key,value) pairs are treated as attribute presence + // information, while other times they are treated as operations that + // mutate a set of attributes, and this affects whether an empty value + // is a deletion or a change. + // Examples, of the form (att1Items, att2Items, resultIsMutation) -> result + // ([], [(bold, )], true) -> [(bold, )] + // ([], [(bold, )], false) -> [] + // ([], [(bold, true)], true) -> [(bold, true)] + // ([], [(bold, true)], false) -> [(bold, true)] + // ([(bold, true)], [(bold, )], true) -> [(bold, )] + // ([(bold, true)], [(bold, )], false) -> [] + // pool can be null if att2 has no attributes. + if ((!att1) && resultIsMutation) { + // In the case of a mutation (i.e. composing two exportss), + // an att2 composed with an empy att1 is just att2. If att1 + // is part of an attribution string, then att2 may remove + // attributes that are already gone, so don't do this optimization. + return att2; + } + if (!att2) return att1; + var atts = []; + att1.replace(/\*([0-9a-z]+)/g, function (_, a) { + atts.push(pool.getAttrib(exports.parseNum(a))); + return ''; + }); + att2.replace(/\*([0-9a-z]+)/g, function (_, a) { + var pair = pool.getAttrib(exports.parseNum(a)); + var found = false; + for (var i = 0; i < atts.length; i++) { + var oldPair = atts[i]; + if (oldPair[0] == pair[0]) { + if (pair[1] || resultIsMutation) { + oldPair[1] = pair[1]; + } else { + atts.splice(i, 1); + } + found = true; + break; + } + } + if ((!found) && (pair[1] || resultIsMutation)) { + atts.push(pair); + } + return ''; + }); + atts.sort(); + var buf = exports.stringAssembler(); + for (var i = 0; i < atts.length; i++) { + buf.append('*'); + buf.append(exports.numToString(pool.putAttrib(atts[i]))); + } + //print(att1+" / "+att2+" / "+buf.toString()); + return buf.toString(); +}; + +exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { + // attOp is the op from the sequence that is being operated on, either an + // attribution string or the earlier of two exportss being composed. + // pool can be null if definitely not needed. + //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); + if (attOp.opcode == '-') { + exports.copyOp(attOp, opOut); + attOp.opcode = ''; + } else if (!attOp.opcode) { + exports.copyOp(csOp, opOut); + csOp.opcode = ''; + } else { + switch (csOp.opcode) { + case '-': + { + if (csOp.chars <= attOp.chars) { + // delete or delete part + if (attOp.opcode == '=') { + opOut.opcode = '-'; + opOut.chars = csOp.chars; + opOut.lines = csOp.lines; + opOut.attribs = ''; + } + attOp.chars -= csOp.chars; + attOp.lines -= csOp.lines; + csOp.opcode = ''; + if (!attOp.chars) { + attOp.opcode = ''; + } + } else { + // delete and keep going + if (attOp.opcode == '=') { + opOut.opcode = '-'; + opOut.chars = attOp.chars; + opOut.lines = attOp.lines; + opOut.attribs = ''; + } + csOp.chars -= attOp.chars; + csOp.lines -= attOp.lines; + attOp.opcode = ''; + } + break; + } + case '+': + { + // insert + exports.copyOp(csOp, opOut); + csOp.opcode = ''; + break; + } + case '=': + { + if (csOp.chars <= attOp.chars) { + // keep or keep part + opOut.opcode = attOp.opcode; + opOut.chars = csOp.chars; + opOut.lines = csOp.lines; + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + csOp.opcode = ''; + attOp.chars -= csOp.chars; + attOp.lines -= csOp.lines; + if (!attOp.chars) { + attOp.opcode = ''; + } + } else { + // keep and keep going + opOut.opcode = attOp.opcode; + opOut.chars = attOp.chars; + opOut.lines = attOp.lines; + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + attOp.opcode = ''; + csOp.chars -= attOp.chars; + csOp.lines -= attOp.lines; + } + break; + } + case '': + { + exports.copyOp(attOp, opOut); + attOp.opcode = ''; + break; + } + } + } +}; + +exports.applyToAttribution = function (cs, astr, pool) { + var unpacked = exports.unpack(cs); + + return exports.applyZip(astr, 0, unpacked.ops, 0, function (op1, op2, opOut) { + return exports._slicerZipperFunc(op1, op2, opOut, pool); + }); +}; + +/*exports.oneInsertedLineAtATimeOpIterator = function(opsStr, optStartIndex, charBank) { + var iter = exports.opIterator(opsStr, optStartIndex); + var bankIndex = 0; + +};*/ + +exports.mutateAttributionLines = function (cs, lines, pool) { + //dmesg(cs); + //dmesg(lines.toSource()+" ->"); + var unpacked = exports.unpack(cs); + var csIter = exports.opIterator(unpacked.ops); + var csBank = unpacked.charBank; + var csBankIndex = 0; + // treat the attribution lines as text lines, mutating a line at a time + var mut = exports.textLinesMutator(lines); + + var lineIter = null; + + function isNextMutOp() { + return (lineIter && lineIter.hasNext()) || mut.hasMore(); + } + + function nextMutOp(destOp) { + if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) { + var line = mut.removeLines(1); + lineIter = exports.opIterator(line); + } + if (lineIter && lineIter.hasNext()) { + lineIter.next(destOp); + } else { + destOp.opcode = ''; + } + } + var lineAssem = null; + + function outputMutOp(op) { + //print("outputMutOp: "+op.toSource()); + if (!lineAssem) { + lineAssem = exports.mergingOpAssembler(); + } + lineAssem.append(op); + if (op.lines > 0) { + exports.assert(op.lines == 1, "Can't have op.lines of ", op.lines, " in attribution lines"); + // ship it to the mut + mut.insert(lineAssem.toString(), 1); + lineAssem = null; + } + } + + var csOp = exports.newOp(); + var attOp = exports.newOp(); + var opOut = exports.newOp(); + while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) { + if ((!csOp.opcode) && csIter.hasNext()) { + csIter.next(csOp); + } + //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); + //print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+"/"+lineIter+"/"+(lineIter?lineIter.hasNext():null)); + //print("csOp: "+csOp.toSource()); + if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { + break; // done + } else if (csOp.opcode == '=' && csOp.lines > 0 && (!csOp.attribs) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { + // skip multiple lines; this is what makes small changes not order of the document size + mut.skipLines(csOp.lines); + //print("skipped: "+csOp.lines); + csOp.opcode = ''; + } else if (csOp.opcode == '+') { + if (csOp.lines > 1) { + var firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex; + exports.copyOp(csOp, opOut); + csOp.chars -= firstLineLen; + csOp.lines--; + opOut.lines = 1; + opOut.chars = firstLineLen; + } else { + exports.copyOp(csOp, opOut); + csOp.opcode = ''; + } + outputMutOp(opOut); + csBankIndex += opOut.chars; + opOut.opcode = ''; + } else { + if ((!attOp.opcode) && isNextMutOp()) { + nextMutOp(attOp); + } + //print("attOp: "+attOp.toSource()); + exports._slicerZipperFunc(attOp, csOp, opOut, pool); + if (opOut.opcode) { + outputMutOp(opOut); + opOut.opcode = ''; + } + } + } + + exports.assert(!lineAssem, "line assembler not finished"); + mut.close(); + + //dmesg("-> "+lines.toSource()); +}; + +exports.joinAttributionLines = function (theAlines) { + var assem = exports.mergingOpAssembler(); + for (var i = 0; i < theAlines.length; i++) { + var aline = theAlines[i]; + var iter = exports.opIterator(aline); + while (iter.hasNext()) { + assem.append(iter.next()); + } + } + return assem.toString(); +}; + +exports.splitAttributionLines = function (attrOps, text) { + var iter = exports.opIterator(attrOps); + var assem = exports.mergingOpAssembler(); + var lines = []; + var pos = 0; + + function appendOp(op) { + assem.append(op); + if (op.lines > 0) { + lines.push(assem.toString()); + assem.clear(); + } + pos += op.chars; + } + + while (iter.hasNext()) { + var op = iter.next(); + var numChars = op.chars; + var numLines = op.lines; + while (numLines > 1) { + var newlineEnd = text.indexOf('\n', pos) + 1; + exports.assert(newlineEnd > 0, "newlineEnd <= 0 in splitAttributionLines"); + op.chars = newlineEnd - pos; + op.lines = 1; + appendOp(op); + numChars -= op.chars; + numLines -= op.lines; + } + if (numLines == 1) { + op.chars = numChars; + op.lines = 1; + } + appendOp(op); + } + + return lines; +}; + +exports.splitTextLines = function (text) { + return text.match(/[^\n]*(?:\n|[^\n]$)/g); +}; + +exports.compose = function (cs1, cs2, pool) { + var unpacked1 = exports.unpack(cs1); + var unpacked2 = exports.unpack(cs2); + var len1 = unpacked1.oldLen; + var len2 = unpacked1.newLen; + exports.assert(len2 == unpacked2.oldLen, "mismatched composition"); + var len3 = unpacked2.newLen; + var bankIter1 = exports.stringIterator(unpacked1.charBank); + var bankIter2 = exports.stringIterator(unpacked2.charBank); + var bankAssem = exports.stringAssembler(); + + var newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) { + //var debugBuilder = exports.stringAssembler(); + //debugBuilder.append(exports.opString(op1)); + //debugBuilder.append(','); + //debugBuilder.append(exports.opString(op2)); + //debugBuilder.append(' / '); + var op1code = op1.opcode; + var op2code = op2.opcode; + if (op1code == '+' && op2code == '-') { + bankIter1.skip(Math.min(op1.chars, op2.chars)); + } + exports._slicerZipperFunc(op1, op2, opOut, pool); + if (opOut.opcode == '+') { + if (op2code == '+') { + bankAssem.append(bankIter2.take(opOut.chars)); + } else { + bankAssem.append(bankIter1.take(opOut.chars)); + } + } + + //debugBuilder.append(exports.opString(op1)); + //debugBuilder.append(','); + //debugBuilder.append(exports.opString(op2)); + //debugBuilder.append(' -> '); + //debugBuilder.append(exports.opString(opOut)); + //print(debugBuilder.toString()); + }); + + return exports.pack(len1, len3, newOps, bankAssem.toString()); +}; + +exports.attributeTester = function (attribPair, pool) { + // returns a function that tests if a string of attributes + // (e.g. *3*4) contains a given attribute key,value that + // is already present in the pool. + if (!pool) { + return never; + } + var attribNum = pool.putAttrib(attribPair, true); + if (attribNum < 0) { + return never; + } else { + var re = new RegExp('\\*' + exports.numToString(attribNum) + '(?!\\w)'); + return function (attribs) { + return re.test(attribs); + }; + } + + function never(attribs) { + return false; + } +}; + +exports.identity = function (N) { + return exports.pack(N, N, "", ""); +}; + +exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) { + var oldLen = oldFullText.length; + + if (spliceStart >= oldLen) { + spliceStart = oldLen - 1; + } + if (numRemoved > oldFullText.length - spliceStart - 1) { + numRemoved = oldFullText.length - spliceStart - 1; + } + var oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved); + var newLen = oldLen + newText.length - oldText.length; + + var assem = exports.smartOpAssembler(); + assem.appendOpWithText('=', oldFullText.substring(0, spliceStart)); + assem.appendOpWithText('-', oldText); + assem.appendOpWithText('+', newText, optNewTextAPairs, pool); + assem.endDocument(); + return exports.pack(oldLen, newLen, assem.toString(), newText); +}; + +exports.toSplices = function (cs) { + // get a list of splices, [startChar, endChar, newText] + var unpacked = exports.unpack(cs); + var splices = []; + + var oldPos = 0; + var iter = exports.opIterator(unpacked.ops); + var charIter = exports.stringIterator(unpacked.charBank); + var inSplice = false; + while (iter.hasNext()) { + var op = iter.next(); + if (op.opcode == '=') { + oldPos += op.chars; + inSplice = false; + } else { + if (!inSplice) { + splices.push([oldPos, oldPos, ""]); + inSplice = true; + } + if (op.opcode == '-') { + oldPos += op.chars; + splices[splices.length - 1][1] += op.chars; + } else if (op.opcode == '+') { + splices[splices.length - 1][2] += charIter.take(op.chars); + } + } + } + + return splices; +}; + +exports.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter) { + var newStartChar = startChar; + var newEndChar = endChar; + var splices = exports.toSplices(cs); + var lengthChangeSoFar = 0; + for (var i = 0; i < splices.length; i++) { + var splice = splices[i]; + var spliceStart = splice[0] + lengthChangeSoFar; + var spliceEnd = splice[1] + lengthChangeSoFar; + var newTextLength = splice[2].length; + var thisLengthChange = newTextLength - (spliceEnd - spliceStart); + + if (spliceStart <= newStartChar && spliceEnd >= newEndChar) { + // splice fully replaces/deletes range + // (also case that handles insertion at a collapsed selection) + if (insertionsAfter) { + newStartChar = newEndChar = spliceStart; + } else { + newStartChar = newEndChar = spliceStart + newTextLength; + } + } else if (spliceEnd <= newStartChar) { + // splice is before range + newStartChar += thisLengthChange; + newEndChar += thisLengthChange; + } else if (spliceStart >= newEndChar) { + // splice is after range + } else if (spliceStart >= newStartChar && spliceEnd <= newEndChar) { + // splice is inside range + newEndChar += thisLengthChange; + } else if (spliceEnd < newEndChar) { + // splice overlaps beginning of range + newStartChar = spliceStart + newTextLength; + newEndChar += thisLengthChange; + } else { + // splice overlaps end of range + newEndChar = spliceStart; + } + + lengthChangeSoFar += thisLengthChange; + } + + return [newStartChar, newEndChar]; +}; + +exports.moveOpsToNewPool = function (cs, oldPool, newPool) { + // works on exports or attribution string + var dollarPos = cs.indexOf('$'); + if (dollarPos < 0) { + dollarPos = cs.length; + } + var upToDollar = cs.substring(0, dollarPos); + var fromDollar = cs.substring(dollarPos); + // order of attribs stays the same + return upToDollar.replace(/\*([0-9a-z]+)/g, function (_, a) { + var oldNum = exports.parseNum(a); + var pair = oldPool.getAttrib(oldNum); + var newNum = newPool.putAttrib(pair); + return '*' + exports.numToString(newNum); + }) + fromDollar; +}; + +exports.makeAttribution = function (text) { + var assem = exports.smartOpAssembler(); + assem.appendOpWithText('+', text); + return assem.toString(); +}; + +// callable on a exports, attribution string, or attribs property of an op +exports.eachAttribNumber = function (cs, func) { + var dollarPos = cs.indexOf('$'); + if (dollarPos < 0) { + dollarPos = cs.length; + } + var upToDollar = cs.substring(0, dollarPos); + + upToDollar.replace(/\*([0-9a-z]+)/g, function (_, a) { + func(exports.parseNum(a)); + return ''; + }); +}; + +// callable on a exports, attribution string, or attribs property of an op, +// though it may easily create adjacent ops that can be merged. +exports.filterAttribNumbers = function (cs, filter) { + return exports.mapAttribNumbers(cs, filter); +}; + +exports.mapAttribNumbers = function (cs, func) { + var dollarPos = cs.indexOf('$'); + if (dollarPos < 0) { + dollarPos = cs.length; + } + var upToDollar = cs.substring(0, dollarPos); + + var newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, function (s, a) { + var n = func(exports.parseNum(a)); + if (n === true) { + return s; + } else if ((typeof n) === "number") { + return '*' + exports.numToString(n); + } else { + return ''; + } + }); + + return newUpToDollar + cs.substring(dollarPos); +}; + +exports.makeAText = function (text, attribs) { + return { + text: text, + attribs: (attribs || exports.makeAttribution(text)) + }; +}; + +exports.applyToAText = function (cs, atext, pool) { + return { + text: exports.applyToText(cs, atext.text), + attribs: exports.applyToAttribution(cs, atext.attribs, pool) + }; +}; + +exports.cloneAText = function (atext) { + return { + text: atext.text, + attribs: atext.attribs + }; +}; + +exports.copyAText = function (atext1, atext2) { + atext2.text = atext1.text; + atext2.attribs = atext1.attribs; +}; + +exports.appendATextToAssembler = function (atext, assem) { + // intentionally skips last newline char of atext + var iter = exports.opIterator(atext.attribs); + var op = exports.newOp(); + while (iter.hasNext()) { + iter.next(op); + if (!iter.hasNext()) { + // last op, exclude final newline + if (op.lines <= 1) { + op.lines = 0; + op.chars--; + if (op.chars) { + assem.append(op); + } + } else { + var nextToLastNewlineEnd = + atext.text.lastIndexOf('\n', atext.text.length - 2) + 1; + var lastLineLength = atext.text.length - nextToLastNewlineEnd - 1; + op.lines--; + op.chars -= (lastLineLength + 1); + assem.append(op); + op.lines = 0; + op.chars = lastLineLength; + if (op.chars) { + assem.append(op); + } + } + } else { + assem.append(op); + } + } +}; + +exports.prepareForWire = function (cs, pool) { + var newPool = AttributePoolFactory.createAttributePool();; + var newCs = exports.moveOpsToNewPool(cs, pool, newPool); + return { + translated: newCs, + pool: newPool + }; +}; + +exports.isIdentity = function (cs) { + var unpacked = exports.unpack(cs); + return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen; +}; + +exports.opAttributeValue = function (op, key, pool) { + return exports.attribsAttributeValue(op.attribs, key, pool); +}; + +exports.attribsAttributeValue = function (attribs, key, pool) { + var value = ''; + if (attribs) { + exports.eachAttribNumber(attribs, function (n) { + if (pool.getAttribKey(n) == key) { + value = pool.getAttribValue(n); + } + }); + } + return value; +}; + +exports.builder = function (oldLen) { + var assem = exports.smartOpAssembler(); + var o = exports.newOp(); + var charBank = exports.stringAssembler(); + + var self = { + // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case) + keep: function (N, L, attribs, pool) { + o.opcode = '='; + o.attribs = (attribs && exports.makeAttribsString('=', attribs, pool)) || ''; + o.chars = N; + o.lines = (L || 0); + assem.append(o); + return self; + }, + keepText: function (text, attribs, pool) { + assem.appendOpWithText('=', text, attribs, pool); + return self; + }, + insert: function (text, attribs, pool) { + assem.appendOpWithText('+', text, attribs, pool); + charBank.append(text); + return self; + }, + remove: function (N, L) { + o.opcode = '-'; + o.attribs = ''; + o.chars = N; + o.lines = (L || 0); + assem.append(o); + return self; + }, + toString: function () { + assem.endDocument(); + var newLen = oldLen + assem.getLengthChange(); + return exports.pack(oldLen, newLen, assem.toString(), charBank.toString()); + } + }; + + return self; +}; + +exports.makeAttribsString = function (opcode, attribs, pool) { + // makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work + if (!attribs) { + return ''; + } else if ((typeof attribs) == "string") { + return attribs; + } else if (pool && attribs && attribs.length) { + if (attribs.length > 1) { + attribs = attribs.slice(); + attribs.sort(); + } + var result = []; + for (var i = 0; i < attribs.length; i++) { + var pair = attribs[i]; + if (opcode == '=' || (opcode == '+' && pair[1])) { + result.push('*' + exports.numToString(pool.putAttrib(pair))); + } + } + return result.join(''); + } +}; + +// like "substring" but on a single-line attribution string +exports.subattribution = function (astr, start, optEnd) { + var iter = exports.opIterator(astr, 0); + var assem = exports.smartOpAssembler(); + var attOp = exports.newOp(); + var csOp = exports.newOp(); + var opOut = exports.newOp(); + + function doCsOp() { + if (csOp.chars) { + while (csOp.opcode && (attOp.opcode || iter.hasNext())) { + if (!attOp.opcode) iter.next(attOp); + + if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars && attOp.lines > 0 && csOp.lines <= 0) { + csOp.lines++; + } + + exports._slicerZipperFunc(attOp, csOp, opOut, null); + if (opOut.opcode) { + assem.append(opOut); + opOut.opcode = ''; + } + } + } + } + + csOp.opcode = '-'; + csOp.chars = start; + + doCsOp(); + + if (optEnd === undefined) { + if (attOp.opcode) { + assem.append(attOp); + } + while (iter.hasNext()) { + iter.next(attOp); + assem.append(attOp); + } + } else { + csOp.opcode = '='; + csOp.chars = optEnd - start; + doCsOp(); + } + + return assem.toString(); +}; + +exports.inverse = function (cs, lines, alines, pool) { + // lines and alines are what the exports is meant to apply to. + // They may be arrays or objects with .get(i) and .length methods. + // They include final newlines on lines. + + function lines_get(idx) { + if (lines.get) { + return lines.get(idx); + } else { + return lines[idx]; + } + } + + function lines_length() { + if ((typeof lines.length) == "number") { + return lines.length; + } else { + return lines.length(); + } + } + + function alines_get(idx) { + if (alines.get) { + return alines.get(idx); + } else { + return alines[idx]; + } + } + + function alines_length() { + if ((typeof alines.length) == "number") { + return alines.length; + } else { + return alines.length(); + } + } + + var curLine = 0; + var curChar = 0; + var curLineOpIter = null; + var curLineOpIterLine; + var curLineNextOp = exports.newOp('+'); + + var unpacked = exports.unpack(cs); + var csIter = exports.opIterator(unpacked.ops); + var builder = exports.builder(unpacked.newLen); + + function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ ) { + + if ((!curLineOpIter) || (curLineOpIterLine != curLine)) { + // create curLineOpIter and advance it to curChar + curLineOpIter = exports.opIterator(alines_get(curLine)); + curLineOpIterLine = curLine; + var indexIntoLine = 0; + var done = false; + while (!done) { + curLineOpIter.next(curLineNextOp); + if (indexIntoLine + curLineNextOp.chars >= curChar) { + curLineNextOp.chars -= (curChar - indexIntoLine); + done = true; + } else { + indexIntoLine += curLineNextOp.chars; + } + } + } + + while (numChars > 0) { + if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { + curLine++; + curChar = 0; + curLineOpIterLine = curLine; + curLineNextOp.chars = 0; + curLineOpIter = exports.opIterator(alines_get(curLine)); + } + if (!curLineNextOp.chars) { + curLineOpIter.next(curLineNextOp); + } + var charsToUse = Math.min(numChars, curLineNextOp.chars); + func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0); + numChars -= charsToUse; + curLineNextOp.chars -= charsToUse; + curChar += charsToUse; + } + + if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { + curLine++; + curChar = 0; + } + } + + function skip(N, L) { + if (L) { + curLine += L; + curChar = 0; + } else { + if (curLineOpIter && curLineOpIterLine == curLine) { + consumeAttribRuns(N, function () {}); + } else { + curChar += N; + } + } + } + + function nextText(numChars) { + var len = 0; + var assem = exports.stringAssembler(); + var firstString = lines_get(curLine).substring(curChar); + len += firstString.length; + assem.append(firstString); + + var lineNum = curLine + 1; + while (len < numChars) { + var nextString = lines_get(lineNum); + len += nextString.length; + assem.append(nextString); + lineNum++; + } + + return assem.toString().substring(0, numChars); + } + + function cachedStrFunc(func) { + var cache = {}; + return function (s) { + if (!cache[s]) { + cache[s] = func(s); + } + return cache[s]; + }; + } + + var attribKeys = []; + var attribValues = []; + while (csIter.hasNext()) { + var csOp = csIter.next(); + if (csOp.opcode == '=') { + if (csOp.attribs) { + attribKeys.length = 0; + attribValues.length = 0; + exports.eachAttribNumber(csOp.attribs, function (n) { + attribKeys.push(pool.getAttribKey(n)); + attribValues.push(pool.getAttribValue(n)); + }); + var undoBackToAttribs = cachedStrFunc(function (attribs) { + var backAttribs = []; + for (var i = 0; i < attribKeys.length; i++) { + var appliedKey = attribKeys[i]; + var appliedValue = attribValues[i]; + var oldValue = exports.attribsAttributeValue(attribs, appliedKey, pool); + if (appliedValue != oldValue) { + backAttribs.push([appliedKey, oldValue]); + } + } + return exports.makeAttribsString('=', backAttribs, pool); + }); + consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { + builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs)); + }); + } else { + skip(csOp.chars, csOp.lines); + builder.keep(csOp.chars, csOp.lines); + } + } else if (csOp.opcode == '+') { + builder.remove(csOp.chars, csOp.lines); + } else if (csOp.opcode == '-') { + var textBank = nextText(csOp.chars); + var textBankIndex = 0; + consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { + builder.insert(textBank.substr(textBankIndex, len), attribs); + textBankIndex += len; + }); + } + } + + return exports.checkRep(builder.toString()); +}; + +// %CLIENT FILE ENDS HERE% +exports.follow = function (cs1, cs2, reverseInsertOrder, pool) { + var unpacked1 = exports.unpack(cs1); + var unpacked2 = exports.unpack(cs2); + var len1 = unpacked1.oldLen; + var len2 = unpacked2.oldLen; + exports.assert(len1 == len2, "mismatched follow"); + var chars1 = exports.stringIterator(unpacked1.charBank); + var chars2 = exports.stringIterator(unpacked2.charBank); + + var oldLen = unpacked1.newLen; + var oldPos = 0; + var newLen = 0; + + var hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool); + + var newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) { + if (op1.opcode == '+' || op2.opcode == '+') { + var whichToDo; + if (op2.opcode != '+') { + whichToDo = 1; + } else if (op1.opcode != '+') { + whichToDo = 2; + } else { + // both + + var firstChar1 = chars1.peek(1); + var firstChar2 = chars2.peek(1); + var insertFirst1 = hasInsertFirst(op1.attribs); + var insertFirst2 = hasInsertFirst(op2.attribs); + if (insertFirst1 && !insertFirst2) { + whichToDo = 1; + } else if (insertFirst2 && !insertFirst1) { + whichToDo = 2; + } + // insert string that doesn't start with a newline first so as not to break up lines + else if (firstChar1 == '\n' && firstChar2 != '\n') { + whichToDo = 2; + } else if (firstChar1 != '\n' && firstChar2 == '\n') { + whichToDo = 1; + } + // break symmetry: + else if (reverseInsertOrder) { + whichToDo = 2; + } else { + whichToDo = 1; + } + } + if (whichToDo == 1) { + chars1.skip(op1.chars); + opOut.opcode = '='; + opOut.lines = op1.lines; + opOut.chars = op1.chars; + opOut.attribs = ''; + op1.opcode = ''; + } else { + // whichToDo == 2 + chars2.skip(op2.chars); + exports.copyOp(op2, opOut); + op2.opcode = ''; + } + } else if (op1.opcode == '-') { + if (!op2.opcode) { + op1.opcode = ''; + } else { + if (op1.chars <= op2.chars) { + op2.chars -= op1.chars; + op2.lines -= op1.lines; + op1.opcode = ''; + if (!op2.chars) { + op2.opcode = ''; + } + } else { + op1.chars -= op2.chars; + op1.lines -= op2.lines; + op2.opcode = ''; + } + } + } else if (op2.opcode == '-') { + exports.copyOp(op2, opOut); + if (!op1.opcode) { + op2.opcode = ''; + } else if (op2.chars <= op1.chars) { + // delete part or all of a keep + op1.chars -= op2.chars; + op1.lines -= op2.lines; + op2.opcode = ''; + if (!op1.chars) { + op1.opcode = ''; + } + } else { + // delete all of a keep, and keep going + opOut.lines = op1.lines; + opOut.chars = op1.chars; + op2.lines -= op1.lines; + op2.chars -= op1.chars; + op1.opcode = ''; + } + } else if (!op1.opcode) { + exports.copyOp(op2, opOut); + op2.opcode = ''; + } else if (!op2.opcode) { + exports.copyOp(op1, opOut); + op1.opcode = ''; + } else { + // both keeps + opOut.opcode = '='; + opOut.attribs = exports.followAttributes(op1.attribs, op2.attribs, pool); + if (op1.chars <= op2.chars) { + opOut.chars = op1.chars; + opOut.lines = op1.lines; + op2.chars -= op1.chars; + op2.lines -= op1.lines; + op1.opcode = ''; + if (!op2.chars) { + op2.opcode = ''; + } + } else { + opOut.chars = op2.chars; + opOut.lines = op2.lines; + op1.chars -= op2.chars; + op1.lines -= op2.lines; + op2.opcode = ''; + } + } + switch (opOut.opcode) { + case '=': + oldPos += opOut.chars; + newLen += opOut.chars; + break; + case '-': + oldPos += opOut.chars; + break; + case '+': + newLen += opOut.chars; + break; + } + }); + newLen += oldLen - oldPos; + + return exports.pack(oldLen, newLen, newOps, unpacked2.charBank); +}; + +exports.followAttributes = function (att1, att2, pool) { + // The merge of two sets of attribute changes to the same text + // takes the lexically-earlier value if there are two values + // for the same key. Otherwise, all key/value changes from + // both attribute sets are taken. This operation is the "follow", + // so a set of changes is produced that can be applied to att1 + // to produce the merged set. + if ((!att2) || (!pool)) return ''; + if (!att1) return att2; + var atts = []; + att2.replace(/\*([0-9a-z]+)/g, function (_, a) { + atts.push(pool.getAttrib(exports.parseNum(a))); + return ''; + }); + att1.replace(/\*([0-9a-z]+)/g, function (_, a) { + var pair1 = pool.getAttrib(exports.parseNum(a)); + for (var i = 0; i < atts.length; i++) { + var pair2 = atts[i]; + if (pair1[0] == pair2[0]) { + if (pair1[1] <= pair2[1]) { + // winner of merge is pair1, delete this attribute + atts.splice(i, 1); + } + break; + } + } + return ''; + }); + // we've only removed attributes, so they're already sorted + var buf = exports.stringAssembler(); + for (var i = 0; i < atts.length; i++) { + buf.append('*'); + buf.append(exports.numToString(pool.putAttrib(atts[i]))); + } + return buf.toString(); +}; diff --git a/node/MessageHandler.js b/node/MessageHandler.js new file mode 100644 index 000000000..2bbadce95 --- /dev/null +++ b/node/MessageHandler.js @@ -0,0 +1,454 @@ +/** + * 2011 Peter 'Pita' Martischka + * + * 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. + */ + +var padManager = require("./PadManager"); +var Changeset = require("./Changeset"); +var AttributePoolFactory = require("./AttributePoolFactory"); +var authorManager = require("./AuthorManager"); + +//var token2author = {}; +//var author2token = {}; + +var session2pad = {}; +var pad2sessions = {}; + +var sessioninfos = {}; + +var socketio; + +exports.setSocketIO = function(socket_io) +{ + socketio=socket_io; +} + +exports.handleConnect = function(client) +{ + throwExceptionIfClientOrIOisInvalid(client); + + session2pad[client.sessionId]=null; + sessioninfos[client.sessionId]={}; +} + +exports.handleDisconnect = function(client) +{ + throwExceptionIfClientOrIOisInvalid(client); + + var sessionPad=session2pad[client.sessionId]; + + for(i in pad2sessions[sessionPad]) + { + if(pad2sessions[sessionPad][i] == client.sessionId) + { + delete pad2sessions[sessionPad][i]; + break; + } + } + + delete session2pad[client.sessionId]; + delete sessioninfos[client.sessionId]; +} + +exports.handleMessage = function(client, message) +{ + throwExceptionIfClientOrIOisInvalid(client); + + if(message == null) + { + throw "Message is null!"; + } + + if(typeof message == "string") + { + message = JSON.parse(message); + } + + if(!message.type) + { + throw "Message have no type attribute!"; + } + + if(message.type == "CLIENT_READY") + { + handleClientReady(client, message); + } + else if(message.type == "COLLABROOM" && + message.data.type == "USER_CHANGES") + { + console.error(JSON.stringify(message)); + handleUserChanges(client, message); + } + else if(message.type == "COLLABROOM" && + message.data.type == "USERINFO_UPDATE") + { + console.error(JSON.stringify(message)); + handleUserInfoUpdate(client, message); + } + else + { + console.error(message); + throw "unkown Message Type: '" + message.type + "'"; + } +} + +function handleUserInfoUpdate(client, message) +{ + if(message.data.userInfo.name == null) + { + throw "USERINFO_UPDATE Message have no name!"; + } + if(message.data.userInfo.colorId == null) + { + throw "USERINFO_UPDATE Message have no colorId!"; + } + + var author = sessioninfos[client.sessionId].author; + + authorManager.setAuthorColorId(author, message.data.userInfo.colorId); + authorManager.setAuthorName(author, message.data.userInfo.name); +} + +function handleUserChanges(client, message) +{ + if(message.data.baseRev == null) + { + throw "USER_CHANGES Message have no baseRev!"; + } + if(message.data.apool == null) + { + throw "USER_CHANGES Message have no apool!"; + } + if(message.data.changeset == null) + { + throw "USER_CHANGES Message have no changeset!"; + } + + var baseRev = message.data.baseRev; + var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool); + //console.error({"wireApool": wireApool}); + var changeset = message.data.changeset; + var pad = padManager.getPad(session2pad[client.sessionId], false); + + //ex. _checkChangesetAndPool + + Changeset.checkRep(changeset); + Changeset.eachAttribNumber(changeset, function(n) { + if (! wireApool.getAttrib(n)) { + throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; + } + }); + + //ex. adoptChangesetAttribs + + + console.error({"changeset": changeset}); + //console.error({"before: pad.pool()": pad.pool()}); + Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool()); + //console.error({"after: pad.pool()": pad.pool()}); + + //ex. applyUserChanges + + var apool = pad.pool(); + var r = baseRev; + + while (r < pad.getHeadRevisionNumber()) { + r++; + var c = pad.getRevisionChangeset(r); + changeset = Changeset.follow(c, changeset, false, apool); + } + + var prevText = pad.text(); + if (Changeset.oldLen(changeset) != prevText.length) { + throw "Can't apply USER_CHANGES "+changeset+" with oldLen " + + Changeset.oldLen(changeset) + " to document of length " + prevText.length; + } + + var thisAuthor = sessioninfos[client.sessionId].author; + + pad.appendRevision(changeset, thisAuthor); + + var correctionChangeset = _correctMarkersInPad(pad.atext(), pad.pool()); + if (correctionChangeset) { + pad.appendRevision(correctionChangeset); + } + + if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) { + var nlChangeset = Changeset.makeSplice( + pad.text(), pad.text().length-1, 0, "\n"); + pad.appendRevision(nlChangeset); + } + + console.error(JSON.stringify(pad.pool())); + + //ex. updatePadClients + + //console.error({"sessioninfos[client.sessionId].author":sessioninfos[client.sessionId].author}); + + for(i in pad2sessions[pad.id]) + { + var session = pad2sessions[pad.id][i]; + //console.error({"session":session}); + var lastRev = sessioninfos[session].rev; + + while (lastRev < pad.getHeadRevisionNumber()) + { + var r = ++lastRev; + var author = pad.getRevisionAuthor(r); + + //console.error({"author":author}); + + if(author == sessioninfos[session].author) + { + socketio.clients[session].send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); + } + else + { + var forWire = Changeset.prepareForWire(pad.getRevisionChangeset(r), pad.pool()); + var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r, + changeset: forWire.translated, + apool: forWire.pool, + author: author}}; + socketio.clients[session].send(wireMsg); + } + } + + sessioninfos[session].rev = pad.getHeadRevisionNumber(); + } + + //pad.getAllAuthors(); +} + +function _correctMarkersInPad(atext, apool) { + var text = atext.text; + + // collect char positions of line markers (e.g. bullets) in new atext + // that aren't at the start of a line + var badMarkers = []; + var iter = Changeset.opIterator(atext.attribs); + var offset = 0; + while (iter.hasNext()) { + var op = iter.next(); + var listValue = Changeset.opAttributeValue(op, 'list', apool); + if (listValue) { + for(var i=0;i 0 && text.charAt(offset-1) != '\n') { + badMarkers.push(offset); + } + offset++; + } + } + else { + offset += op.chars; + } + } + + if (badMarkers.length == 0) { + return null; + } + + // create changeset that removes these bad markers + offset = 0; + var builder = Changeset.builder(text.length); + badMarkers.forEach(function(pos) { + builder.keepText(text.substring(offset, pos)); + builder.remove(1); + offset = pos+1; + }); + return builder.toString(); +} + +function handleClientReady(client, message) +{ + if(!message.token) + { + throw "CLIENT_READY Message have no token!"; + } + if(!message.padId) + { + throw "CLIENT_READY Message have no padId!"; + } + if(!message.protocolVersion) + { + throw "CLIENT_READY Message have no protocolVersion!"; + } + if(message.protocolVersion != 1) + { + throw "CLIENT_READY Message have a unkown protocolVersion '" + protocolVersion + "'!"; + } + + var author = authorManager.getAuthor4Token(message.token); + /*if(token2author[message.token]) + { + author = token2author[message.token]; + } + else + { + author = "g." + _randomString(16); + + token2author[message.token] = author; + author2token[author] = message.token; + }*/ + + var sessionId=String(client.sessionId); + session2pad[sessionId] = message.padId; + + if(!pad2sessions[message.padId]) + { + pad2sessions[message.padId] = []; + } + + padManager.ensurePadExists(message.padId); + pad2sessions[message.padId].push(sessionId); + + /*console.dir({"session2pad": session2pad}); + console.dir({"pad2sessions": pad2sessions}); + console.dir({"token2author": token2author}); + console.dir({"author2token": author2token});*/ + + var pad = padManager.getPad(message.padId, false); + + atext = pad.atext(); + var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool()); + var apool = attribsForWire.pool.toJsonable(); + atext.attribs = attribsForWire.translated; + + var clientVars = { + //"userAgent": "Anonymous Agent", + "accountPrivs": { + "maxRevisions": 100 + }, + "initialRevisionList": [], + "initialOptions": { + "guestPolicy": "deny" + }, + "collab_client_vars": { + "initialAttributedText": atext, + "clientIp": client.request.connection.remoteAddress, + //"clientAgent": "Anonymous Agent", + "padId": message.padId, + "historicalAuthorData": {}, + "apool": apool, + "rev": pad.getHeadRevisionNumber(), + "globalPadId": message.padId + }, + "colorPalette": ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd"], + "clientIp": client.request.connection.remoteAddress, + "userIsGuest": true, + "userColor": authorManager.getAuthorColorId(author), + "padId": message.padId, + "initialTitle": "Pad: " + message.padId, + "opts": {}, + "chatHistory": { + "start": 0, + "historicalAuthorData": {}, + "end": 0, + "lines": [] + }, + "numConnectedUsers": pad2sessions[message.padId].length, + "isProPad": false, + "serverTimestamp": new Date().getTime(), + "globalPadId": message.padId, + "userId": author, + "cookiePrefsToSet": { + "fullWidth": false, + "hideSidebar": false + }, + "hooks": {} + } + + if(authorManager.getAuthorName(author) != null) + { + clientVars.userName = authorManager.getAuthorName(author); + } + + var allAuthors = pad.getAllAuthors(); + + for(i in allAuthors) + { + clientVars.collab_client_vars.historicalAuthorData[allAuthors[i]] = {}; + if(authorManager.getAuthorName(author) != null) + clientVars.collab_client_vars.historicalAuthorData[allAuthors[i]].name = authorManager.getAuthorName(author); + clientVars.collab_client_vars.historicalAuthorData[allAuthors[i]].colorId = authorManager.getAuthorColorId(author); + } + + client.send(clientVars); + + sessioninfos[client.sessionId].rev = pad.getHeadRevisionNumber(); + sessioninfos[client.sessionId].author = author; + + var messageToTheOtherUsers = { + "type": "COLLABROOM", + "data": { + type: "USER_NEWINFO", + userInfo: { + "ip": "127.0.0.1", + "colorId": authorManager.getAuthorColorId(author), + "userAgent": "Anonymous", + "userId": author + } + } + }; + + if(authorManager.getAuthorName(author) != null) + { + messageToTheOtherUsers.data.userInfo.name = authorManager.getAuthorName(author); + } + + for(i in pad2sessions[message.padId]) + { + if(pad2sessions[message.padId][i] != client.sessionId) + { + socketio.clients[pad2sessions[message.padId][i]].send(messageToTheOtherUsers); + + var messageToNotifyTheClientAboutTheOthers = { + "type": "COLLABROOM", + "data": { + type: "USER_NEWINFO", + userInfo: { + "ip": "127.0.0.1", + "colorId": authorManager.getAuthorColorId(sessioninfos[pad2sessions[message.padId][i]].author), + "userAgent": "Anonymous", + "userId": sessioninfos[pad2sessions[message.padId][i]].author + } + } + }; + + client.send(messageToNotifyTheClientAboutTheOthers); + } + } + + +} + +/*function _randomString(len) { + // use only numbers and lowercase letters + var pieces = []; + for(var i=0;i globalPads[id].head) + throw "The Revision " + revNum + " don't exist'"; +} + +/** + * Copied from the Etherpad source code, don't know what its good for + * @param txt + */ +exports.cleanText = function (txt) { + return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' '); +} + + diff --git a/node/easysync_tests.js b/node/easysync_tests.js new file mode 100644 index 000000000..b650d38f2 --- /dev/null +++ b/node/easysync_tests.js @@ -0,0 +1,942 @@ +/** + * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka + * + * 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. + */ + +var Changeset = require('./Changeset'); +var AttributePoolFactory = require("./AttributePoolFactory"); + +function random() { + this.nextInt = function (maxValue) { + return Math.floor(Math.random() * maxValue); + } + + this.nextDouble = function (maxValue) { + return Math.random(); + } +} + +function runTests() { + + function print(str) { + console.log(str); + } + + function assert(code, optMsg) { + if (!eval(code)) throw new Error("FALSE: " + (optMsg || code)); + } + + function literal(v) { + if ((typeof v) == "string") { + return '"' + v.replace(/[\\\"]/g, '\\$1').replace(/\n/g, '\\n') + '"'; + } else + return JSON.stringify(v); + } + + function assertEqualArrays(a, b) { + assert("JSON.stringify(" + literal(a) + ") == JSON.stringify(" + literal(b) + ")"); + } + + function assertEqualStrings(a, b) { + assert(literal(a) + " == " + literal(b)); + } + + function throughIterator(opsStr) { + var iter = Changeset.opIterator(opsStr); + var assem = Changeset.opAssembler(); + while (iter.hasNext()) { + assem.append(iter.next()); + } + return assem.toString(); + } + + function throughSmartAssembler(opsStr) { + var iter = Changeset.opIterator(opsStr); + var assem = Changeset.smartOpAssembler(); + while (iter.hasNext()) { + assem.append(iter.next()); + } + assem.endDocument(); + return assem.toString(); + } + + (function () { + print("> throughIterator"); + var x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1'; + assert("throughIterator(" + literal(x) + ") == " + literal(x)); + })(); + + (function () { + print("> throughSmartAssembler"); + var x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1'; + assert("throughSmartAssembler(" + literal(x) + ") == " + literal(x)); + })(); + + function applyMutations(mu, arrayOfArrays) { + arrayOfArrays.forEach(function (a) { + var result = mu[a[0]].apply(mu, a.slice(1)); + if (a[0] == 'remove' && a[3]) { + assertEqualStrings(a[3], result); + } + }); + } + + function mutationsToChangeset(oldLen, arrayOfArrays) { + var assem = Changeset.smartOpAssembler(); + var op = Changeset.newOp(); + var bank = Changeset.stringAssembler(); + var oldPos = 0; + var newLen = 0; + arrayOfArrays.forEach(function (a) { + if (a[0] == 'skip') { + op.opcode = '='; + op.chars = a[1]; + op.lines = (a[2] || 0); + assem.append(op); + oldPos += op.chars; + newLen += op.chars; + } else if (a[0] == 'remove') { + op.opcode = '-'; + op.chars = a[1]; + op.lines = (a[2] || 0); + assem.append(op); + oldPos += op.chars; + } else if (a[0] == 'insert') { + op.opcode = '+'; + bank.append(a[1]); + op.chars = a[1].length; + op.lines = (a[2] || 0); + assem.append(op); + newLen += op.chars; + } + }); + newLen += oldLen - oldPos; + assem.endDocument(); + return Changeset.pack(oldLen, newLen, assem.toString(), bank.toString()); + } + + function runMutationTest(testId, origLines, muts, correct) { + print("> runMutationTest#" + testId); + var lines = origLines.slice(); + var mu = Changeset.textLinesMutator(lines); + applyMutations(mu, muts); + mu.close(); + assertEqualArrays(correct, lines); + + var inText = origLines.join(''); + var cs = mutationsToChangeset(inText.length, muts); + lines = origLines.slice(); + Changeset.mutateTextLines(cs, lines); + assertEqualArrays(correct, lines); + + var correctText = correct.join(''); + //print(literal(cs)); + var outText = Changeset.applyToText(cs, inText); + assertEqualStrings(correctText, outText); + } + + runMutationTest(1, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [ + ['remove', 1, 0, "a"], + ['insert', "tu"], + ['remove', 1, 0, "p"], + ['skip', 4, 1], + ['skip', 7, 1], + ['insert', "cream\npie\n", 2], + ['skip', 2], + ['insert', "bot"], + ['insert', "\n", 1], + ['insert', "bu"], + ['skip', 3], + ['remove', 3, 1, "ge\n"], + ['remove', 6, 0, "duffle"] + ], ["tuple\n", "banana\n", "cream\n", "pie\n", "cabot\n", "bubba\n", "eggplant\n"]); + + runMutationTest(2, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [ + ['remove', 1, 0, "a"], + ['remove', 1, 0, "p"], + ['insert', "tu"], + ['skip', 11, 2], + ['insert', "cream\npie\n", 2], + ['skip', 2], + ['insert', "bot"], + ['insert', "\n", 1], + ['insert', "bu"], + ['skip', 3], + ['remove', 3, 1, "ge\n"], + ['remove', 6, 0, "duffle"] + ], ["tuple\n", "banana\n", "cream\n", "pie\n", "cabot\n", "bubba\n", "eggplant\n"]); + + runMutationTest(3, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [ + ['remove', 6, 1, "apple\n"], + ['skip', 15, 2], + ['skip', 6], + ['remove', 1, 1, "\n"], + ['remove', 8, 0, "eggplant"], + ['skip', 1, 1] + ], ["banana\n", "cabbage\n", "duffle\n"]); + + runMutationTest(4, ["15\n"], [ + ['skip', 1], + ['insert', "\n2\n3\n4\n", 4], + ['skip', 2, 1] + ], ["1\n", "2\n", "3\n", "4\n", "5\n"]); + + runMutationTest(5, ["1\n", "2\n", "3\n", "4\n", "5\n"], [ + ['skip', 1], + ['remove', 7, 4, "\n2\n3\n4\n"], + ['skip', 2, 1] + ], ["15\n"]); + + runMutationTest(6, ["123\n", "abc\n", "def\n", "ghi\n", "xyz\n"], [ + ['insert', "0"], + ['skip', 4, 1], + ['skip', 4, 1], + ['remove', 8, 2, "def\nghi\n"], + ['skip', 4, 1] + ], ["0123\n", "abc\n", "xyz\n"]); + + runMutationTest(7, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [ + ['remove', 6, 1, "apple\n"], + ['skip', 15, 2, true], + ['skip', 6, 0, true], + ['remove', 1, 1, "\n"], + ['remove', 8, 0, "eggplant"], + ['skip', 1, 1, true] + ], ["banana\n", "cabbage\n", "duffle\n"]); + + function poolOrArray(attribs) { + if (attribs.getAttrib) { + return attribs; // it's already an attrib pool + } else { + // assume it's an array of attrib strings to be split and added + var p = AttributePoolFactory.createAttributePool(); + attribs.forEach(function (kv) { + p.putAttrib(kv.split(',')); + }); + return p; + } + } + + function runApplyToAttributionTest(testId, attribs, cs, inAttr, outCorrect) { + print("> applyToAttribution#" + testId); + var p = poolOrArray(attribs); + var result = Changeset.applyToAttribution( + Changeset.checkRep(cs), inAttr, p); + assertEqualStrings(outCorrect, result); + } + + // turn cactus\n into actusabcd\n + runApplyToAttributionTest(1, ['bold,', 'bold,true'], "Z:7>3-1*0=1*1=1=3+4$abcd", "+1*1+1|1+5", "+1*1+1|1+8"); + + // turn "david\ngreenspan\n" into "david\ngreen\n" + runApplyToAttributionTest(2, ['bold,', 'bold,true'], "Z:g<4*1|1=6*1=5-4$", "|2+g", "*1|1+6*1+5|1+1"); + + (function () { + print("> mutatorHasMore"); + var lines = ["1\n", "2\n", "3\n", "4\n"]; + var mu; + + mu = Changeset.textLinesMutator(lines); + assert(mu.hasMore() + ' == true'); + mu.skip(8, 4); + assert(mu.hasMore() + ' == false'); + mu.close(); + assert(mu.hasMore() + ' == false'); + + // still 1,2,3,4 + mu = Changeset.textLinesMutator(lines); + assert(mu.hasMore() + ' == true'); + mu.remove(2, 1); + assert(mu.hasMore() + ' == true'); + mu.skip(2, 1); + assert(mu.hasMore() + ' == true'); + mu.skip(2, 1); + assert(mu.hasMore() + ' == true'); + mu.skip(2, 1); + assert(mu.hasMore() + ' == false'); + mu.insert("5\n", 1); + assert(mu.hasMore() + ' == false'); + mu.close(); + assert(mu.hasMore() + ' == false'); + + // 2,3,4,5 now + mu = Changeset.textLinesMutator(lines); + assert(mu.hasMore() + ' == true'); + mu.remove(6, 3); + assert(mu.hasMore() + ' == true'); + mu.remove(2, 1); + assert(mu.hasMore() + ' == false'); + mu.insert("hello\n", 1); + assert(mu.hasMore() + ' == false'); + mu.close(); + assert(mu.hasMore() + ' == false'); + + })(); + + function runMutateAttributionTest(testId, attribs, cs, alines, outCorrect) { + print("> runMutateAttributionTest#" + testId); + var p = poolOrArray(attribs); + var alines2 = Array.prototype.slice.call(alines); + var result = Changeset.mutateAttributionLines( + Changeset.checkRep(cs), alines2, p); + assertEqualArrays(outCorrect, alines2); + + print("> runMutateAttributionTest#" + testId + ".applyToAttribution"); + + function removeQuestionMarks(a) { + return a.replace(/\?/g, ''); + } + var inMerged = Changeset.joinAttributionLines(alines.map(removeQuestionMarks)); + var correctMerged = Changeset.joinAttributionLines(outCorrect.map(removeQuestionMarks)); + var mergedResult = Changeset.applyToAttribution(cs, inMerged, p); + assertEqualStrings(correctMerged, mergedResult); + } + + // turn 123\n 456\n 789\n into 123\n 456\n 789\n + runMutateAttributionTest(1, ["bold,true"], "Z:c>0|1=4=1*0=1$", ["|1+4", "|1+4", "|1+4"], ["|1+4", "+1*0+1|1+2", "|1+4"]); + + // make a document bold + runMutateAttributionTest(2, ["bold,true"], "Z:c>0*0|3=c$", ["|1+4", "|1+4", "|1+4"], ["*0|1+4", "*0|1+4", "*0|1+4"]); + + // clear bold on document + runMutateAttributionTest(3, ["bold,", "bold,true"], "Z:c>0*0|3=c$", ["*1+1+1*1+1|1+1", "+1*1+1|1+2", "*1+1+1*1+1|1+1"], ["|1+4", "|1+4", "|1+4"]); + + // add a character on line 3 of a document with 5 blank lines, and make sure + // the optimization that skips purely-kept lines is working; if any attribution string + // with a '?' is parsed it will cause an error. + runMutateAttributionTest(4, ['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'], "Z:5>1|2=2+1$x", ["?*1|1+1", "?*2|1+1", "*3|1+1", "?*4|1+1", "?*5|1+1"], ["?*1|1+1", "?*2|1+1", "+1*3|1+1", "?*4|1+1", "?*5|1+1"]); + + var testPoolWithChars = (function () { + var p = AttributePoolFactory.createAttributePool(); + p.putAttrib(['char', 'newline']); + for (var i = 1; i < 36; i++) { + p.putAttrib(['char', Changeset.numToString(i)]); + } + p.putAttrib(['char', '']); + return p; + })(); + + // based on runMutationTest#1 + runMutateAttributionTest(5, testPoolWithChars, "Z:11>7-2*t+1*u+1|2=b|2+a=2*b+1*o+1*t+1*0|1+1*b+1*u+1=3|1-3-6$" + "tucream\npie\nbot\nbu", ["*a+1*p+2*l+1*e+1*0|1+1", "*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1", "*c+1*a+1*b+2*a+1*g+1*e+1*0|1+1", "*d+1*u+1*f+2*l+1*e+1*0|1+1", "*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1"], ["*t+1*u+1*p+1*l+1*e+1*0|1+1", "*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1", "|1+6", "|1+4", "*c+1*a+1*b+1*o+1*t+1*0|1+1", "*b+1*u+1*b+2*a+1*0|1+1", "*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1"]); + + // based on runMutationTest#3 + runMutateAttributionTest(6, testPoolWithChars, "Z:117=1|4+7$\n2\n3\n4\n", ["*1+1*5|1+2"], ["*1+1|1+1", "|1+2", "|1+2", "|1+2", "*5|1+2"]); + + // based on runMutationTest#5 + runMutateAttributionTest(8, testPoolWithChars, "Z:a<7=1|4-7$", ["*1|1+2", "*2|1+2", "*3|1+2", "*4|1+2", "*5|1+2"], ["*1+1*5|1+2"]); + + // based on runMutationTest#6 + runMutateAttributionTest(9, testPoolWithChars, "Z:k<7*0+1*10|2=8|2-8$0", ["*1+1*2+1*3+1|1+1", "*a+1*b+1*c+1|1+1", "*d+1*e+1*f+1|1+1", "*g+1*h+1*i+1|1+1", "?*x+1*y+1*z+1|1+1"], ["*0+1|1+4", "|1+4", "?*x+1*y+1*z+1|1+1"]); + + runMutateAttributionTest(10, testPoolWithChars, "Z:6>4=1+1=1+1|1=1+1=1*0+1$abcd", ["|1+3", "|1+3"], ["|1+5", "+2*0+1|1+2"]); + + + runMutateAttributionTest(11, testPoolWithChars, "Z:s>1|1=4=6|1+1$\n", ["*0|1+4", "*0|1+8", "*0+5|1+1", "*0|1+1", "*0|1+5", "*0|1+1", "*0|1+1", "*0|1+1", "|1+1"], ["*0|1+4", "*0+6|1+1", "*0|1+2", "*0+5|1+1", "*0|1+1", "*0|1+5", "*0|1+1", "*0|1+1", "*0|1+1", "|1+1"]); + + function randomInlineString(len, rand) { + var assem = Changeset.stringAssembler(); + for (var i = 0; i < len; i++) { + assem.append(String.fromCharCode(rand.nextInt(26) + 97)); + } + return assem.toString(); + } + + function randomMultiline(approxMaxLines, approxMaxCols, rand) { + var numParts = rand.nextInt(approxMaxLines * 2) + 1; + var txt = Changeset.stringAssembler(); + txt.append(rand.nextInt(2) ? '\n' : ''); + for (var i = 0; i < numParts; i++) { + if ((i % 2) == 0) { + if (rand.nextInt(10)) { + txt.append(randomInlineString(rand.nextInt(approxMaxCols) + 1, rand)); + } else { + txt.append('\n'); + } + } else { + txt.append('\n'); + } + } + return txt.toString(); + } + + function randomStringOperation(numCharsLeft, rand) { + var result; + switch (rand.nextInt(9)) { + case 0: + { + // insert char + result = { + insert: randomInlineString(1, rand) + }; + break; + } + case 1: + { + // delete char + result = { + remove: 1 + }; + break; + } + case 2: + { + // skip char + result = { + skip: 1 + }; + break; + } + case 3: + { + // insert small + result = { + insert: randomInlineString(rand.nextInt(4) + 1, rand) + }; + break; + } + case 4: + { + // delete small + result = { + remove: rand.nextInt(4) + 1 + }; + break; + } + case 5: + { + // skip small + result = { + skip: rand.nextInt(4) + 1 + }; + break; + } + case 6: + { + // insert multiline; + result = { + insert: randomMultiline(5, 20, rand) + }; + break; + } + case 7: + { + // delete multiline + result = { + remove: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble()) + }; + break; + } + case 8: + { + // skip multiline + result = { + skip: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble()) + }; + break; + } + case 9: + { + // delete to end + result = { + remove: numCharsLeft + }; + break; + } + case 10: + { + // skip to end + result = { + skip: numCharsLeft + }; + break; + } + } + var maxOrig = numCharsLeft - 1; + if ('remove' in result) { + result.remove = Math.min(result.remove, maxOrig); + } else if ('skip' in result) { + result.skip = Math.min(result.skip, maxOrig); + } + return result; + } + + function randomTwoPropAttribs(opcode, rand) { + // assumes attrib pool like ['apple,','apple,true','banana,','banana,true'] + if (opcode == '-' || rand.nextInt(3)) { + return ''; + } else if (rand.nextInt(3)) { + if (opcode == '+' || rand.nextInt(2)) { + return '*' + Changeset.numToString(rand.nextInt(2) * 2 + 1); + } else { + return '*' + Changeset.numToString(rand.nextInt(2) * 2); + } + } else { + if (opcode == '+' || rand.nextInt(4) == 0) { + return '*1*3'; + } else { + return ['*0*2', '*0*3', '*1*2'][rand.nextInt(3)]; + } + } + } + + function randomTestChangeset(origText, rand, withAttribs) { + var charBank = Changeset.stringAssembler(); + var textLeft = origText; // always keep final newline + var outTextAssem = Changeset.stringAssembler(); + var opAssem = Changeset.smartOpAssembler(); + var oldLen = origText.length; + + var nextOp = Changeset.newOp(); + + function appendMultilineOp(opcode, txt) { + nextOp.opcode = opcode; + if (withAttribs) { + nextOp.attribs = randomTwoPropAttribs(opcode, rand); + } + txt.replace(/\n|[^\n]+/g, function (t) { + if (t == '\n') { + nextOp.chars = 1; + nextOp.lines = 1; + opAssem.append(nextOp); + } else { + nextOp.chars = t.length; + nextOp.lines = 0; + opAssem.append(nextOp); + } + return ''; + }); + } + + function doOp() { + var o = randomStringOperation(textLeft.length, rand); + if (o.insert) { + var txt = o.insert; + charBank.append(txt); + outTextAssem.append(txt); + appendMultilineOp('+', txt); + } else if (o.skip) { + var txt = textLeft.substring(0, o.skip); + textLeft = textLeft.substring(o.skip); + outTextAssem.append(txt); + appendMultilineOp('=', txt); + } else if (o.remove) { + var txt = textLeft.substring(0, o.remove); + textLeft = textLeft.substring(o.remove); + appendMultilineOp('-', txt); + } + } + + while (textLeft.length > 1) doOp(); + for (var i = 0; i < 5; i++) doOp(); // do some more (only insertions will happen) + var outText = outTextAssem.toString() + '\n'; + opAssem.endDocument(); + var cs = Changeset.pack(oldLen, outText.length, opAssem.toString(), charBank.toString()); + Changeset.checkRep(cs); + return [cs, outText]; + } + + function testCompose(randomSeed) { + var rand = new random(); + print("> testCompose#" + randomSeed); + + var p = AttributePoolFactory.createAttributePool(); + + var startText = randomMultiline(10, 20, rand) + '\n'; + + var x1 = randomTestChangeset(startText, rand); + var change1 = x1[0]; + var text1 = x1[1]; + + var x2 = randomTestChangeset(text1, rand); + var change2 = x2[0]; + var text2 = x2[1]; + + var x3 = randomTestChangeset(text2, rand); + var change3 = x3[0]; + var text3 = x3[1]; + + //print(literal(Changeset.toBaseTen(startText))); + //print(literal(Changeset.toBaseTen(change1))); + //print(literal(Changeset.toBaseTen(change2))); + var change12 = Changeset.checkRep(Changeset.compose(change1, change2, p)); + var change23 = Changeset.checkRep(Changeset.compose(change2, change3, p)); + var change123 = Changeset.checkRep(Changeset.compose(change12, change3, p)); + var change123a = Changeset.checkRep(Changeset.compose(change1, change23, p)); + assertEqualStrings(change123, change123a); + + assertEqualStrings(text2, Changeset.applyToText(change12, startText)); + assertEqualStrings(text3, Changeset.applyToText(change23, text1)); + assertEqualStrings(text3, Changeset.applyToText(change123, startText)); + } + + for (var i = 0; i < 30; i++) testCompose(i); + + (function simpleComposeAttributesTest() { + print("> simpleComposeAttributesTest"); + var p = AttributePoolFactory.createAttributePool(); + p.putAttrib(['bold', '']); + p.putAttrib(['bold', 'true']); + var cs1 = Changeset.checkRep("Z:2>1*1+1*1=1$x"); + var cs2 = Changeset.checkRep("Z:3>0*0|1=3$"); + var cs12 = Changeset.checkRep(Changeset.compose(cs1, cs2, p)); + assertEqualStrings("Z:2>1+1*0|1=2$x", cs12); + })(); + + (function followAttributesTest() { + var p = AttributePoolFactory.createAttributePool(); + p.putAttrib(['x', '']); + p.putAttrib(['x', 'abc']); + p.putAttrib(['x', 'def']); + p.putAttrib(['y', '']); + p.putAttrib(['y', 'abc']); + p.putAttrib(['y', 'def']); + + function testFollow(a, b, afb, bfa, merge) { + assertEqualStrings(afb, Changeset.followAttributes(a, b, p)); + assertEqualStrings(bfa, Changeset.followAttributes(b, a, p)); + assertEqualStrings(merge, Changeset.composeAttributes(a, afb, true, p)); + assertEqualStrings(merge, Changeset.composeAttributes(b, bfa, true, p)); + } + + testFollow('', '', '', '', ''); + testFollow('*0', '', '', '*0', '*0'); + testFollow('*0', '*0', '', '', '*0'); + testFollow('*0', '*1', '', '*0', '*0'); + testFollow('*1', '*2', '', '*1', '*1'); + testFollow('*0*1', '', '', '*0*1', '*0*1'); + testFollow('*0*4', '*2*3', '*3', '*0', '*0*3'); + testFollow('*0*4', '*2', '', '*0*4', '*0*4'); + })(); + + function testFollow(randomSeed) { + var rand = new random(); + print("> testFollow#" + randomSeed); + + var p = AttributePoolFactory.createAttributePool(); + + var startText = randomMultiline(10, 20, rand) + '\n'; + + var cs1 = randomTestChangeset(startText, rand)[0]; + var cs2 = randomTestChangeset(startText, rand)[0]; + + var afb = Changeset.checkRep(Changeset.follow(cs1, cs2, false, p)); + var bfa = Changeset.checkRep(Changeset.follow(cs2, cs1, true, p)); + + var merge1 = Changeset.checkRep(Changeset.compose(cs1, afb)); + var merge2 = Changeset.checkRep(Changeset.compose(cs2, bfa)); + + assertEqualStrings(merge1, merge2); + } + + for (var i = 0; i < 30; i++) testFollow(i); + + function testSplitJoinAttributionLines(randomSeed) { + var rand = new random(); + print("> testSplitJoinAttributionLines#" + randomSeed); + + var doc = randomMultiline(10, 20, rand) + '\n'; + + function stringToOps(str) { + var assem = Changeset.mergingOpAssembler(); + var o = Changeset.newOp('+'); + o.chars = 1; + for (var i = 0; i < str.length; i++) { + var c = str.charAt(i); + o.lines = (c == '\n' ? 1 : 0); + o.attribs = (c == 'a' || c == 'b' ? '*' + c : ''); + assem.append(o); + } + return assem.toString(); + } + + var theJoined = stringToOps(doc); + var theSplit = doc.match(/[^\n]*\n/g).map(stringToOps); + + assertEqualArrays(theSplit, Changeset.splitAttributionLines(theJoined, doc)); + assertEqualStrings(theJoined, Changeset.joinAttributionLines(theSplit)); + } + + for (var i = 0; i < 10; i++) testSplitJoinAttributionLines(i); + + (function testMoveOpsToNewPool() { + print("> testMoveOpsToNewPool"); + + var pool1 = AttributePoolFactory.createAttributePool(); + var pool2 = AttributePoolFactory.createAttributePool(); + + pool1.putAttrib(['baz', 'qux']); + pool1.putAttrib(['foo', 'bar']); + + pool2.putAttrib(['foo', 'bar']); + + assertEqualStrings(Changeset.moveOpsToNewPool('Z:1>2*1+1*0+1$ab', pool1, pool2), 'Z:1>2*0+1*1+1$ab'); + assertEqualStrings(Changeset.moveOpsToNewPool('*1+1*0+1', pool1, pool2), '*0+1*1+1'); + })(); + + + (function testMakeSplice() { + print("> testMakeSplice"); + + var t = "a\nb\nc\n"; + var t2 = Changeset.applyToText(Changeset.makeSplice(t, 5, 0, "def"), t); + assertEqualStrings("a\nb\ncdef\n", t2); + + })(); + + (function testToSplices() { + print("> testToSplices"); + + var cs = Changeset.checkRep('Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk'); + var correctSplices = [ + [5, 8, "123456789"], + [9, 17, "abcdefghijk"] + ]; + assertEqualArrays(correctSplices, Changeset.toSplices(cs)); + })(); + + function testCharacterRangeFollow(testId, cs, oldRange, insertionsAfter, correctNewRange) { + print("> testCharacterRangeFollow#" + testId); + + var cs = Changeset.checkRep(cs); + assertEqualArrays(correctNewRange, Changeset.characterRangeFollow(cs, oldRange[0], oldRange[1], insertionsAfter)); + + } + + testCharacterRangeFollow(1, 'Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk', [7, 10], false, [14, 15]); + testCharacterRangeFollow(2, "Z:bc<6|x=b4|2-6$", [400, 407], false, [400, 401]); + testCharacterRangeFollow(3, "Z:4>0-3+3$abc", [0, 3], false, [3, 3]); + testCharacterRangeFollow(4, "Z:4>0-3+3$abc", [0, 3], true, [0, 0]); + testCharacterRangeFollow(5, "Z:5>1+1=1-3+3$abcd", [1, 4], false, [5, 5]); + testCharacterRangeFollow(6, "Z:5>1+1=1-3+3$abcd", [1, 4], true, [2, 2]); + testCharacterRangeFollow(7, "Z:5>1+1=1-3+3$abcd", [0, 6], false, [1, 7]); + testCharacterRangeFollow(8, "Z:5>1+1=1-3+3$abcd", [0, 3], false, [1, 2]); + testCharacterRangeFollow(9, "Z:5>1+1=1-3+3$abcd", [2, 5], false, [5, 6]); + testCharacterRangeFollow(10, "Z:2>1+1$a", [0, 0], false, [1, 1]); + testCharacterRangeFollow(11, "Z:2>1+1$a", [0, 0], true, [0, 0]); + + (function testOpAttributeValue() { + print("> testOpAttributeValue"); + + var p = AttributePoolFactory.createAttributePool(); + p.putAttrib(['name', 'david']); + p.putAttrib(['color', 'green']); + + assertEqualStrings("david", Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'name', p)); + assertEqualStrings("david", Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'name', p)); + assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'name', p)); + assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('+1'), 'name', p)); + assertEqualStrings("green", Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'color', p)); + assertEqualStrings("green", Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'color', p)); + assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'color', p)); + assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('+1'), 'color', p)); + })(); + + function testAppendATextToAssembler(testId, atext, correctOps) { + print("> testAppendATextToAssembler#" + testId); + + var assem = Changeset.smartOpAssembler(); + Changeset.appendATextToAssembler(atext, assem); + assertEqualStrings(correctOps, assem.toString()); + } + + testAppendATextToAssembler(1, { + text: "\n", + attribs: "|1+1" + }, ""); + testAppendATextToAssembler(2, { + text: "\n\n", + attribs: "|2+2" + }, "|1+1"); + testAppendATextToAssembler(3, { + text: "\n\n", + attribs: "*x|2+2" + }, "*x|1+1"); + testAppendATextToAssembler(4, { + text: "\n\n", + attribs: "*x|1+1|1+1" + }, "*x|1+1"); + testAppendATextToAssembler(5, { + text: "foo\n", + attribs: "|1+4" + }, "+3"); + testAppendATextToAssembler(6, { + text: "\nfoo\n", + attribs: "|2+5" + }, "|1+1+3"); + testAppendATextToAssembler(7, { + text: "\nfoo\n", + attribs: "*x|2+5" + }, "*x|1+1*x+3"); + testAppendATextToAssembler(8, { + text: "\n\n\nfoo\n", + attribs: "|2+2*x|2+5" + }, "|2+2*x|1+1*x+3"); + + function testMakeAttribsString(testId, pool, opcode, attribs, correctString) { + print("> testMakeAttribsString#" + testId); + + var p = poolOrArray(pool); + var str = Changeset.makeAttribsString(opcode, attribs, p); + assertEqualStrings(correctString, str); + } + + testMakeAttribsString(1, ['bold,'], '+', [ + ['bold', ''] + ], ''); + testMakeAttribsString(2, ['abc,def', 'bold,'], '=', [ + ['bold', ''] + ], '*1'); + testMakeAttribsString(3, ['abc,def', 'bold,true'], '+', [ + ['abc', 'def'], + ['bold', 'true'] + ], '*0*1'); + testMakeAttribsString(4, ['abc,def', 'bold,true'], '+', [ + ['bold', 'true'], + ['abc', 'def'] + ], '*0*1'); + + function testSubattribution(testId, astr, start, end, correctOutput) { + print("> testSubattribution#" + testId); + + var str = Changeset.subattribution(astr, start, end); + assertEqualStrings(correctOutput, str); + } + + testSubattribution(1, "+1", 0, 0, ""); + testSubattribution(2, "+1", 0, 1, "+1"); + testSubattribution(3, "+1", 0, undefined, "+1"); + testSubattribution(4, "|1+1", 0, 0, ""); + testSubattribution(5, "|1+1", 0, 1, "|1+1"); + testSubattribution(6, "|1+1", 0, undefined, "|1+1"); + testSubattribution(7, "*0+1", 0, 0, ""); + testSubattribution(8, "*0+1", 0, 1, "*0+1"); + testSubattribution(9, "*0+1", 0, undefined, "*0+1"); + testSubattribution(10, "*0|1+1", 0, 0, ""); + testSubattribution(11, "*0|1+1", 0, 1, "*0|1+1"); + testSubattribution(12, "*0|1+1", 0, undefined, "*0|1+1"); + testSubattribution(13, "*0+2+1*1+3", 0, 1, "*0+1"); + testSubattribution(14, "*0+2+1*1+3", 0, 2, "*0+2"); + testSubattribution(15, "*0+2+1*1+3", 0, 3, "*0+2+1"); + testSubattribution(16, "*0+2+1*1+3", 0, 4, "*0+2+1*1+1"); + testSubattribution(17, "*0+2+1*1+3", 0, 5, "*0+2+1*1+2"); + testSubattribution(18, "*0+2+1*1+3", 0, 6, "*0+2+1*1+3"); + testSubattribution(19, "*0+2+1*1+3", 0, 7, "*0+2+1*1+3"); + testSubattribution(20, "*0+2+1*1+3", 0, undefined, "*0+2+1*1+3"); + testSubattribution(21, "*0+2+1*1+3", 1, undefined, "*0+1+1*1+3"); + testSubattribution(22, "*0+2+1*1+3", 2, undefined, "+1*1+3"); + testSubattribution(23, "*0+2+1*1+3", 3, undefined, "*1+3"); + testSubattribution(24, "*0+2+1*1+3", 4, undefined, "*1+2"); + testSubattribution(25, "*0+2+1*1+3", 5, undefined, "*1+1"); + testSubattribution(26, "*0+2+1*1+3", 6, undefined, ""); + testSubattribution(27, "*0+2+1*1|1+3", 0, 1, "*0+1"); + testSubattribution(28, "*0+2+1*1|1+3", 0, 2, "*0+2"); + testSubattribution(29, "*0+2+1*1|1+3", 0, 3, "*0+2+1"); + testSubattribution(30, "*0+2+1*1|1+3", 0, 4, "*0+2+1*1+1"); + testSubattribution(31, "*0+2+1*1|1+3", 0, 5, "*0+2+1*1+2"); + testSubattribution(32, "*0+2+1*1|1+3", 0, 6, "*0+2+1*1|1+3"); + testSubattribution(33, "*0+2+1*1|1+3", 0, 7, "*0+2+1*1|1+3"); + testSubattribution(34, "*0+2+1*1|1+3", 0, undefined, "*0+2+1*1|1+3"); + testSubattribution(35, "*0+2+1*1|1+3", 1, undefined, "*0+1+1*1|1+3"); + testSubattribution(36, "*0+2+1*1|1+3", 2, undefined, "+1*1|1+3"); + testSubattribution(37, "*0+2+1*1|1+3", 3, undefined, "*1|1+3"); + testSubattribution(38, "*0+2+1*1|1+3", 4, undefined, "*1|1+2"); + testSubattribution(39, "*0+2+1*1|1+3", 5, undefined, "*1|1+1"); + testSubattribution(40, "*0+2+1*1|1+3", 1, 5, "*0+1+1*1+2"); + testSubattribution(41, "*0+2+1*1|1+3", 2, 6, "+1*1|1+3"); + testSubattribution(42, "*0+2+1*1+3", 2, 6, "+1*1+3"); + + function testFilterAttribNumbers(testId, cs, filter, correctOutput) { + print("> testFilterAttribNumbers#" + testId); + + var str = Changeset.filterAttribNumbers(cs, filter); + assertEqualStrings(correctOutput, str); + } + + testFilterAttribNumbers(1, "*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6", function (n) { + return (n % 2) == 0; + }, "*0+1+2+3+4*2+5*0*2*c+6"); + testFilterAttribNumbers(2, "*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6", function (n) { + return (n % 2) == 1; + }, "*1+1+2+3*1+4+5*1*b+6"); + + function testInverse(testId, cs, lines, alines, pool, correctOutput) { + print("> testInverse#" + testId); + + pool = poolOrArray(pool); + var str = Changeset.inverse(Changeset.checkRep(cs), lines, alines, pool); + assertEqualStrings(correctOutput, str); + } + + // take "FFFFTTTTT" and apply "-FT--FFTT", the inverse of which is "--F--TT--" + testInverse(1, "Z:9>0=1*0=1*1=1=2*0=2*1|1=2$", null, ["+4*1+5"], ['bold,', 'bold,true'], "Z:9>0=2*0=1=2*1=2$"); + + function testMutateTextLines(testId, cs, lines, correctLines) { + print("> testMutateTextLines#" + testId); + + var a = lines.slice(); + Changeset.mutateTextLines(cs, a); + assertEqualArrays(correctLines, a); + } + + testMutateTextLines(1, "Z:4<1|1-2-1|1+1+1$\nc", ["a\n", "b\n"], ["\n", "c\n"]); + testMutateTextLines(2, "Z:4>0|1-2-1|2+3$\nc\n", ["a\n", "b\n"], ["\n", "c\n", "\n"]); + + function testInverseRandom(randomSeed) { + var rand = new random(); + print("> testInverseRandom#" + randomSeed); + + var p = poolOrArray(['apple,', 'apple,true', 'banana,', 'banana,true']); + + var startText = randomMultiline(10, 20, rand) + '\n'; + var alines = Changeset.splitAttributionLines(Changeset.makeAttribution(startText), startText); + var lines = startText.slice(0, -1).split('\n').map(function (s) { + return s + '\n'; + }); + + var stylifier = randomTestChangeset(startText, rand, true)[0]; + + //print(alines.join('\n')); + Changeset.mutateAttributionLines(stylifier, alines, p); + //print(stylifier); + //print(alines.join('\n')); + Changeset.mutateTextLines(stylifier, lines); + + var changeset = randomTestChangeset(lines.join(''), rand, true)[0]; + var inverseChangeset = Changeset.inverse(changeset, lines, alines, p); + + var origLines = lines.slice(); + var origALines = alines.slice(); + + Changeset.mutateTextLines(changeset, lines); + Changeset.mutateAttributionLines(changeset, alines, p); + //print(origALines.join('\n')); + //print(changeset); + //print(inverseChangeset); + //print(origLines.map(function(s) { return '1: '+s.slice(0,-1); }).join('\n')); + //print(lines.map(function(s) { return '2: '+s.slice(0,-1); }).join('\n')); + //print(alines.join('\n')); + Changeset.mutateTextLines(inverseChangeset, lines); + Changeset.mutateAttributionLines(inverseChangeset, alines, p); + //print(lines.map(function(s) { return '3: '+s.slice(0,-1); }).join('\n')); + assertEqualArrays(origLines, lines); + assertEqualArrays(origALines, alines); + } + + for (var i = 0; i < 30; i++) testInverseRandom(i); +} + +runTests(); diff --git a/node/server.js b/node/server.js new file mode 100644 index 000000000..a110eef2b --- /dev/null +++ b/node/server.js @@ -0,0 +1,128 @@ +// Simple Node & Socket server + +var http = require('http') + , url = require('url') + , fs = require('fs') + , io = require('socket.io') + , sys = require('sys') + , server; + +server = http.createServer(function(req, res){ + var path = url.parse(req.url).pathname; + + if(path.substring(0,"/static".length) == "/static" || path.substring(0,"/p/".length) == "/p/") + { + if(path.substring(0,"/p/".length) == "/p/") + { + if(path.length < 7) + send404(res, path); + + path = "/static/padhtml"; + } + + sendFile(res, path, __dirname + "/.." + path); + } + else if(path == "/") + { + sendRedirect(res, path, "/p/test"); + } + else if(path == "/newpad") + { + sendRedirect(res, path, "/p/" + randomPadName()); + } + else if(path == "/ep/pad/reconnect") + { + if(req.headers.referer != null) + sendRedirect(res, path, req.headers.referer); + else + send404(res, path); + } + else + { + send404(res, path); + } +}); +server.listen(80); + +function randomPadName() { + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + var string_length = 10; + var randomstring = ''; + for (var i=0; i " + path); + } + }); +} + +function send404(res, reqPath) +{ + res.writeHead(404); + res.write("404 - Not Found"); + res.end(); + + requestLog(404, reqPath, "NOT FOUND!"); +} + +function sendRedirect(res, reqPath, location) +{ + res.writeHead(302, {'Location': location}); + res.end(); + + requestLog(302, reqPath, "-> " + location); +} + +function requestLog(code, path, desc) +{ + //console.log(code +", " + path + ", " + desc); +} + +var io = io.listen(server); +var messageHandler = require("./MessageHandler"); +messageHandler.setSocketIO(io); + +io.on('connection', function(client){ + try{ + messageHandler.handleConnect(client); + }catch(e){console.error(e);} + + client.on('message', function(message){ + //try{ + messageHandler.handleMessage(client, message); + //}catch(e){console.error(e);} + }); + + client.on('disconnect', function(){ + try{ + messageHandler.handleDisconnect(client); + }catch(e){console.error(e);} + }); +}); + + + + diff --git a/static/css/editor.css b/static/css/editor.css new file mode 100644 index 000000000..0a43478b3 --- /dev/null +++ b/static/css/editor.css @@ -0,0 +1,109 @@ + +/* These CSS rules are included in both the outer and inner ACE iframe. + Also see inner.css, included only in the inner one. +*/ + +body { + margin: 0; + white-space: nowrap; +} + +#outerdocbody { + background-color: #fff; +} +body.grayedout { background-color: #eee !important } + +#innerdocbody { + font-size: 12px; /* overridden by body.style */ + font-family: monospace; /* overridden by body.style */ + line-height: 16px; /* overridden by body.style */ +} + +body.doesWrap { + white-space: normal; +} + +#innerdocbody { + padding-top: 1px; /* important for some reason? */ + padding-right: 10px; + padding-bottom: 8px; + padding-left: 1px /* prevents characters from looking chopped off in FF3 */; + overflow: hidden; + /* blank 1x1 gif, so that IE8 doesn't consider the body transparent */ + background-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==); +} + +#sidediv { + font-size: 11px; + font-family: monospace; + line-height: 16px; /* overridden by sideDiv.style */ + padding-top: 8px; /* EDIT_BODY_PADDING_TOP */ + padding-right: 3px; /* LINE_NUMBER_PADDING_RIGHT - 1 */ + position: absolute; + width: 20px; /* MIN_LINEDIV_WIDTH */ + top: 0; + left: 0; + cursor: default; + color: white; +} + +#sidedivinner { + text-align: right; +} + +.sidedivdelayed { /* class set after sizes are set */ + background-color: #eee; + color: #888 !important; + border-right: 1px solid #999; +} +.sidedivhidden { + display: none; +} + +#outerdocbody iframe { + display: block; /* codemirror says it suppresses bugs */ + position: relative; + left: 32px; /* MIN_LINEDIV_WIDTH + LINE_NUMBER_PADDING_RIGHT + EDIT_BODY_PADDING_LEFT */ + top: 7px; /* EDIT_BODY_PADDING_TOP - 1*/ + border: 0; + width: 1px; /* changed programmatically */ + height: 1px; /* changed programmatically */ +} + +#outerdocbody .hotrect { + border: 1px solid #999; + position: absolute; +} + +/* cause "body" area (e.g. where clicks are heard) to grow horizontally with text */ +body.mozilla, body.safari { + display: table-cell; +} + +body.doesWrap { + display: block !important; +} + +.safari div { + /* prevents the caret from disappearing on the longest line of the doc */ + padding-right: 1px; +} + +p { + margin: 0; +} + +/*b, strong, .Apple-style-span { font-weight: normal !important; font-style: normal !important; + color: red !important; }*/ + +#linemetricsdiv { + position: absolute; + left: -1000px; + top: -1000px; + color: white; + z-index: -1; + font-size: 12px; /* overridden by lineMetricsDiv.style */ + font-family: monospace; /* overridden by lineMetricsDiv.style */ +} + +#overlaysdiv { position: absolute; left: -1000px; top: -1000px; } \ No newline at end of file diff --git a/static/css/inner.css b/static/css/inner.css new file mode 100644 index 000000000..a2db762f8 --- /dev/null +++ b/static/css/inner.css @@ -0,0 +1,45 @@ + +/* Firefox (3) is bad about keeping the text cursor in design mode; + various actions (clicking, dragging, scroll-wheel) lose it and it + doesn't come back easily, presumably because of optimizations. + These rules try to maximize the chance Firefox will think the cursor + needs changing again. +*/ +html { cursor: text; } /* in Safari, produces text cursor for whole doc (inc. below body) */ +span { cursor: auto; } + +a { cursor: pointer !important; } + +/*span { padding-bottom: 1px; }/* padding-top: 1px; }*/ + +/*div { background: transparent url(/static/img/acecarets/default.gif) repeat-y left top }*/ + +/*tt { padding-left: 3px; padding-right: 3px; margin-right: -3px; margin-left: -3px; }*/ + +/*div { display: list-item; list-style: disc outside; margin-left: 20px; }*/ +/*div:before { content:"foo" }*/ + +ul, ol, li { + padding: 0; + margin: 0; +} +ul { margin-left: 1.5em; } +ul ul { margin-left: 0 !important; } +ul.list-bullet1 { margin-left: 1.5em; } +ul.list-bullet2 { margin-left: 3em; } +ul.list-bullet3 { margin-left: 4.5em; } +ul.list-bullet4 { margin-left: 6em; } +ul.list-bullet5 { margin-left: 7.5em; } +ul.list-bullet6 { margin-left: 9em; } +ul.list-bullet7 { margin-left: 10.5em; } +ul.list-bullet8 { margin-left: 12em; } + +ul { list-style-type: disc; } +ul.list-bullet1 { list-style-type: disc; } +ul.list-bullet2 { list-style-type: circle; } +ul.list-bullet3 { list-style-type: square; } +ul.list-bullet4 { list-style-type: disc; } +ul.list-bullet5 { list-style-type: circle; } +ul.list-bullet6 { list-style-type: square; } +ul.list-bullet7 { list-style-type: disc; } +ul.list-bullet8 { list-style-type: circle; } diff --git a/static/css/pad2_ejs.css b/static/css/pad2_ejs.css new file mode 100644 index 000000000..c0ccae489 --- /dev/null +++ b/static/css/pad2_ejs.css @@ -0,0 +1,1131 @@ + +*,html.body,p { margin: 0; padding: 0; } +h1, h2, h3, h4, h5, h6 { font-size: 100%; font-weight: normal; font-style: normal; + text-decoration: none; margin: 0; padding: 0; } + +.clear { clear: both; } + +html { font-size: 62.5%; } + +body { background: #ebebeb url(/static/img/jun09/pad/backgrad.gif) repeat-x left top; } +body, textarea { font-family: Arial, sans-serif; } + +#padpage { + position: absolute; + top: 0px; + bottom: 8px; + left: 50%; + margin-left: -450px; + width: 900px; +} + +.maximized #padpage { + left: 8px; + right: 8px; + width: auto; + margin-left: 0; +} + +body.fullwidth #padpage { width: auto; margin-left: 6px; margin-right: 6px; } +body.squish1width #padpage { width: 800px; } +body.squish2width #padpage { width: 700px; } + +#topbar { height: 25px; background: #326cbd url(/static/img/jun09/pad/padtopback2.gif) repeat-x left top; + position: relative; } + +#topbarleft { float: left; height: 100%; overflow: hidden; + background: url(/static/img/jun09/pad/padtop5.gif) no-repeat left top; width: 5px; + + } +#topbarright { float: right; height: 100%; overflow: hidden; + background: url(/static/img/jun09/pad/padtop5.gif) no-repeat right top; width: 5px; } + +.propad #topbar { background: #2c2c2c url(/static/img/jun09/pad/protop.gif) repeat-x 0 -25px; } +.propad #topbarleft { background: url(/static/img/jun09/pad/protop.gif) no-repeat left top; } +.propad #topbarright { background: url(/static/img/jun09/pad/protop.gif) no-repeat right top; } + +a#backtoprosite, #accountnav { + display: block; position: absolute; height: 15px; line-height: 15px; + width: auto; top: 5px; font-size: 1.2em; display:none; +} +a#backtoprosite, #accountnav a { color: #cde7ff; text-decoration: underline; } + +a#backtoprosite { padding-left: 20px; left: 6px; + background: url(/static/img/jun09/pad/protop.gif) no-repeat -5px -6px; } +#accountnav { right: 30px; color: #fff; } + +#topbarcenter { margin-left: 150px; margin-right: 150px; text-align:center;} +a#topbaretherpad { margin-left: auto; margin-right: auto; display: block; width: 103px; + position: relative; top: 3px; height: 0; padding-top: 20px; +/* background: url(/static/img/jun09/pad/padtop5.gif) no-repeat -397px -3px; overflow: hidden; */ +} + + +#topbarBrand {font-size:2.1em; color: white; font-weight: bold; text-decoration: none; text-align: center;} + + + +#EtherpadLink {font-size: 1.2em; color: white;float:left;margin-left:-140px;margin-top:4px; text-decoration: none; } +#Licensing {font-size: 1.2em; color: white;float:left;margin-top:4px;margin-left:-75px; text-decoration: none; } +#fullscreen {font-size: 1.2em; color: white;float:right;margin-top:4px;margin-right:-120px; text-decoration: none; } + +.propad a#topbaretherpad { background: url(/static/img/jun09/pad/protop.gif) no-repeat -397px -3px; } + +#specialkeyarea { top: 5px; left: 250px; color: yellow; font-weight: bold; + font-size: 1.5em; position: absolute; } + +#alertbar { + margin-top: 6px; + opacity: 0; + filter: alpha(opacity = 0); /* IE */ + display: none; + position:absolute; + left:0; + right:0; + z-index:53; +} + +#servermsg { position: relative; zoom: 1; border: 1px solid #992; + background: #ffc; padding: 0.8em; font-size: 1.2em; } +#servermsg h3 { font-weight: bold; margin-right: 10px; + margin-bottom: 1em; float: left; width: auto; } +#servermsg #servermsgdate { font-style: italic; font-weight: normal; color: #666; } +a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; } + +#shuttingdown { position: relative; zoom: 1; border: 1px solid #992; + background: #ffc; padding: 0.6em; font-size: 1.2em; margin-top: 6px; } + +#docbar { margin-top: 6px; height: 25px; position: relative; zoom: 1; + background: #fbfbfb url(/static/img/jun09/pad/padtopback2.gif) repeat-x 0 -31px; } + +.docbarbutton +{ + padding-top: 2px; + padding-bottom: 2px; + padding-left: 4px; + padding-right: 4px; + border-left: 1px solid #CCC; + white-space: nowrap; +} + +.docbarbutton img +{ + border: 0px; + width: 13px; + margin-right: 2px; + vertical-align: middle; + margin-top: 3px; + margin-bottom: 2px; +} + +.menu, +.menu ul { + font-size: 10pt; + list-style-type: none; +} + +.menu ul { + padding-left: 20px; +} + +.menu a { + font-size: 10px; + line-height: 18px; + text-decoration: none; + color: #444; + font-weight: bold; +} + +.docbarbutton.highlight +{ + background-color: #fef2bd; + border: 1px solid #CCC; + border-right: 0px; +} + +#docbarleft { position: absolute; left: 0; top: 0; height: 100%; + overflow: hidden; + background: url(/static/img/jun09/pad/padtop5.gif) no-repeat left -31px; width: 7px; } + + + +#docbarpadtitle { position: absolute; height: auto; left: 9px; + width: 280px; font-size: 1.6em; color: #444; font-weight: normal; + line-height: 22px; margin-left: 2px; height: 22px; top: 2px; + overflow: hidden; text-overflow: ellipsis /*not supported in FF*/; + white-space:nowrap; } +.docbar-public #docbarpadtitle { padding-left: 22px; + background: url(/static/img/jun09/pad/public.gif) no-repeat left center; } + +#docbarrenamelink { position: absolute; top: 6px; + font-size: 1.1em; display: none; } +#docbarrenamelink a { color: #999; } +#docbarrenamelink a:hover { color: #48d; } +#padtitlebuttons { position: absolute; width: 74px; zoom: 1; + height: 17px; top: 4px; left: 170px; display: none; + background: url(/static/img/jun09/pad/ok_or_cancel.gif) 0px 0px; } +#padtitlesave { position: absolute; display: block; + height: 0; padding-top: 17px; overflow: hidden; + width: 23px; left: 0; top: 0; } +#padtitlecancel { position: absolute; display: block; + height: 0; padding-top: 17px; overflow: hidden; + width: 35px; right: 0; top: 0; } +#padtitleedit { position: absolute; top: 2px; left: 5px; + height: 15px; padding: 2px; font-size: 1.4em; + background: white; border-left: 1px solid #c3c3c3; + border-top: 1px solid #c3c3c3; + border-right: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6; + width: 150px; display: none; +} + +#padmain { + margin-top: 6px; + position: absolute; + top: 56px; + left: 0px; + right: 0px; + bottom: 0px; + zoom: 1; +} + +#padeditor { + bottom:0px; + left:0; + position:absolute; + right:300px; + top:0; + zoom: 1; +} +.hidesidebar #padeditor { right: 0; } + +#vdraggie { + background: url(/static/img/jun09/pad/vdraggie.gif) no-repeat top center; + cursor: W-resize; + bottom:0; + position:absolute; + right:268px; + top:0; + width:56px; + z-index: 10; +} + +#editbar { height: 36px; + background: #a5bfe2 url(/static/img/jun09/pad/editbar_background.gif) repeat-x; + height:36px; + left:0; + position:absolute; + right:0; + top:0; + color: #444444; +} + +#editbarinner { + overflow: hidden; +} + +#editbarleft { float: left; height: 100%; overflow: hidden; + background: url(/static/img/jun09/pad/editbar_background_left.gif) no-repeat left top; width: 2px; } +#editbarright { float: right; height: 100%; overflow: hidden; + background: url(/static/img/jun09/pad/editbar_background_right.gif) no-repeat right top; width: 2px; } + +#editbartable +{ + position:absolute; + top: 6px; + left: 7px; + width: 250px; + height: 24px; +} + +#editbarsavetable +{ + position:absolute; + top: 6px; + right: 29px; + height: 24px; +} + +#editbarsavetable td, #editbartable td +{ + white-space: nowrap; +} + +.editbarbutton +{ + border-top: 1px solid #8b9eba; + border-bottom: 1px solid #8b9eba; + border-left: 1px solid #758aa9; + background-color: white; + padding: 0px 2px; +} + +.editbarbutton img +{ + border: 0px; + width: 16px; + height: 16px; +} + +.editbarbutton a { + text-decoration: none; +} +.editbarbutton a:active +{ + position: relative; + top: 1px; + left: 1px; +} + +.editbargroupsfirst +{ + border-left-width: 0px !important; +} + +#editbar #syncstatussyncing { position: absolute; height: 26px; width: 26px; + background: url(/static/img/jun09/pad/syncing2.gif) no-repeat center center; + right: 38px; top: 5px; display: none; } +#editbar #syncstatusdone { position: absolute; height: 26px; width: 26px; + background: url(/static/img/jun09/pad/syncdone.gif) no-repeat center center; + right: 38px; top: 5px; display: none; } + +#editorcontainerbox { + border-left: 1px solid #c4c4c4; border-right: 1px solid #c4c4c4; + border-bottom: 1px solid #c4c4c4; + background: #fff; + bottom:0; + left:0; + overflow: hidden; + position: absolute; + right: 0; + top: 36px; + zoom: 1; + z-index: 11; +} + +#editorcontainer { height: 100%; } + +#editorcontainer iframe { width: 100%; height: 100%; padding: 0; margin: 0; } + +#editorloadingbox { padding-top: 100px; padding-bottom: 100px; font-size: 2.5em; color: #aaa; + text-align: center; position: absolute; width: 100%; height: 30px; z-index: 100; } + +#padsidebar { + bottom: 0px; + position: absolute; + right: 0; + top: 0; + width: 290px; + overflow: hidden; + z-index: 11; +} +.hidesidebar #padsidebar { width: 0; overflow: hidden; } + +#padsidebarfull { + border: 1px solid #c4c4c4; + background: #fafafa; + zoom: 1; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: 8px; +} + +#padusers { + border: 1px solid #c4c4c4; + background: #fafafa; + zoom: 1; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 300px; +} +#myuser { + background: #d9e7f9; + padding: 5px; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 31px; +} +#otherusers { + overflow: auto; + position: absolute; + top: 41px; + left: 0; + right: 0; + bottom: 28px; +} +#userlistbuttonarea { + background: url(/static/img/jun09/pad/inviteshare2.gif) repeat-x 0 0; + position: absolute; + height: 28px; + left: 0; + right: 0; + bottom: 0; +} + +#hdraggie { + background: url(/static/img/jun09/pad/hdraggie.gif) repeat-x center top; + cursor: S-resize; + position: absolute; + top: 300px; + left: 0; + right: 0; + height: 10px; +} + +#padchat { + border: 1px solid #c4c4c4; + position: absolute; + top: 310px; + left: 0; + right: 0; + bottom: 0px; +} +#chatlines { + overflow: auto; + background: #fafafa; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 30px; +} +#chatbottom { + background: #ecf2fa; + padding: 4px; + position: absolute; + height: 22px; + left: 0; + right: 0; + bottom: 0px; +} + +#myswatchbox { position: absolute; left: 5px; top: 5px; width: 22px; height: 22px; + /*border-top: 1px solid #c3cfe0; border-left: 1px solid #c3cfe0; + border-right: 1px solid #ecf3fc; border-bottom: 1px solid #ecf3fc;*/ + border: 1px solid #bbb; + padding: 1px; background: transparent; cursor: pointer; } +#myuser .myswatchboxhoverable, #myuser .myswatchboxunhoverable { + background: white; +} +#myuser .myswatchboxhoverable:hover { + background: #bbb; +} +#myswatch { width: 100%; height: 100%; background: transparent;/*...initially*/ } +#mycolorpicker { + background: url(/static/img/jun09/pad/colorpicker.gif) no-repeat left top; + width: 232px; height: 140px; + position: absolute; + left: 13px; top: 13px; z-index: 101; + display: none;/*...initially*/ +} +#mycolorpicker .n1 { left: 13px; } +#mycolorpicker .n2 { left: 40px; } +#mycolorpicker .n3 { left: 67px; } +#mycolorpicker .n4 { left: 94px; } +#mycolorpicker .n5 { left: 121px; } +#mycolorpicker .n6 { left: 148px; } +#mycolorpicker .n7 { left: 175px; } +#mycolorpicker .n8 { left: 202px; } + +#mycolorpicker .n9 { left: 13px; top: 34px ! important;} +#mycolorpicker .n10 { left: 40px; top: 34px ! important;} +#mycolorpicker .n11 { left: 67px; top: 34px ! important;} +#mycolorpicker .n12 { left: 94px; top: 34px ! important;} +#mycolorpicker .n13 { left: 121px; top: 34px ! important;} +#mycolorpicker .n14 { left: 148px; top: 34px ! important;} +#mycolorpicker .n15 { left: 175px; top: 34px ! important;} +#mycolorpicker .n16 { left: 202px; top: 34px ! important;} + +#mycolorpicker .n17 { left: 13px; top: 56px ! important;} +#mycolorpicker .n18 { left: 40px; top: 56px ! important;} +#mycolorpicker .n19 { left: 67px; top: 56px ! important;} +#mycolorpicker .n20 { left: 94px; top: 56px ! important;} +#mycolorpicker .n21 { left: 121px; top: 56px ! important;} +#mycolorpicker .n22 { left: 148px; top: 56px ! important;} +#mycolorpicker .n23 { left: 175px; top: 56px ! important;} +#mycolorpicker .n24 { left: 202px; top: 56px ! important;} + +#mycolorpicker .n25 { left: 13px; top: 78px ! important;} +#mycolorpicker .n26 { left: 40px; top: 78px ! important;} +#mycolorpicker .n27 { left: 67px; top: 78px ! important;} +#mycolorpicker .n28 { left: 94px; top: 78px ! important;} +#mycolorpicker .n29 { left: 121px; top: 78px ! important;} +#mycolorpicker .n30 { left: 148px; top: 78px ! important;} +#mycolorpicker .n31 { left: 175px; top: 78px ! important;} +#mycolorpicker .n32 { left: 202px; top: 78px ! important;} + +#mycolorpicker .pickerswatchouter { + border: 1px solid white; + width: 15px; height: 15px; position: absolute; + top: 12px; +} +#mycolorpicker .pickerswatch { + border: 1px solid #999; + width: 13px; + height: 13px; + position: absolute; + left: 0; top: 0; +} +#mycolorpicker .picked { border: 1px solid #666 !important; } +#mycolorpicker .picked .pickerswatch { border: 1px solid #666; } +#mycolorpickersave { position: absolute; left: 14px; top: 102px; + width: 47px; height: 0; padding-top: 20px; overflow: hidden; + cursor: pointer; } +#mycolorpickercancel { position: absolute; left: 87px; top: 102px; + width: 44px; height: 0; padding-top: 20px; overflow: hidden; + cursor: pointer; } +#myusernameform { margin-left: 35px; } +#myusernameedit { font-size: 1.6em; color: #444; + padding: 3px; height: 18px; margin: 0; border: 0; + width: 197px; background: transparent; } +#myusernameform input.editable { border: 1px solid #bbb; } +#myuser .myusernameedithoverable:hover { background: white; } +#mystatusform { margin-left: 35px; margin-top: 5px; } +#mystatusedit { font-size: 1.2em; color: #777; + font-style: italic; display: none; + padding: 2px; height: 14px; margin: 0; border: 1px solid #bbb; + width: 199px; background: transparent; } +#myusernameform .editactive, #myusernameform .editempty { + background: white; border-left: 1px solid #c3c3c3; + border-top: 1px solid #c3c3c3; + border-right: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6; +} +#myusernameform .editempty { color: #ef641e; } + +table#otheruserstable { display: none; } +#nootherusers { padding: 10px; font-size: 1.2em; color: #999; font-weight: bold;} +#nootherusers a { color: #48d; } + +#otheruserstable td { + border-bottom: 1px solid #e1e1e1; + height: 26px; + vertical-align: middle; + padding: 0 2px; +} + +#otheruserstable .swatch { + border: 1px solid #999; width: 13px; height: 13px; overflow: hidden; + margin: 0 4px; +} + +.usertdswatch { width: 1%; } +.usertdname { font-size: 1.3em; color: #444; } +.usertdstatus { font-size: 1.1em; font-style: italic; color: #999; } +.usertdactivity { font-size: 1.1em; color: #777; } + +.usertdname input { border: 1px solid #bbb; width: 80px; padding: 2px; } +.usertdname input.editactive, .usertdname input.editempty { + background: white; border-left: 1px solid #c3c3c3; + border-top: 1px solid #c3c3c3; + border-right: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6; +} +.usertdname input.editempty { color: #888; font-style: italic;} + +#sharebutton { + background: url(/static/img/jun09/pad/inviteshare2.gif) no-repeat 0 -31px; + position: absolute; display: block; top: 3px; padding-top: 23px; + height: 0; overflow: hidden; width: 96px; + left:50%; + margin-left: -48px; +} + + /*#guestslabel { font-size: 1.2em; position: absolute; width: auto; + height: 22px; line-height: 22px; top: 4px; left: 8px; } +#guestsmenu { font-size: 1.2em; position: absolute; left: 100px; + top: 5px; width: 95px; } +.guestpolicystuff { display: none; }*/ + +.guestprompt { border: 1px solid #ccc; font-size: 1.2em; + padding: 5px; color: #222; background: #ffc; } +.guestprompt .choices { float: right; } +.guestprompt a { margin: 0 0.5em; } + +#chattop { background: #ecf2fa; padding: 5px; font-size: 1.2em; border-bottom: 1px solid #ddd; } +#chattop a { color: #36b; } +#chatlines .chatline { color: #444; padding-left: 5px; padding-top: 2px; padding-bottom: 2px; + background: #ddd; overflow: hidden; } +#chatlines .chatlinetime { display: block; font-size: 1em; color: #666; float: right; width: auto; + padding-right: 5px; } +#chatlines .chatlinename, #chatlines .chatlinetext { font-size: 1.2em; } +#chatlines h2 { margin: 0; padding-left: 5px; padding-top: 2px; padding-bottom: 2px; color: #999; font-style: italic; font-weight: bold; font-size: 1.2em; } +#chatprompt { font-size: 1.2em; color: #444; float: left; line-height: 22px; width: 35px; text-align: right; } +#chatentryform { margin-left: 40px; } +#chatentrybox { font-size: 1.2em; color: #444; + padding: 2px; height: 16px; margin: 0; border-left: 1px solid #c3c3c3; + border-top: 1px solid #c3c3c3; + border-right: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6; + width: 230px; } +#padchat a#chatloadmore { display: none; font-size: 1.2em; padding: 2px 5px; font-style: italic; } +#padchat #chatloadingmore { display: none; font-size: 1.2em; padding: 2px 5px; font-style: italic; + color: #999; } +#padchat a#chatloadmore:focus { outline: 0; } + +#djs { font-family: monospace; font-size: 10pt; + height: 200px; overflow: auto; border: 1px solid #ccc; + background: #fee; margin: 0; padding: 6px; +} +#djs p { margin: 0; padding: 0; display: block; } + +.modaldialog.cboxreconnecting .modaldialog-inner, +.modaldialog.cboxconnecting .modaldialog-inner { + background: url(/static/img/jun09/pad/connectingbar.gif) no-repeat center 60px; + height: 100px; +} +.modaldialog.cboxreconnecting { + background: #fed; +} +.modaldialog.cboxconnecting, +.modaldialog.cboxdisconnected { + background: #fdd; +} +.cboxdisconnected #connectionboxinner div { display: none; } +.cboxdisconnected_userdup #connectionboxinner #disconnected_userdup { display: block; } +.cboxdisconnected_initsocketfail #connectionboxinner #disconnected_initsocketfail { display: block; } +.cboxdisconnected_looping #connectionboxinner #disconnected_looping { display: block; } +.cboxdisconnected_slowcommit #connectionboxinner #disconnected_slowcommit { display: block; } +.cboxdisconnected_unauth #connectionboxinner #disconnected_unauth { display: block; } +.cboxdisconnected_unknown #connectionboxinner #disconnected_unknown { display: block; } +.cboxdisconnected_initsocketfail #connectionboxinner #reconnect_advise, +.cboxdisconnected_looping #connectionboxinner #reconnect_advise, +.cboxdisconnected_slowcommit #connectionboxinner #reconnect_advise, +.cboxdisconnected_unknown #connectionboxinner #reconnect_advise { display: block; } +.cboxdisconnected div#reconnect_form { display: block; } +.cboxdisconnected .disconnected h2 { display: none; } +.cboxdisconnected .disconnected .h2_disconnect { display: block; } +.cboxdisconnected_userdup .disconnected h2.h2_disconnect { display: none; } +.cboxdisconnected_userdup .disconnected h2.h2_userdup { display: block; } +.cboxdisconnected_unauth .disconnected h2.h2_disconnect { display: none; } +.cboxdisconnected_unauth .disconnected h2.h2_unauth { display: block; } + +#connectionstatus { + position: absolute; width: 37px; height: 41px; overflow: hidden; + right: 0; + z-index: 11; +} +#connectionboxinner .connecting { + margin-top: 20px; + font-size: 2.0em; color: #555; + text-align: center; display: none; +} +.cboxconnecting #connectionboxinner .connecting { display: block; } + +#connectionboxinner .disconnected h2 { + font-size: 1.8em; color: #333; + text-align: left; + margin-top: 10px; margin-left: 10px; margin-right: 10px; + margin-bottom: 10px; +} +#connectionboxinner .disconnected p { + margin: 10px 10px; + font-size: 1.2em; + line-height: 1.1; + color: #333; +} +#connectionboxinner .disconnected { display: none; } +.cboxdisconnected #connectionboxinner .disconnected { display: block; } + +#connectionboxinner .reconnecting { + margin-top: 20px; + font-size: 1.6em; color: #555; + text-align: center; display: none; +} +.cboxreconnecting #connectionboxinner .reconnecting { display: block; } + +#reconnect_form button { + font-size: 12pt; +} + +/* We give docbar a higher z-index than its descendant impexp-wrapper in + order to allow the Import/Export panel to be on top of stuff lower + down on the page in IE. Strange but it works! */ +#docbar { z-index: 52; } + +#impexp-wrapper { width: 650px; right: 10px; } +#impexp-panel { height: 160px; } +.docbarimpexp-closing #impexp-wrapper { z-index: 50; } + +#savedrevs-wrapper { width: 100%; left: 0; } +#savedrevs-panel { height: 79px; } +.docbarsavedrevs-closing #savedrevs-wrapper { z-index: 50; } +#savedrevs-wrapper .dbpanel-rightedge { background-position: 0 -10px; } + +#options-wrapper { width: 340px; right: 200px; } +#options-panel { height: 114px; } +.docbaroptions-closing #options-wrapper { z-index: 50; } + +#security-wrapper { width: 320px; right: 300px; } +#security-panel { height: 130px; } +.docbarsecurity-closing #security-wrapper { z-index: 50; } + +#revision-notifier { position: absolute; right: 8px; top: 25px; + width: auto; height: auto; font-size: 1.2em; background: #ffc; + border: 1px solid #aaa; color: #444; padding: 3px 5px; + display: none; z-index: 55; } +#revision-notifier .label { color: #777; font-weight: bold; } + +/* We don't ever actually hide the wrapper, even when the panel is + cloased, so that its contents can always be manipulated accurately. */ +.dbpanel-wrapper { position: absolute; + overflow: hidden; /* animated: */ height: 0; top: 25px; /* /animated */ + z-index: 51; zoom: 1; } +.dbpanel-panel { position: absolute; bottom: 0; width: 100%; } + +.dbpanel-middle { margin-left: 7px; margin-right: 7px; + position: relative; height: 100%; overflow: hidden; zoom: 1; } +.dbpanel-inner { background: #f7f7f7 /* covered up by images */; + width: 100%; height: 100%; position: absolute; overflow: hidden; top: -10px; } + +.dbpanel-top { position: absolute; top: 0; width: 100%; + height: 400px; background-image: url(/static/img/jun09/pad/docpanelmiddle2.png); + background-position: left top; } + +.dbpanel-bottom { position: absolute; height: 400px; + bottom: -390px; width: 100%; + background-image: url(/static/img/jun09/pad/docpanelmiddle2.png); + background-position: left top; +} + +* html .dbpanel-top, * html .dbpanel-bottom { /* for IE 6+ */ + background-color: transparent; + background-image: url(/static/img/apr09/blank.gif); + /* scale the image instead of repeating, but it amounts to the same */ + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/static/img/jun09/pad/docpanelmiddle2.png", sizingMethod="scale"); +} + +.dbpanel-leftedge, .dbpanel-rightedge, .dbpanel-botleftcorner, .dbpanel-botrightcorner { + position: absolute; + background-repeat: no-repeat; + background-color: transparent; + background-image: url(/static/img/jun09/pad/docpaneledge2.png); +} + +.dbpanel-leftedge, .dbpanel-rightedge { height: 100%; width: 7px; bottom: 11px; } +.dbpanel-botleftcorner, .dbpanel-botrightcorner { height: 11px; width: 7px; bottom: 0; } + +.dbpanel-leftedge, .dbpanel-botleftcorner { left: 0; background-position: -7px 0; } +.dbpanel-rightedge, .dbpanel-botrightcorner { right: 0; background-position: 0 0; } + +#importexport { position: absolute; top: 5px; left: 0; font-size: 1.2em; color: #444; + height: 100%; width: 100%; } + +* html .dbpanel-leftedge, * html .dbpanel-rightedge, * html .dbpanel-botleftcorner, * html .dbpanel-botrightcorner { + background-color: transparent; + background-image: url(/static/img/apr09/blank.gif); + /* crop the image */ + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/static/img/jun09/pad/docpaneledge2.png", sizingMethod="crop"); +} +* html .dbpanel-leftedge, * html .dbpanel-botleftcorner { left: -7px; width: 14px; } + +#impexp-importlabel { position: absolute; top: 5px; left: 10px; width: 300px; } + +#importform { position: absolute; top: 24px; left: 5px; width: 300px; height: 60px; } +#importformsubmitdiv, #importformfilediv { padding: 5px 5px; } +#importexport .importformenabled { + background: #cfc; + border: 1px solid #292; +} +#importexport span.nowrap { white-space: nowrap; } +#importexport #importstatusball { margin-left: 3px; padding-top: 1px; display: none; } +#importexport #importarrow { margin-left: 5px; padding-top: 1px; display: none; } +#importexport .importmessage { border: 1px solid #992; + background: #ffc; padding: 5px; font-size: 85%; display: none; } +#importexport #importmessagefail { margin-top: 5px; } +#importexport #importmessagesuccess { margin: 0 20px; } +#importexport a.disabledexport { + color: #333; text-decoration: none; + opacity: 0.5; filter: alpha(opacity = 50) /*IE*/; +} +#importexport #importfileinput { padding: 2px 0; } +#importexport #importsubmitinput { padding: 2px; } + +#impexp-divider { position: absolute; left: 320px; top: 5px; height: 135px; width: 2px; + background: #ddd; } +#impexp-close { display: block; position: absolute; right: 2px; bottom: 15px; + width: auto; height: auto; font-size: 85%; color: #444; + z-index: 61 /* > clickcatcher */} +#impexp-disabled-clickcatcher { + display: none; + position: absolute; width: 100%; height: 100%; + z-index: 60; +} + +#impexp-exportlabel { position: absolute; top: 5px; left: 350px; + width: 300px; } +#exportlinks .exportlink { + display: block; position: absolute; height: 22px; width: auto; + background-repeat: no-repeat; + background-image: url(/static/img/jun09/pad/fileicons.gif); + line-height: 22px; padding-left: 22px; padding-right: 2px; +} +#exportlinks .n1 { left: 350px; top: 30px; } +#exportlinks .n2 { left: 350px; top: 57px; } +#exportlinks .n3 { left: 350px; top: 84px; } +#exportlinks .n4 { left: 485px; top: 30px; } +#exportlinks .n5 { left: 485px; top: 57px; } +#exportlinks .n6 { left: 485px; top: 84px; } +#exportlinks .exporthrefdoc { background-position: 2px -1px; } +#exportlinks .exporthrefhtml { background-position: 2px -25px; } +#exportlinks .exporthreflink { background-position: 2px -49px; } +#exportlinks .exporthrefodt { background-position: 2px -73px; } +#exportlinks .exporthrefpdf { background-position: 2px -97px; } +#exportlinks .exporthreftxt { background-position: 2px -121px; } + +#savedrevisions { position: absolute; top: 0; left: 0; font-size: 1.2em; + color: #444; height: 100%; width: 100%; } +#savedrevs-scrolly { height: 75px; width: auto; margin-right: 136px; + overflow: hidden; position: relative; top: 1px; +} +#savedrevs-scrollleft { height: 100%; width: 14px; position: absolute; + left: 0; top: 0; cursor: pointer; + background: url(/static/img/jun09/pad/savedrevarrows.gif) no-repeat right top; +} +#savedrevs-scrollright { height: 100%; width: 14px; position: absolute; + right: 0; top: 0; cursor: pointer; + background: url(/static/img/jun09/pad/savedrevarrows.gif) no-repeat left top; +} +#savedrevs-scrolly .disabledscrollleft { background-position: right bottom; } +#savedrevs-scrolly .disabledscrollright { background-position: left bottom; } +#savedrevs-scrollouter { margin-left: 14px; margin-right: 14px; + width: auto; height: 100%; overflow: hidden; position: relative; +} +#savedrevs-scrollinner { position: absolute; width: 1px; height: 100%; + overflow: visible; right: 0/*...initially*/; top: 0; } +#savedrevisions .srouterbox { width: 120px; height: 100%; + position: absolute; top: 0; +} +#savedrevisions .srinnerbox { position: relative; top: 8px; + height: 59px; width: auto; border-left: 1px solid #ddd; + padding: 0 8px 0 8px; } +#savedrevisions a.srname { display: block; white-space: nowrap; + text-overflow: ellipsis /*no FF support*/; overflow: hidden; + text-decoration: none; color: #444; cursor: text; + padding: 1px; height: 14px; position: relative; left: -1px; + width: 100px /*specify for proper overflow in IE*/; +} +#savedrevisions a.srname:hover { text-decoration: none; color: #444; + border: 1px solid #ccc; padding: 0; } +#savedrevisions .sractions { font-size: 85%; color: #ccc; + margin-top: 1px; height: 12px; } +#savedrevisions .sractions a { text-decoration: none; + color: #06c; } +#savedrevisions .sractions a:hover { text-decoration: underline; } +#savedrevisions .srtime { color: #666; font-size: 90%; + white-space: nowrap; margin-top: 3px; } +#savedrevisions .srauthor { color: #666; font-size: 90%; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis /*no FF*/; +} +#savedrevisions .srtwirly { position: absolute; display: block; + bottom: 0; right: 10px; display: none; } +#savedrevisions .srnameedit { + position: absolute; +} +#savedrevs-savenow { display: block; position: absolute; + overflow: hidden; height: 0; padding-top: 24px; width: 81px; + top: 22px; right: 27px; + background: url(/static/img/jun09/pad/savedrevsgfx2.gif) no-repeat 0 0; +} +#savedrevs-savenow:active { background-position: 0 -24px; } +#savedrevs-close { display: block; position: absolute; right: 7px; bottom: 8px; + width: auto; height: auto; font-size: 85%; color: #444; } +form#reconnectform { display: none; } + +#padoptions { position: absolute; top: 0; left: 0; font-size: 1.2em; + color: #444; height: 100%; width: 100%; line-height: 15px; } +#options-viewhead { font-weight: bold; position: absolute; top: 10px; left: 15px; + width: auto; height: auto; } +#padoptions label { display: block; } +#padoptions input { padding: 0; margin: 0; } +#options-colorscheck { position: absolute; left: 15px; top: 34px; width: 15px; height: 15px; } +#options-colorslabel { position: absolute; left: 35px; top: 34px; } +#options-linenoscheck { position: absolute; left: 15px; top: 57px; width: 15px; height: 15px; } +#options-linenoslabel { position: absolute; left: 35px; top: 57px; } +#options-fontlabel { position: absolute; left: 15px; top: 82px; } +#viewfontmenu { position: absolute; top: 80px; left: 90px; width: 110px; } +#options-viewexplain { position: absolute; left: 215px; top: 15px; width: 100px; height: 70px; font-size: .7em; + padding-left: 10px; padding-top: 10px; border-left: 1px solid #ccc; + line-height: 20px; font-weight: bold; color: #999; } +#options-close { display: block; position: absolute; right: 7px; bottom: 8px; + width: auto; height: auto; font-size: 85%; color: #444; } + +#padsecurity { position: absolute; top: 0; left: 0; font-size: 1.1em; + color: #444; height: 100%; width: 100%; line-height: 15px; } +#security-close { display: block; position: absolute; right: 7px; bottom: 8px; + width: auto; height: auto; font-size: 85%; color: #444; } +#security-passhead { font-weight: bold; position: absolute; top: 90px; left: 15px; + width: auto; height: auto; } +#security-passbody { position: absolute; left: 75px; top: 90px; } +#security-passwordedit { height: 15px; border: 1px solid #bbb; + position: absolute; top: 0; left: 15px; width: 120px; } +#security-password a { text-decoration: none; display: block; + width: auto; height: auto; } +#password-savelink, #password-cancellink {position: absolute; top: 0; } +#security-password a:hover { text-decoration: underline; } +#password-savelink { left: 144px; color: #06c; } +#password-cancellink { left: 180px; color: #666; } +#password-nonedit { left: 15px; position: absolute; + width: 220px; top: 0; } +#password-setlink { color: #06c; } +#password-clearlink { color: #06c; } +#password-display { height: 15px; width: auto; } +#password-inedit { display: none; } +#password-display, #password-setlink, #password-clearlink { + float: left; margin-right: 10px; +} +#password-display { font-size: 18px; } +#security-password .nopassword #password-display { font-size: 100%; } +#security-password .nopassword #password-clearlink { display: none; } +#security-password .nopassword #password-setlink { left: 60px; } + +#security-access { position: absolute; left: 15px; width: 200px; } +#security-accesshead { font-weight: bold; position: absolute; top: 10px; + left: 0; width: auto; height: auto; } +#security-access input, #security-access label { position: absolute; } +#security-access input { left: 10px; } +#security-access label { left: 30px; width: 250px; } +#access-private, #access-private-label { top: 35px; } +#access-public, #access-public-label { top: 60px; } +#security-access label { color: #999; } +#security-access label strong { font-weight: normal; padding-right: 10px; + color: #444; } + +#mainmodals { z-index: 600; /* higher than the modals themselves + so that modals are on top in IE */ } + +.modalfield { font-size: 1.2em; padding: 1px; border: 1px solid #bbb;} +#mainmodals .editempty { color: #aaa; } + + +#feedbackbox { + position: absolute; display: none; + width: 400px; height: 270px; + left: 100px/*set in code*/; bottom: 50px; + z-index: 501; zoom: 1; +} +#feedbackbox-tl, #feedbackbox-tr, #feedbackbox-bl, #feedbackbox-br, +#feedbackbox-hide, #feedbackbox-send, #feedbackbox-back { + position: absolute; display: block; + background-repeat: no-repeat; + background-image: url(/static/img/jun09/pad/feedbackbox2.gif); +} +#feedbackbox-tl { width: 392px; + height: 262px; left: 0; top: 0; + background-position: left top; } +#feedbackbox-tr { width: 8px; height: 262px; + right: 0; top: 0; background-position: right top; } +#feedbackbox-bl { width: 392px; + height: 8px; left: 0; bottom: 0; + background-position: left bottom; } +#feedbackbox-br { width: 8px; height: 8px; bottom: 0; right: 0; + background-position: right bottom; } +#feedbackbox-hide { width: 22px; height: 22px; right: 9px; top: 7px; + background-position: -569px -6px; +} +#feedbackbox-back { width: 384px; + height: 254px; left: 8px; top: 8px; + background-position: -8px -8px; + background-color: white; } +#feedbackbox-contents { width: 384px; + height: 254px; left: 8px; top: 8px; + position: absolute; font-size: 1.4em; color: #444; } +#feedbackbox-contentsinner { padding: 10px; } +#feedbackbox-send { width: 50px; height: 22px; right: 15px; bottom: 15px; + background-position: -535px -363px; +} +#feedbackbox-email { left: 90px; top: 48px; width: 356px; height: auto; } +#feedbackbox-message { left: 90px; top: 84px; width: 358px; height: 100px; } +#feedbackbox-response { position: absolute; bottom: 15px; left: 15px; + width: 390px; height: auto; font-size: 1.2em; display: none; } +#feedbackbox .goodresponse { font-weight: bold; color: green; } +#feedbackbox .badresponse { font-weight: bold; color: red; } +#feedbackbox p { margin-bottom: 1em; } +#feedbackbox ul { margin: 1em 0 1em 2em } +#feedbackbox li { padding: 0.3em 0; } +#feedbackbox li a { display: block; font-weight: bold; } +#feedbackbox li a:hover { background: #ffe; } +#feedbackbox a, #feedbackbox li a:visited { color: #47b; } +#feedbackbox tt { font-size: 110%; } + +.expand-collapse { + height: 22px; + background-image: url(/static/img/jun09/pad/sharedistri.gif); + background-repeat: no-repeat; + background-position: 0 3px; + padding-left: 17px; + text-decoration: none; +} +.expand-collapse.expanded { + background-position: 0 -31px; +} + + +.modaldialog { + position: absolute; + top: 100px; + left:50%; + margin-left:-243px; + width: 485px; + display: none; + z-index: 501; + zoom: 1; + overflow: hidden; + background: white; + border: 1px solid #999; +} +.modaldialog .modaldialog-inner { padding: 10pt; } +.modaldialog .modaldialog-inner * { margin: 2pt; } +.modaldialog .modaldialog-hide { + float: right; + background-repeat: no-repeat; + background-image: url(/static/img/jun09/pad/sharebox4.gif); + display: block; + width: 22px; height: 22px; + background-position: -454px -6px; + margin-right:-5px; + margin-top:-5px; +} + +.modaldialog label, +.modaldialog h1 { + color:#222222; + font-size:125%; + font-weight:bold; +} + +.modaldialog th { + vertical-align: top; + text-align: left; +} + +.nonprouser #sharebox-stripe { display: none; } + +.sharebox-url { + width: 440px; height: 18px; + text-align: left; + font-size: 1.3em; + line-height: 18px; + padding: 2px; +} + +#sharebox-send { + float: right; + background-repeat: no-repeat; + background-image: url(/static/img/jun09/pad/sharebox4.gif); + display: block; + width: 87px; height: 22px; + background-position: -383px -289px; +} + + +#viewbarcontents { display: none; } +#viewzoomtitle { + position: absolute; left: 10px; top: 4px; height: 20px; line-height: 20px; + width: auto; +} +#viewzoommenu { + width: 65px; +} +#bottomarea { + height: 28px; + overflow: hidden; + position: absolute; + height: 28px; + bottom: 0px; + left: 0px; + right: 0px; + font-size: 1.2em; + color: #444; +} +#widthprefcheck { position: absolute; + background-image: url(/static/img/jun09/pad/layoutbuttons.gif); + background-repeat: no-repeat; cursor: pointer; + width: 86px; height: 20px; top: 4px; right: 2px; } +.widthprefunchecked { background-position: -1px -1px; } +.widthprefchecked { background-position: -1px -23px; } +#sidebarcheck { position: absolute; + background-image: url(/static/img/jun09/pad/layoutbuttons.gif); + background-repeat: no-repeat; cursor: pointer; + width: 86px; height: 20px; top: 4px; right: 90px; } +.sidebarunchecked { background-position: -1px -45px; } +.sidebarchecked { background-position: -1px -67px; } +#feedbackbutton { display: block; position: absolute; width: 68px; + height: 0; padding-top: 17px; overflow: hidden; + background: url(/static/img/jun09/pad/bottomareagfx.gif); + top: 5px; right: 220px; +} + +#modaloverlay { + z-index: 500; display: none; + background-image: url(/static/img/jun09/pad/overlay2.png); + background-repeat: repeat-both; + width: 100%; position: absolute; + height: 100%; left: 0; top: 0; +} + +* html #modaloverlay { /* for IE 6+ */ + opacity: 1; /* in case this is looked at */ + background-image: none; + background-repeat: no-repeat; + /* scale the image */ + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/static/img/jun09/pad/overlay2.png", sizingMethod="scale"); +} + +a#topbarmaximize { + float: right; + width: 16px; + height: 16px; + margin-right:-143px; + margin-top:4px; + background: url(/static/img/jun09/pad/maximize_normal.png); +} + +.maximized a#topbarmaximize { + background: url(/static/img/jun09/pad/maximize_maximized.png); +} + +#editbarinner h1 { + line-height: 29px; + font-size: 16px; + padding-left: 6pt; + margin-top: 0; +} + +#editbarinner h1 a { + font-size: 12px; +} + +.bigbutton { + display: block; + background-color: #a3bde0; + color: #555555; + border-style: solid; + border-width: 2px; + border-left-color: #d6e2f1; + border-right-color: #86aee1; + border-top-color: #d6e2f1; + border-bottom-color: #86aee1; + margin: 10pt; + text-align: center; + text-decoration: none; + padding: 50pt; + font-size: 20pt; + -moz-border-radius-topleft: 3pt; + -moz-border-radius-topright: 3pt; + -moz-border-radius-bottomleft: 3pt; + -moz-border-radius-bottomright: 3pt; + -webkit-border-top-left-radius: 3pt; + -webkit-border-top-right-radius: 3pt; + -webkit-border-bottom-left-radius: 3pt; + -webkit-border-bottom-right-radius: 3pt; +} + +.modaldialog .bigbutton { + padding-left: 0; + padding-right: 0; + width: 100%; +} diff --git a/static/css/syntax.css b/static/css/syntax.css new file mode 100644 index 000000000..e018320ef --- /dev/null +++ b/static/css/syntax.css @@ -0,0 +1,32 @@ +/* ---------- Used by JavaScript Lexer ---------- */ +.syntax .c { color: #bd3f00; font-style: italic } /* Comment */ +.syntax .o { font-weight: bold; } /* Operator */ +.syntax .p { font-weight: bold; } /* Punctuation */ +.syntax .k { color: blue; } /* Keyword */ +.syntax .kc { color: purple } /* Keyword.Constant */ +.syntax .nx { } /* Name.Other */ +.syntax .mf { color: purple } /* Literal.Number.Float */ +.syntax .mh { color: purple } /* Literal.Number.Hex */ +.syntax .mi { color: purple } /* Literal.Number.Integer */ +.syntax .sr { color: purple } /* Literal.String.Regex */ +.syntax .s2 { color: purple } /* Literal.String.Double */ +.syntax .s1 { color: purple } /* Literal.String.Single */ +.syntax .sd { color: purple } /* Literal.String.Doc */ +.syntax .cs { color: #00aa33; font-weight: bold; font-style: italic } /* Comment.Special */ +.syntax .err { color: #cc0000; font-weight: bold; text-decoration: underline; } /* Error */ + +/* css */ +.syntax .nt { font-weight: bold; } /* tag */ +.syntax .nc { color: #336; } /* class */ +.syntax .nf { color: #336; } /* id */ +.syntax .nd { color: #999; } /* :foo */ +.syntax .m { color: purple } /* number */ +.syntax .nb { color: purple } /* built-in */ +.syntax .cp { color: #bd3f00; } /* !important */ + +.syntax .flash { background-color: #adf !important; } +.syntax .flashbad { background-color: #f55 !important; } + +/*.syntax .test { background-color: #0f0; }*/ +/*.syntax .test { background: url(http://dl.getdropbox.com/u/88/blackvert.gif) + repeat-y left top; }*/ diff --git a/static/img/jun09/pad/backgrad.gif b/static/img/jun09/pad/backgrad.gif new file mode 100644 index 000000000..8fee1a5b7 Binary files /dev/null and b/static/img/jun09/pad/backgrad.gif differ diff --git a/static/img/jun09/pad/colorpicker.gif b/static/img/jun09/pad/colorpicker.gif new file mode 100644 index 000000000..effa3cc01 Binary files /dev/null and b/static/img/jun09/pad/colorpicker.gif differ diff --git a/static/img/jun09/pad/docbarstates.png b/static/img/jun09/pad/docbarstates.png new file mode 100644 index 000000000..a489b87e2 --- /dev/null +++ b/static/img/jun09/pad/docbarstates.png @@ -0,0 +1,1077 @@ + + + + + + + + + + + + + docbarstates.png - + etherpad - + + Project Hosting on Google Code + + + + + + + + + + + + + + + + + + + +
+
+ + + + My favorites + | Sign in + + + +
+
+
+ + +
+ + + + + + + + + + + + +
+ +
+ etherpad +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ Project Home + + + + + + + Wiki + + + + + + + + Source + + + +
+
+ + + + + + + + + + + + + + +
+
+
+ +
+ + Repository: + +
+ + + + Checkout   + Browse   + Changes   + Clones   +
+ +   + + + +
+
+
+ +
+ + + +
+ + + + + + + +
+
+ + + + + + + + + + + +
+ + +

+ + This file is not plain text (only UTF-8 and Latin-1 + text encodings are currently supported). + +

+ + +
+ +
+ +
+
+
+
+
+
+

Change log

+
+ 81287c8af1 + by Aaron Iba <aaroniba> + on Dec 17, 2009 +   Diff +
+
removed duplicate trunk directory
+
+ + + + + + + + + + +
+
Go to:  + +
+ + + + + + +
+ Project members, + sign in to write a code review
+ + + +
+ + +
+
+
+
+
+
+
+
+
+
+
+

Older revisions

+ + +
+ + + a9f8774a2e + by Aaron Iba <aaroniba> + on Dec 17, 2009 +   Diff +
+
initial code push
+
+ + + All revisions of this file +
+
+
+
+
+
+
+
+
+
+
+
+

File info

+ +
Size: 3314 bytes
+ + +
+ +
+
+
+
+
+
+
+ + +
+ +
+
+ + + + + + + + + + + + + + +
+ +
+ Powered by Google Project Hosting +
+ + + + + + + + diff --git a/static/img/jun09/pad/docpaneledge2.png b/static/img/jun09/pad/docpaneledge2.png new file mode 100644 index 000000000..c119c74f3 Binary files /dev/null and b/static/img/jun09/pad/docpaneledge2.png differ diff --git a/static/img/jun09/pad/docpanelmiddle2.png b/static/img/jun09/pad/docpanelmiddle2.png new file mode 100644 index 000000000..d251c2393 Binary files /dev/null and b/static/img/jun09/pad/docpanelmiddle2.png differ diff --git a/static/img/jun09/pad/editbar_background.gif b/static/img/jun09/pad/editbar_background.gif new file mode 100644 index 000000000..54ef6e484 Binary files /dev/null and b/static/img/jun09/pad/editbar_background.gif differ diff --git a/static/img/jun09/pad/editbar_background_left.gif b/static/img/jun09/pad/editbar_background_left.gif new file mode 100644 index 000000000..fe8d06ec4 Binary files /dev/null and b/static/img/jun09/pad/editbar_background_left.gif differ diff --git a/static/img/jun09/pad/editbar_background_right.gif b/static/img/jun09/pad/editbar_background_right.gif new file mode 100644 index 000000000..55ab00a6b Binary files /dev/null and b/static/img/jun09/pad/editbar_background_right.gif differ diff --git a/static/img/jun09/pad/editbar_bold.gif b/static/img/jun09/pad/editbar_bold.gif new file mode 100644 index 000000000..d22bcafbb Binary files /dev/null and b/static/img/jun09/pad/editbar_bold.gif differ diff --git a/static/img/jun09/pad/editbar_clearauthorship.gif b/static/img/jun09/pad/editbar_clearauthorship.gif new file mode 100644 index 000000000..2c6d1098b Binary files /dev/null and b/static/img/jun09/pad/editbar_clearauthorship.gif differ diff --git a/static/img/jun09/pad/editbar_groupleft.gif b/static/img/jun09/pad/editbar_groupleft.gif new file mode 100644 index 000000000..3e18749cf Binary files /dev/null and b/static/img/jun09/pad/editbar_groupleft.gif differ diff --git a/static/img/jun09/pad/editbar_groupright.gif b/static/img/jun09/pad/editbar_groupright.gif new file mode 100644 index 000000000..bf8b7576d Binary files /dev/null and b/static/img/jun09/pad/editbar_groupright.gif differ diff --git a/static/img/jun09/pad/editbar_indent.gif b/static/img/jun09/pad/editbar_indent.gif new file mode 100644 index 000000000..989523acf Binary files /dev/null and b/static/img/jun09/pad/editbar_indent.gif differ diff --git a/static/img/jun09/pad/editbar_insertunorderedlist.gif b/static/img/jun09/pad/editbar_insertunorderedlist.gif new file mode 100644 index 000000000..b032d5936 Binary files /dev/null and b/static/img/jun09/pad/editbar_insertunorderedlist.gif differ diff --git a/static/img/jun09/pad/editbar_italic.gif b/static/img/jun09/pad/editbar_italic.gif new file mode 100644 index 000000000..a0174026c Binary files /dev/null and b/static/img/jun09/pad/editbar_italic.gif differ diff --git a/static/img/jun09/pad/editbar_outdent.gif b/static/img/jun09/pad/editbar_outdent.gif new file mode 100644 index 000000000..4b9bf3897 Binary files /dev/null and b/static/img/jun09/pad/editbar_outdent.gif differ diff --git a/static/img/jun09/pad/editbar_redo.gif b/static/img/jun09/pad/editbar_redo.gif new file mode 100644 index 000000000..826a25471 Binary files /dev/null and b/static/img/jun09/pad/editbar_redo.gif differ diff --git a/static/img/jun09/pad/editbar_save.gif b/static/img/jun09/pad/editbar_save.gif new file mode 100644 index 000000000..2ccced678 Binary files /dev/null and b/static/img/jun09/pad/editbar_save.gif differ diff --git a/static/img/jun09/pad/editbar_strikethrough.gif b/static/img/jun09/pad/editbar_strikethrough.gif new file mode 100644 index 000000000..92ffa2383 Binary files /dev/null and b/static/img/jun09/pad/editbar_strikethrough.gif differ diff --git a/static/img/jun09/pad/editbar_underline.gif b/static/img/jun09/pad/editbar_underline.gif new file mode 100644 index 000000000..ec3cc4e82 Binary files /dev/null and b/static/img/jun09/pad/editbar_underline.gif differ diff --git a/static/img/jun09/pad/editbar_undo.gif b/static/img/jun09/pad/editbar_undo.gif new file mode 100644 index 000000000..78ae0bea7 Binary files /dev/null and b/static/img/jun09/pad/editbar_undo.gif differ diff --git a/static/img/jun09/pad/feedbackbox2.gif b/static/img/jun09/pad/feedbackbox2.gif new file mode 100644 index 000000000..f1b8f5b79 Binary files /dev/null and b/static/img/jun09/pad/feedbackbox2.gif differ diff --git a/static/img/jun09/pad/fileicons.gif b/static/img/jun09/pad/fileicons.gif new file mode 100644 index 000000000..26f63882b Binary files /dev/null and b/static/img/jun09/pad/fileicons.gif differ diff --git a/static/img/jun09/pad/hdraggie.gif b/static/img/jun09/pad/hdraggie.gif new file mode 100644 index 000000000..d1f7dc163 Binary files /dev/null and b/static/img/jun09/pad/hdraggie.gif differ diff --git a/static/img/jun09/pad/icon_import_export.gif b/static/img/jun09/pad/icon_import_export.gif new file mode 100644 index 000000000..b8e3417d8 Binary files /dev/null and b/static/img/jun09/pad/icon_import_export.gif differ diff --git a/static/img/jun09/pad/icon_pad_options.gif b/static/img/jun09/pad/icon_pad_options.gif new file mode 100644 index 000000000..a89cec929 Binary files /dev/null and b/static/img/jun09/pad/icon_pad_options.gif differ diff --git a/static/img/jun09/pad/icon_saved_revisions.gif b/static/img/jun09/pad/icon_saved_revisions.gif new file mode 100644 index 000000000..7ba9bcaf2 Binary files /dev/null and b/static/img/jun09/pad/icon_saved_revisions.gif differ diff --git a/static/img/jun09/pad/icon_time_slider.gif b/static/img/jun09/pad/icon_time_slider.gif new file mode 100644 index 000000000..a8dea0061 Binary files /dev/null and b/static/img/jun09/pad/icon_time_slider.gif differ diff --git a/static/img/jun09/pad/inviteshare2.gif b/static/img/jun09/pad/inviteshare2.gif new file mode 100644 index 000000000..98d4c8597 Binary files /dev/null and b/static/img/jun09/pad/inviteshare2.gif differ diff --git a/static/img/jun09/pad/maximize_maximized.png b/static/img/jun09/pad/maximize_maximized.png new file mode 100644 index 000000000..d1ae8f87e Binary files /dev/null and b/static/img/jun09/pad/maximize_maximized.png differ diff --git a/static/img/jun09/pad/maximize_normal.png b/static/img/jun09/pad/maximize_normal.png new file mode 100644 index 000000000..faa0c86fc Binary files /dev/null and b/static/img/jun09/pad/maximize_normal.png differ diff --git a/static/img/jun09/pad/ok_or_cancel.gif b/static/img/jun09/pad/ok_or_cancel.gif new file mode 100644 index 000000000..76ba69244 Binary files /dev/null and b/static/img/jun09/pad/ok_or_cancel.gif differ diff --git a/static/img/jun09/pad/overlay.png b/static/img/jun09/pad/overlay.png new file mode 100644 index 000000000..89b6daacd --- /dev/null +++ b/static/img/jun09/pad/overlay.png @@ -0,0 +1,1077 @@ + + + + + + + + + + + + + overlay.png - + etherpad - + + Project Hosting on Google Code + + + + + + + + + + + + + + + + + + + +
+
+ + + + My favorites + | Sign in + + + +
+
+
+ + +
+ + + + + + + + + + + + +
+ +
+ etherpad +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ Project Home + + + + + + + Wiki + + + + + + + + Source + + + +
+
+ + + + + + + + + + + + + + +
+
+
+ +
+ + Repository: + +
+ + + + Checkout   + Browse   + Changes   + Clones   +
+ +   + + + +
+
+
+ +
+ + + +
+ + + + + + + +
+
+ + + + + + + + + + + +
+ + +

+ + This file is not plain text (only UTF-8 and Latin-1 + text encodings are currently supported). + +

+ + +
+ +
+ +
+
+
+
+
+
+

Change log

+
+ 81287c8af1 + by Aaron Iba <aaroniba> + on Dec 17, 2009 +   Diff +
+
removed duplicate trunk directory
+
+ + + + + + + + + + +
+
Go to:  + +
+ + + + + + +
+ Project members, + sign in to write a code review
+ + + +
+ + +
+
+
+
+
+
+
+
+
+
+
+

Older revisions

+ + +
+ + + a9f8774a2e + by Aaron Iba <aaroniba> + on Dec 17, 2009 +   Diff +
+
initial code push
+
+ + + All revisions of this file +
+
+
+
+
+
+
+
+
+
+
+
+

File info

+ +
Size: 141 bytes
+ + +
+ +
+
+
+
+
+
+
+ + +
+ +
+
+ + + + + + + + + + + + + + +
+ +
+ Powered by Google Project Hosting +
+ + + + + + + + diff --git a/static/img/jun09/pad/overlay2.png b/static/img/jun09/pad/overlay2.png new file mode 100644 index 000000000..c3d3f1cc8 Binary files /dev/null and b/static/img/jun09/pad/overlay2.png differ diff --git a/static/img/jun09/pad/padtop5.gif b/static/img/jun09/pad/padtop5.gif new file mode 100644 index 000000000..e6e071d6c Binary files /dev/null and b/static/img/jun09/pad/padtop5.gif differ diff --git a/static/img/jun09/pad/padtopback2.gif b/static/img/jun09/pad/padtopback2.gif new file mode 100644 index 000000000..db46567b9 Binary files /dev/null and b/static/img/jun09/pad/padtopback2.gif differ diff --git a/static/img/jun09/pad/public.gif b/static/img/jun09/pad/public.gif new file mode 100644 index 000000000..ac3093bd4 Binary files /dev/null and b/static/img/jun09/pad/public.gif differ diff --git a/static/img/jun09/pad/roundcorner_left.gif b/static/img/jun09/pad/roundcorner_left.gif new file mode 100644 index 000000000..000de7525 Binary files /dev/null and b/static/img/jun09/pad/roundcorner_left.gif differ diff --git a/static/img/jun09/pad/roundcorner_right_orange.gif b/static/img/jun09/pad/roundcorner_right_orange.gif new file mode 100644 index 000000000..717e3fc41 Binary files /dev/null and b/static/img/jun09/pad/roundcorner_right_orange.gif differ diff --git a/static/img/jun09/pad/savedrevarrows.gif b/static/img/jun09/pad/savedrevarrows.gif new file mode 100644 index 000000000..2aa2e41b5 Binary files /dev/null and b/static/img/jun09/pad/savedrevarrows.gif differ diff --git a/static/img/jun09/pad/savedrevsgfx2.gif b/static/img/jun09/pad/savedrevsgfx2.gif new file mode 100644 index 000000000..45c34590d Binary files /dev/null and b/static/img/jun09/pad/savedrevsgfx2.gif differ diff --git a/static/img/jun09/pad/sharebox4.gif b/static/img/jun09/pad/sharebox4.gif new file mode 100644 index 000000000..eccaa7ef0 Binary files /dev/null and b/static/img/jun09/pad/sharebox4.gif differ diff --git a/static/img/jun09/pad/sharedistri.gif b/static/img/jun09/pad/sharedistri.gif new file mode 100644 index 000000000..8eb589122 Binary files /dev/null and b/static/img/jun09/pad/sharedistri.gif differ diff --git a/static/img/jun09/pad/vdraggie.gif b/static/img/jun09/pad/vdraggie.gif new file mode 100644 index 000000000..b56112ba4 Binary files /dev/null and b/static/img/jun09/pad/vdraggie.gif differ diff --git a/static/img/may09/leftarrow2.gif b/static/img/may09/leftarrow2.gif new file mode 100644 index 000000000..63bbddca3 Binary files /dev/null and b/static/img/may09/leftarrow2.gif differ diff --git a/static/img/misc/status-ball.gif b/static/img/misc/status-ball.gif new file mode 100644 index 000000000..085ccaeca Binary files /dev/null and b/static/img/misc/status-ball.gif differ diff --git a/static/js/ace.js b/static/js/ace.js new file mode 100644 index 000000000..23396c398 --- /dev/null +++ b/static/js/ace.js @@ -0,0 +1,253 @@ +/** + * Copyright 2009 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. + */ + +// requires: top +// requires: plugins +// requires: undefined + + +Ace2Editor.registry = { nextId: 1 }; + +function Ace2Editor() { + var thisFunctionsName = "Ace2Editor"; + var ace2 = Ace2Editor; + + var editor = {}; + var info = { editor: editor, id: (ace2.registry.nextId++) }; + var loaded = false; + + var actionsPendingInit = []; + function pendingInit(func, optDoNow) { + return function() { + var that = this; + var args = arguments; + function action() { + func.apply(that, args); + } + if (optDoNow) { + optDoNow.apply(that, args); + } + if (loaded) { + action(); + } + else { + actionsPendingInit.push(action); + } + }; + } + function doActionsPendingInit() { + for(var i=0;i, time: +new Date()} + return info.ace_getUnhandledErrors(); + }; + + editor.callWithAce = pendingInit(function(fn, callStack, normalize) { + return info.ace_callWithAce(fn, callStack, normalize); + }); + + editor.execCommand = pendingInit(function(cmd, arg1) { + info.ace_execCommand(cmd, arg1); + }); + editor.replaceRange = pendingInit(function(start, end, text) { + info.ace_replaceRange(start, end, text); + }); + + + // calls to these functions ($$INCLUDE_...) are replaced when this file is processed + // and compressed, putting the compressed code from the named file directly into the + // source here. + + var $$INCLUDE_CSS = function(fileName) { + return ''; + }; + var $$INCLUDE_JS = function(fileName) { + return '\x3cscript type="text/javascript" src="'+fileName+'">\x3c/script>'; + }; + var $$INCLUDE_JS_DEV = $$INCLUDE_JS; + var $$INCLUDE_CSS_DEV = $$INCLUDE_CSS; + + var $$INCLUDE_CSS_Q = function(fileName) { + return '\'\''; + }; + var $$INCLUDE_JS_Q = function(fileName) { + return '\'\\x3cscript type="text/javascript" src="'+fileName+'">\\x3c/script>\''; + }; + var $$INCLUDE_JS_Q_DEV = $$INCLUDE_JS_Q; + var $$INCLUDE_CSS_Q_DEV = $$INCLUDE_CSS_Q; + + editor.destroy = pendingInit(function() { + info.ace_dispose(); + info.frame.parentNode.removeChild(info.frame); + delete ace2.registry[info.id]; + info = null; // prevent IE 6 closure memory leaks + }); + + editor.init = function(containerId, initialCode, doneFunc) { + + editor.importText(initialCode); + + info.onEditorReady = function() { + loaded = true; + doActionsPendingInit(); + doneFunc(); + }; + + (function() { + var doctype = ''; + + var iframeHTML = ["'"+doctype+"'"]; + + plugins.callHook( + "aceInitInnerdocbodyHead", {iframeHTML:iframeHTML}); + + // these lines must conform to a specific format because they are passed by the build script: + //iframeHTML.push($$INCLUDE_CSS_Q("editor.css syntax.css inner.css")); + + iframeHTML.push($$INCLUDE_CSS_Q("/static/css/editor.css")); + iframeHTML.push($$INCLUDE_CSS_Q("/static/css/syntax.cs")); + iframeHTML.push($$INCLUDE_CSS_Q("/static/css/inner.css")); + + //iframeHTML.push(INCLUDE_JS_Q_DEV("ace2_common_dev.js")); + //iframeHTML.push(INCLUDE_JS_Q_DEV("profiler.js")); + + //iframeHTML.push($$INCLUDE_JS_Q("ace2_common.js skiplist.js virtual_lines.js easysync2.js cssmanager.js colorutils.js undomodule.js contentcollector.js changesettracker.js linestylefilter.js domline.js")); + //iframeHTML.push($$INCLUDE_JS_Q("ace2_inner.js")); + + iframeHTML.push($$INCLUDE_JS_Q("/static/js/ace2_common.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/skiplist.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/virtual_lines.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/easysync2.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/cssmanager.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/colorutils.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/undomodule.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/contentcollector.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/changesettracker.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/linestylefilter.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/domline.js")); + iframeHTML.push($$INCLUDE_JS_Q("/static/js/ace2_inner.js")); + + iframeHTML.push('\'\\n\\n\''); + iframeHTML.push('\' \''); + + var outerScript = 'editorId = "'+info.id+'"; editorInfo = parent.'+ + thisFunctionsName+'.registry[editorId]; '+ + 'window.onload = function() '+ + '{ window.onload = null; setTimeout'+ + '(function() '+ + '{ var iframe = document.createElement("IFRAME"); '+ + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); '+ + 'iframe.frameBorder = 0; iframe.allowTransparency = true; '+ // for IE + 'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); '+ + 'iframe.ace_outerWin = window; '+ + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; '+ + 'var doc = iframe.contentWindow.document; doc.open(); doc.write('+ + iframeHTML.join('+')+'); doc.close(); '+ + '}, 0); }'; + + var outerHTML = [doctype, '', + $$INCLUDE_CSS("/static/css/editor.css"), + // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly + // (throbs busy while typing) + '', + '\x3cscript>', outerScript, '\x3c/script>', + '
x
']; + + + if (!Array.prototype.map) Array.prototype.map = function(fun) { //needed for IE + if (typeof fun != "function") throw new TypeError(); + var len = this.length; + var res = new Array(len); + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in this) res[i] = fun.call(thisp, this[i], i, this); + } + return res; + }; + + var outerFrame = document.createElement("IFRAME"); + outerFrame.frameBorder = 0; // for IE + info.frame = outerFrame; + document.getElementById(containerId).appendChild(outerFrame); + + var editorDocument = outerFrame.contentWindow.document; + + editorDocument.open(); + editorDocument.write(outerHTML.join('')); + editorDocument.close(); + })(); + }; + + return editor; +} diff --git a/static/js/ace.js.old b/static/js/ace.js.old new file mode 100644 index 000000000..42c3cf28a --- /dev/null +++ b/static/js/ace.js.old @@ -0,0 +1,30 @@ +Ace2Editor.registry={nextId:1};function Ace2Editor(){var K="Ace2Editor";var F=Ace2Editor;var B={};var A={editor:B,id:(F.registry.nextId++)}; +var D=false;var E=[];function C(R,Q){return function(){var T=this;var S=arguments;function U(){R.apply(T,S); +}if(Q){Q.apply(T,S);}if(D){U();}else{E.push(U);}};}function I(){for(var Q=0;Q';};var J=function(Q){return'\x3cscript type="text/javascript" src="'+Q+'">\x3c/script>'; +};var M=J;var N=H;var L=function(Q){return'\''"; +};var G=function(Q){return'\'\\x3cscript type="text/javascript" src="'+Q+"\">\\x3c/script>'";};var P=G; +var O=L;B.destroy=C(function(){A.ace_dispose();A.frame.parentNode.removeChild(A.frame);delete F.registry[A.id]; +A=null;});B.init=function(Q,S,R){B.importText(S);A.onEditorReady=function(){D=true;I();R();};(function(){var W=''; +var T=["'"+W+"'"];plugins.callHook("aceInitInnerdocbodyHead",{iframeHTML:T});T.push(("('\\n\'');T.push('\' \''); +var X='editorId = "'+A.id+'"; editorInfo = parent.'+K+'.registry[editorId]; window.onload = function() { window.onload = null; setTimeout(function() { var iframe = document.createElement("IFRAME"); iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); iframe.frameBorder = 0; iframe.allowTransparency = true; outerdocbody.insertBefore(iframe, outerdocbody.firstChild); iframe.ace_outerWin = window; readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; var doc = iframe.contentWindow.document; doc.open(); doc.write('+T.join("+")+"); doc.close(); }, 0); }"; +var Y=[W,"",(''),'',"\x3cscript>",X,"\x3c/script>",'
x
']; +if(!Array.prototype.map){Array.prototype.map=function(b){if(typeof b!="function"){throw new TypeError(); +}var a=this.length;var c=new Array(a);var d=arguments[1];for(var Z=0;Z, time: +new Date()} + return info.ace_getUnhandledErrors(); + }; + + editor.callWithAce = pendingInit(function(fn, callStack, normalize) { + return info.ace_callWithAce(fn, callStack, normalize); + }); + + editor.execCommand = pendingInit(function(cmd, arg1) { + info.ace_execCommand(cmd, arg1); + }); + editor.replaceRange = pendingInit(function(start, end, text) { + info.ace_replaceRange(start, end, text); + }); + + + // calls to these functions ($$INCLUDE_...) are replaced when this file is processed + // and compressed, putting the compressed code from the named file directly into the + // source here. + + /*var $$INCLUDE_CSS = function(fileName) { + return ''; + }; + var $$INCLUDE_JS = function(fileName) { + return '\x3cscript type="text/javascript" src="'+fileName+'">\x3c/script>'; + }; + var $$INCLUDE_JS_DEV = $$INCLUDE_JS; + var $$INCLUDE_CSS_DEV = $$INCLUDE_CSS; + + var $$INCLUDE_CSS_Q = function(fileName) { + return '\'\''; + }; + var $$INCLUDE_JS_Q = function(fileName) { + return '\'\\x3cscript type="text/javascript" src="'+fileName+'">\\x3c/script>\''; + }; + var $$INCLUDE_JS_Q_DEV = $$INCLUDE_JS_Q; + var $$INCLUDE_CSS_Q_DEV = $$INCLUDE_CSS_Q;*/ + + editor.destroy = pendingInit(function() { + info.ace_dispose(); + info.frame.parentNode.removeChild(info.frame); + delete ace2.registry[info.id]; + info = null; // prevent IE 6 closure memory leaks + }); + + editor.init = function(containerId, initialCode, doneFunc) { + + editor.importText(initialCode); + + info.onEditorReady = function() { + loaded = true; + doActionsPendingInit(); + doneFunc(); + }; + + (function() { + var doctype = ''; + + var iframeHTML = ["'"+doctype+"'"]; + + plugins.callHook( + "aceInitInnerdocbodyHead", {iframeHTML:iframeHTML}); + + // these lines must conform to a specific format because they are passed by the build script: + //iframeHTML.push($$INCLUDE_CSS_Q("editor.css syntax.css inner.css")); + + iframeHTML.push('\'\''); + iframeHTML.push('\'\''); + iframeHTML.push('\'\''); + + //iframeHTML.push(INCLUDE_JS_Q_DEV("ace2_common_dev.js")); + //iframeHTML.push(INCLUDE_JS_Q_DEV("profiler.js")); + // iframeHTML.push($$INCLUDE_JS_Q("ace2_common.js skiplist.js virtual_lines.js easysync2.js cssmanager.js colorutils.js undomodule.js contentcollector.js changesettracker.js linestylefilter.js domline.js")); + //iframeHTML.push($$INCLUDE_JS_Q("ace2_inner.js")); + + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/ace2_common.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/skiplist.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/virtual_lines.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/easysync2.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/cssmanager.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/colorutils.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/undomodule.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/contentcollector.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/changesettracker.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/linestylefilter.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/domline.js">\\x3c/script>\''); + iframeHTML.push('\'\\x3cscript type="text/javascript" src="/static/js/ace2_inner.js">\\x3c/script>\''); + + iframeHTML.push('\'\\n\\n\''); + iframeHTML.push('\' \''); + + var outerScript = 'editorId = "'+info.id+'"; editorInfo = parent.'+ + thisFunctionsName+'.registry[editorId]; '+ + 'window.onload = function() '+ + '{ window.onload = null; setTimeout'+ + '(function() '+ + '{ var iframe = document.createElement("IFRAME"); '+ + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); '+ + 'iframe.frameBorder = 0; iframe.allowTransparency = true; '+ // for IE + 'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); '+ + 'iframe.ace_outerWin = window; '+ + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; '+ + 'var doc = iframe.contentWindow.document; doc.open(); doc.write('+ + iframeHTML.join('+')+'); doc.close(); '+ + '}, 0); }'; + + var outerHTML = [doctype, '', + '', + // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly + // (throbs busy while typing) + '', + '\x3cscript>', outerScript, '\x3c/script>', + '
x
']; + + + if (!Array.prototype.map) Array.prototype.map = function(fun) { //needed for IE + if (typeof fun != "function") throw new TypeError(); + var len = this.length; + var res = new Array(len); + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in this) res[i] = fun.call(thisp, this[i], i, this); + } + return res; + }; + + var outerFrame = document.createElement("IFRAME"); + outerFrame.frameBorder = 0; // for IE + info.frame = outerFrame; + document.getElementById(containerId).appendChild(outerFrame); + + var editorDocument = outerFrame.contentWindow.document; + + editorDocument.open(); + editorDocument.write(outerHTML.join('')); + editorDocument.close(); + })(); + }; + + return editor; +} diff --git a/static/js/ace2_common.js b/static/js/ace2_common.js new file mode 100644 index 000000000..4a08de6f0 --- /dev/null +++ b/static/js/ace2_common.js @@ -0,0 +1,115 @@ +/** + * Copyright 2009 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. + */ + + +function isNodeText(node) { + return (node.nodeType == 3); +} + +function object(o) { + var f = function() {}; + f.prototype = o; + return new f(); +} + +function extend(obj, props) { + for(var p in props) { + obj[p] = props[p]; + } + return obj; +} + +function forEach(array, func) { + for(var i=0;i 1) { + var x = Math.floor((low+high)/2); // x != low, x != high + if (func(x)) high = x; + else low = x; + } + return high; +} + +function binarySearchInfinite(expectedLength, func) { + var i = 0; + while (!func(i)) i += expectedLength; + return binarySearch(i, func); +} + +function htmlPrettyEscape(str) { + return str.replace(/&/g, '&').replace(//g, '>') + .replace(/\r?\n/g, '\\n'); +} diff --git a/static/js/ace2_inner.js b/static/js/ace2_inner.js new file mode 100644 index 000000000..680e8cd04 --- /dev/null +++ b/static/js/ace2_inner.js @@ -0,0 +1,4588 @@ +/** + * Copyright 2009 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. + */ + +function OUTER(gscope) { + + var DEBUG=true;//$$ build script replaces the string "var DEBUG=true;//$$" with "var DEBUG=false;" + + var isSetUp = false; + + var THE_TAB = ' ';//4 + var MAX_LIST_LEVEL = 8; + + var LINE_NUMBER_PADDING_RIGHT = 4; + var LINE_NUMBER_PADDING_LEFT = 4; + var MIN_LINEDIV_WIDTH = 20; + var EDIT_BODY_PADDING_TOP = 8; + var EDIT_BODY_PADDING_LEFT = 8; + + var caughtErrors = []; + + var thisAuthor = ''; + + var disposed = false; + + var editorInfo = parent.editorInfo; + + var iframe = window.frameElement; + var outerWin = iframe.ace_outerWin; + iframe.ace_outerWin = null; // prevent IE 6 memory leak + var sideDiv = iframe.nextSibling; + var lineMetricsDiv = sideDiv.nextSibling; + var overlaysdiv = lineMetricsDiv.nextSibling; + initLineNumbers(); + + var outsideKeyDown = function(evt) {}; + var outsideKeyPress = function(evt) { return true; }; + var outsideNotifyDirty = function() {}; + + // selFocusAtStart -- determines whether the selection extends "backwards", so that the focus + // point (controlled with the arrow keys) is at the beginning; not supported in IE, though + // native IE selections have that behavior (which we try not to interfere with). + // Must be false if selection is collapsed! + var rep = { lines: newSkipList(), selStart: null, selEnd: null, selFocusAtStart: false, + alltext: "", alines: [], + apool: new AttribPool() }; + // lines, alltext, alines, and DOM are set up in setup() + if (undoModule.enabled) { + undoModule.apool = rep.apool; + } + + var root, doc; // set in setup() + + var isEditable = true; + var doesWrap = true; + var hasLineNumbers = true; + var isStyled = true; + + // space around the innermost iframe element + var iframePadLeft = MIN_LINEDIV_WIDTH + LINE_NUMBER_PADDING_RIGHT + EDIT_BODY_PADDING_LEFT; + var iframePadTop = EDIT_BODY_PADDING_TOP; + var iframePadBottom = 0, iframePadRight = 0; + + var console = (DEBUG && top.console); + if (! console) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", + "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; + console = {}; + for (var i = 0; i < names.length; ++i) + console[names[i]] = function() {}; + //console.error = function(str) { alert(str); }; + } + var PROFILER = window.PROFILER; + if (!PROFILER) { + PROFILER = function() { return {start:noop, mark:noop, literal:noop, end:noop, cancel:noop}; }; + } + function noop() {} + function identity(x) { return x; } + + // "dmesg" is for displaying messages in the in-page output pane + // visible when "?djs=1" is appended to the pad URL. It generally + // remains a no-op unless djs is enabled, but we make a habit of + // only calling it in error cases or while debugging. + var dmesg = noop; + window.dmesg = noop; + + var scheduler = parent; + + var textFace = 'monospace'; + var textSize = 12; + function textLineHeight() { return Math.round(textSize * 4/3); } + + var dynamicCSS = null; + function initDynamicCSS() { + dynamicCSS = makeCSSManager("dynamicsyntax"); + } + + var changesetTracker = makeChangesetTracker(scheduler, rep.apool, { + withCallbacks: function(operationName, f) { + inCallStackIfNecessary(operationName, function() { + fastIncorp(1); + f({ + setDocumentAttributedText: function(atext) { + setDocAText(atext); + }, + applyChangesetToDocument: function(changeset, preferInsertionAfterCaret) { + var oldEventType = currentCallStack.editEvent.eventType; + currentCallStack.startNewEvent("nonundoable"); + + performDocumentApplyChangeset(changeset, preferInsertionAfterCaret); + + currentCallStack.startNewEvent(oldEventType); + } + }); + }); + } + }); + + var authorInfos = {}; // presence of key determines if author is present in doc + + function setAuthorInfo(author, info) { + if ((typeof author) != "string") { + throw new Error("setAuthorInfo: author ("+author+") is not a string"); + } + if (! info) { + delete authorInfos[author]; + if (dynamicCSS) { + dynamicCSS.removeSelectorStyle(getAuthorColorClassSelector(getAuthorClassName(author))); + } + } + else { + authorInfos[author] = info; + if (info.bgcolor) { + if (dynamicCSS) { + var bgcolor = info.bgcolor; + if ((typeof info.fade) == "number") { + bgcolor = fadeColor(bgcolor, info.fade); + } + + dynamicCSS.selectorStyle(getAuthorColorClassSelector( + getAuthorClassName(author))).backgroundColor = bgcolor; + } + } + } + } + + function getAuthorClassName(author) { + return "author-"+author.replace(/[^a-y0-9]/g, function(c) { + if (c == ".") return "-"; + return 'z'+c.charCodeAt(0)+'z'; + }); + } + function className2Author(className) { + if (className.substring(0,7) == "author-") { + return className.substring(7).replace(/[a-y0-9]+|-|z.+?z/g, function(cc) { + if (cc == '-') return '.'; + else if (cc.charAt(0) == 'z') { + return String.fromCharCode(Number(cc.slice(1,-1))); + } + else { + return cc; + } + }); + } + return null; + } + function getAuthorColorClassSelector(oneClassName) { + return ".authorColors ."+oneClassName; + } + function setUpTrackingCSS() { + if (dynamicCSS) { + var backgroundHeight = lineMetricsDiv.offsetHeight; + var lineHeight = textLineHeight(); + var extraBodding = 0; + var extraTodding = 0; + if (backgroundHeight < lineHeight) { + extraBodding = Math.ceil((lineHeight - backgroundHeight)/2); + extraTodding = lineHeight - backgroundHeight - extraBodding; + } + var spanStyle = dynamicCSS.selectorStyle("#innerdocbody span"); + spanStyle.paddingTop = extraTodding+"px"; + spanStyle.paddingBottom = extraBodding+"px"; + } + } + function boldColorFromColor(lightColorCSS) { + var color = colorutils.css2triple(lightColorCSS); + + // amp up the saturation to full + color = colorutils.saturate(color); + + // normalize brightness based on luminosity + color = colorutils.scaleColor(color, 0, 0.5 / colorutils.luminosity(color)); + + return colorutils.triple2css(color); + } + function fadeColor(colorCSS, fadeFrac) { + var color = colorutils.css2triple(colorCSS); + color = colorutils.blend(color, [1,1,1], fadeFrac); + return colorutils.triple2css(color); + } + + function doAlert(str) { + scheduler.setTimeout(function() { alert(str); }, 0); + } + + editorInfo.ace_getRep = function () { + return rep; + } + + var currentCallStack = null; + function inCallStack(type, action) { + if (disposed) return; + + if (currentCallStack) { + console.error("Can't enter callstack "+type+", already in "+ + currentCallStack.type); + } + + var profiling = false; + function profileRest() { + profiling = true; + console.profile(); + } + + function newEditEvent(eventType) { + return {eventType:eventType, backset: null}; + } + + function submitOldEvent(evt) { + if (rep.selStart && rep.selEnd) { + var selStartChar = + rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1]; + var selEndChar = + rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1]; + evt.selStart = selStartChar; + evt.selEnd = selEndChar; + evt.selFocusAtStart = rep.selFocusAtStart; + } + if (undoModule.enabled) { + var undoWorked = false; + try { + if (evt.eventType == "setup" || evt.eventType == "importText" || + evt.eventType == "setBaseText") { + undoModule.clearHistory(); + } + else if (evt.eventType == "nonundoable") { + if (evt.changeset) { + undoModule.reportExternalChange(evt.changeset); + } + } + else { + undoModule.reportEvent(evt); + } + undoWorked = true; + } + finally { + if (! undoWorked) { + undoModule.enabled = false; // for safety + } + } + } + } + + function startNewEvent(eventType, dontSubmitOld) { + var oldEvent = currentCallStack.editEvent; + if (! dontSubmitOld) { + submitOldEvent(oldEvent); + } + currentCallStack.editEvent = newEditEvent(eventType); + return oldEvent; + } + + currentCallStack = {type: type, docTextChanged: false, selectionAffected: false, + userChangedSelection: false, + domClean: false, profileRest:profileRest, + isUserChange: false, // is this a "user change" type of call-stack + repChanged: false, editEvent: newEditEvent(type), + startNewEvent:startNewEvent}; + var cleanExit = false; + var result; + try { + result = action(); + //console.log("Just did action for: "+type); + cleanExit = true; + } + catch (e) { + caughtErrors.push({error: e, time: +new Date()}); + dmesg(e.toString()); + throw e; + } + finally { + var cs = currentCallStack; + //console.log("Finished action for: "+type); + if (cleanExit) { + submitOldEvent(cs.editEvent); + if (cs.domClean && cs.type != "setup") { + if (cs.isUserChange) { + if (cs.repChanged) parenModule.notifyChange(); + else parenModule.notifyTick(); + } + recolorModule.recolorLines(); + if (cs.selectionAffected) { + updateBrowserSelectionFromRep(); + } + if ((cs.docTextChanged || cs.userChangedSelection) && cs.type != "applyChangesToBase") { + scrollSelectionIntoView(); + } + if (cs.docTextChanged && cs.type.indexOf("importText") < 0) { + outsideNotifyDirty(); + } + } + } + else { + // non-clean exit + if (currentCallStack.type == "idleWorkTimer") { + idleWorkTimer.atLeast(1000); + } + } + currentCallStack = null; + if (profiling) console.profileEnd(); + } + return result; + } + editorInfo.ace_inCallStack = inCallStack; + + function inCallStackIfNecessary(type, action) { + if (! currentCallStack) { + inCallStack(type, action); + } + else { + action(); + } + } + editorInfo.ace_inCallStackIfNecessary = inCallStackIfNecessary; + + function recolorLineByKey(key) { + if (rep.lines.containsKey(key)) { + var offset = rep.lines.offsetOfKey(key); + var width = rep.lines.atKey(key).width; + recolorLinesInRange(offset, offset + width); + } + } + + function getLineKeyForOffset(charOffset) { + return rep.lines.atOffset(charOffset).key; + } + + var recolorModule = (function() { + var dirtyLineKeys = {}; + + var module = {}; + module.setCharNeedsRecoloring = function(offset) { + if (offset >= rep.alltext.length) { + offset = rep.alltext.length-1; + } + dirtyLineKeys[getLineKeyForOffset(offset)] = true; + } + + module.setCharRangeNeedsRecoloring = function(offset1, offset2) { + if (offset1 >= rep.alltext.length) { + offset1 = rep.alltext.length-1; + } + if (offset2 >= rep.alltext.length) { + offset2 = rep.alltext.length-1; + } + var firstEntry = rep.lines.atOffset(offset1); + var lastKey = rep.lines.atOffset(offset2).key; + dirtyLineKeys[lastKey] = true; + var entry = firstEntry; + while (entry && entry.key != lastKey) { + dirtyLineKeys[entry.key] = true; + entry = rep.lines.next(entry); + } + } + + module.recolorLines = function() { + for(var k in dirtyLineKeys) { + recolorLineByKey(k); + } + dirtyLineKeys = {}; + } + + return module; + })(); + + var parenModule = (function() { + var module = {}; + module.notifyTick = function() { handleFlashing(false); }; + module.notifyChange = function() { handleFlashing(true); }; + module.shouldNormalizeOnChar = function (c) { + if (parenFlashRep.active) { + // avoid highlight style from carrying on to typed text + return true; + } + c = String.fromCharCode(c); + return !! (bracketMap[c]); + } + + var parenFlashRep = { active: false, whichChars: null, whichLineKeys: null, expireTime: null }; + var bracketMap = {'(': 1, ')':-1, '[':2, ']':-2, '{':3, '}':-3}; + var bracketRegex = /[{}\[\]()]/g; + function handleFlashing(docChanged) { + function getSearchRange(aroundLoc) { + var rng = getVisibleCharRange(); + var d = 100; // minimum radius + var e = 3000; // maximum radius; + if (rng[0] > aroundLoc-d) rng[0] = aroundLoc-d; + if (rng[0] < aroundLoc-e) rng[0] = aroundLoc-e; + if (rng[0] < 0) rng[0] = 0; + if (rng[1] < aroundLoc+d) rng[1] = aroundLoc+d; + if (rng[1] > aroundLoc+e) rng[1] = aroundLoc+e; + if (rng[1] > rep.lines.totalWidth()) rng[1] = rep.lines.totalWidth(); + return rng; + } + function findMatchingVisibleBracket(startLoc, forwards) { + var rng = getSearchRange(startLoc); + var str = rep.alltext.substring(rng[0], rng[1]); + var bstr = str.replace(bracketRegex, '('); // handy for searching + var loc = startLoc - rng[0]; + var bracketState = []; + var foundParen = false; + var goodParen = false; + function nextLoc() { + if (loc < 0) return; + if (forwards) loc++; else loc--; + if (loc < 0 || loc >= str.length) loc = -1; + if (loc >= 0) { + if (forwards) loc = bstr.indexOf('(', loc); + else loc = bstr.lastIndexOf('(', loc); + } + } + while ((! foundParen) && (loc >= 0)) { + if (getCharType(loc + rng[0]) == "p") { + var b = bracketMap[str.charAt(loc)]; // -1, 1, -2, 2, -3, 3 + var into = forwards; + var typ = b; + if (typ < 0) { into = ! into; typ = -typ; } + if (into) bracketState.push(typ); + else { + var recent = bracketState.pop(); + if (recent != typ) { + foundParen = true; goodParen = false; + } + else if (bracketState.length == 0) { + foundParen = true; goodParen = true; + } + } + } + //console.log(bracketState.toSource()); + if ((! foundParen) && (loc >= 0)) nextLoc(); + } + if (! foundParen) return null; + return {chr: (loc + rng[0]), good: goodParen}; + } + + var r = parenFlashRep; + var charsToHighlight = null; + var linesToUnhighlight = null; + if (r.active && (docChanged || (now() > r.expireTime))) { + linesToUnhighlight = r.whichLineKeys; + r.active = false; + } + if ((! r.active) && docChanged && isCaret() && caretColumn() > 0) { + var caret = caretDocChar(); + if (caret > 0 && getCharType(caret-1) == "p") { + var charBefore = rep.alltext.charAt(caret-1); + if (bracketMap[charBefore]) { + var lookForwards = (bracketMap[charBefore] > 0); + var findResult = findMatchingVisibleBracket(caret-1, lookForwards); + if (findResult) { + var mateLoc = findResult.chr; + var mateGood = findResult.good; + r.active = true; + charsToHighlight = {}; + charsToHighlight[caret-1] = 'flash'; + charsToHighlight[mateLoc] = (mateGood ? 'flash' : 'flashbad'); + r.whichLineKeys = []; + r.whichLineKeys.push(getLineKeyForOffset(caret-1)); + r.whichLineKeys.push(getLineKeyForOffset(mateLoc)); + r.expireTime = now() + 4000; + newlyActive = true; + } + } + } + + } + if (linesToUnhighlight) { + recolorLineByKey(linesToUnhighlight[0]); + recolorLineByKey(linesToUnhighlight[1]); + } + if (r.active && charsToHighlight) { + function f(txt, cls, next, ofst) { + var flashClass = charsToHighlight[ofst]; + if (cls) { + next(txt, cls+" "+flashClass); + } + else next(txt, cls); + } + for(var c in charsToHighlight) { + recolorLinesInRange((+c), (+c)+1, null, f); + } + } + } + + return module; + })(); + + function dispose() { + disposed = true; + if (idleWorkTimer) idleWorkTimer.never(); + teardown(); + } + + function checkALines() { + return; // disable for speed + function error() { throw new Error("checkALines"); } + if (rep.alines.length != rep.lines.length()) { + error(); + } + for(var i=0;i