}
response.end();
} else {
- kiwi.jade.renderFile(__dirname + '/client/index.html.jade', { locals: { "touchscreen": touchscreen, "debug": debug, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": kiwi.config }}, function (err, html) {
+ fs.readFile(__dirname + '/client/index.html.jade', 'utf8', function (err, str) {
+ var html, hash2;
if (!err) {
- var hash2 = crypto.createHash('md5').update(html).digest('base64');
+ html = kiwi.jade.compile(str)({ "touchscreen": touchscreen, "debug": debug, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": kiwi.config });
+ console.log(typeof html, html);
+ hash2 = crypto.createHash('md5').update(html).digest('base64');
kiwi.cache.html[hash] = {"html": html, "hash": hash2};
if (request.headers['if-none-match'] === hash2) {
response.statusCode = 304;
+++ /dev/null
-.DS_Store
-.tmp*~
-*.local.*
-.pinf-*
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
-lang="en" xml:lang="en">
-<head>
-<title>UglifyJS -- a JavaScript parser/compressor/beautifier</title>
-<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
-<meta name="generator" content="Org-mode"/>
-<meta name="generated" content="2011-07-14 12:50:31 EEST"/>
-<meta name="author" content="Mihai Bazon"/>
-<meta name="description" content="a JavaScript parser/compressor/beautifier in JavaScript"/>
-<meta name="keywords" content="javascript, js, parser, compiler, compressor, mangle, minify, minifier"/>
-<style type="text/css">
- <!--/*--><![CDATA[/*><!--*/
- html { font-family: Times, serif; font-size: 12pt; }
- .title { text-align: center; }
- .todo { color: red; }
- .done { color: green; }
- .tag { background-color: #add8e6; font-weight:normal }
- .target { }
- .timestamp { color: #bebebe; }
- .timestamp-kwd { color: #5f9ea0; }
- p.verse { margin-left: 3% }
- pre {
- border: 1pt solid #AEBDCC;
- background-color: #F3F5F7;
- padding: 5pt;
- font-family: courier, monospace;
- font-size: 90%;
- overflow:auto;
- }
- table { border-collapse: collapse; }
- td, th { vertical-align: top; }
- dt { font-weight: bold; }
- div.figure { padding: 0.5em; }
- div.figure p { text-align: center; }
- textarea { overflow-x: auto; }
- .linenr { font-size:smaller }
- .code-highlighted {background-color:#ffff00;}
- .org-info-js_info-navigation { border-style:none; }
- #org-info-js_console-label { font-size:10px; font-weight:bold;
- white-space:nowrap; }
- .org-info-js_search-highlight {background-color:#ffff00; color:#000000;
- font-weight:bold; }
- /*]]>*/-->
-</style>
-<link rel="stylesheet" type="text/css" href="docstyle.css" />
-<script type="text/javascript">
-<!--/*--><![CDATA[/*><!--*/
- function CodeHighlightOn(elem, id)
- {
- var target = document.getElementById(id);
- if(null != target) {
- elem.cacheClassElem = elem.className;
- elem.cacheClassTarget = target.className;
- target.className = "code-highlighted";
- elem.className = "code-highlighted";
- }
- }
- function CodeHighlightOff(elem, id)
- {
- var target = document.getElementById(id);
- if(elem.cacheClassElem)
- elem.className = elem.cacheClassElem;
- if(elem.cacheClassTarget)
- target.className = elem.cacheClassTarget;
- }
-/*]]>*///-->
-</script>
-
-</head>
-<body>
-<div id="content">
-
-<h1 class="title">UglifyJS – a JavaScript parser/compressor/beautifier</h1>
-
-
-<div id="table-of-contents">
-<h2>Table of Contents</h2>
-<div id="text-table-of-contents">
-<ul>
-<li><a href="#sec-1">1 UglifyJS — a JavaScript parser/compressor/beautifier </a>
-<ul>
-<li><a href="#sec-1_1">1.1 Unsafe transformations </a>
-<ul>
-<li><a href="#sec-1_1_1">1.1.1 Calls involving the global Array constructor </a></li>
-</ul>
-</li>
-<li><a href="#sec-1_2">1.2 Install (NPM) </a></li>
-<li><a href="#sec-1_3">1.3 Install latest code from GitHub </a></li>
-<li><a href="#sec-1_4">1.4 Usage </a>
-<ul>
-<li><a href="#sec-1_4_1">1.4.1 API </a></li>
-<li><a href="#sec-1_4_2">1.4.2 Beautifier shortcoming – no more comments </a></li>
-</ul>
-</li>
-<li><a href="#sec-1_5">1.5 Compression – how good is it? </a></li>
-<li><a href="#sec-1_6">1.6 Bugs? </a></li>
-<li><a href="#sec-1_7">1.7 Links </a></li>
-<li><a href="#sec-1_8">1.8 License </a></li>
-</ul>
-</li>
-</ul>
-</div>
-</div>
-
-<div id="outline-container-1" class="outline-2">
-<h2 id="sec-1"><span class="section-number-2">1</span> UglifyJS — a JavaScript parser/compressor/beautifier </h2>
-<div class="outline-text-2" id="text-1">
-
-
-<p>
-This package implements a general-purpose JavaScript
-parser/compressor/beautifier toolkit. It is developed on <a href="http://nodejs.org/">NodeJS</a>, but it
-should work on any JavaScript platform supporting the CommonJS module system
-(and if your platform of choice doesn't support CommonJS, you can easily
-implement it, or discard the <code>exports.*</code> lines from UglifyJS sources).
-</p>
-<p>
-The tokenizer/parser generates an abstract syntax tree from JS code. You
-can then traverse the AST to learn more about the code, or do various
-manipulations on it. This part is implemented in <a href="../lib/parse-js.js">parse-js.js</a> and it's a
-port to JavaScript of the excellent <a href="http://marijn.haverbeke.nl/parse-js/">parse-js</a> Common Lisp library from <a href="http://marijn.haverbeke.nl/">Marijn Haverbeke</a>.
-</p>
-<p>
-( See <a href="http://github.com/mishoo/cl-uglify-js">cl-uglify-js</a> if you're looking for the Common Lisp version of
-UglifyJS. )
-</p>
-<p>
-The second part of this package, implemented in <a href="../lib/process.js">process.js</a>, inspects and
-manipulates the AST generated by the parser to provide the following:
-</p>
-<ul>
-<li>
-ability to re-generate JavaScript code from the AST. Optionally
-indented—you can use this if you want to “beautify” a program that has
-been compressed, so that you can inspect the source. But you can also run
-our code generator to print out an AST without any whitespace, so you
-achieve compression as well.
-
-</li>
-<li>
-shorten variable names (usually to single characters). Our mangler will
-analyze the code and generate proper variable names, depending on scope
-and usage, and is smart enough to deal with globals defined elsewhere, or
-with <code>eval()</code> calls or <code>with{}</code> statements. In short, if <code>eval()</code> or
-<code>with{}</code> are used in some scope, then all variables in that scope and any
-variables in the parent scopes will remain unmangled, and any references
-to such variables remain unmangled as well.
-
-</li>
-<li>
-various small optimizations that may lead to faster code but certainly
-lead to smaller code. Where possible, we do the following:
-
-<ul>
-<li>
-foo["bar"] ==> foo.bar
-
-</li>
-<li>
-remove block brackets <code>{}</code>
-
-</li>
-<li>
-join consecutive var declarations:
-var a = 10; var b = 20; ==> var a=10,b=20;
-
-</li>
-<li>
-resolve simple constant expressions: 1 +2 * 3 ==> 7. We only do the
-replacement if the result occupies less bytes; for example 1/3 would
-translate to 0.333333333333, so in this case we don't replace it.
-
-</li>
-<li>
-consecutive statements in blocks are merged into a sequence; in many
-cases, this leaves blocks with a single statement, so then we can remove
-the block brackets.
-
-</li>
-<li>
-various optimizations for IF statements:
-
-<ul>
-<li>
-if (foo) bar(); else baz(); ==> foo?bar():baz();
-</li>
-<li>
-if (!foo) bar(); else baz(); ==> foo?baz():bar();
-</li>
-<li>
-if (foo) bar(); ==> foo&&bar();
-</li>
-<li>
-if (!foo) bar(); ==> foo||bar();
-</li>
-<li>
-if (foo) return bar(); else return baz(); ==> return foo?bar():baz();
-</li>
-<li>
-if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
-
-</li>
-</ul>
-</li>
-<li>
-remove some unreachable code and warn about it (code that follows a
-<code>return</code>, <code>throw</code>, <code>break</code> or <code>continue</code> statement, except
-function/variable declarations).
-</li>
-</ul>
-</li>
-</ul>
-
-
-
-</div>
-
-<div id="outline-container-1_1" class="outline-3">
-<h3 id="sec-1_1"><span class="section-number-3">1.1</span> <span class="target">Unsafe transformations</span> </h3>
-<div class="outline-text-3" id="text-1_1">
-
-
-<p>
-UglifyJS tries its best to achieve great compression while leaving the
-semantics of the code intact. In general, if your code logic is broken by
-UglifyJS then it's a bug in UglifyJS and you should report it and I should
-fix it. :-)
-</p>
-<p>
-However, I opted to include the following potentially unsafe transformations
-as default behavior. Discussion is welcome, if you have ideas of how to
-handle this better, or any objections to these optimizations, please let me
-know.
-</p>
-
-</div>
-
-<div id="outline-container-1_1_1" class="outline-4">
-<h4 id="sec-1_1_1"><span class="section-number-4">1.1.1</span> Calls involving the global Array constructor </h4>
-<div class="outline-text-4" id="text-1_1_1">
-
-
-<p>
-The following transformations occur:
-</p>
-
-
-
-<pre class="src src-js"><span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3, 4) => [1,2,3,4]
-Array(a, b, c) => [a,b,c]
-<span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(5) => Array(5)
-<span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(a) => Array(a)
-</pre>
-
-
-
-<p>
-These are all safe if the Array name isn't redefined. JavaScript does allow
-one to globally redefine Array (and pretty much everything, in fact) but I
-personally don't see why would anyone do that.
-</p>
-<p>
-UglifyJS does handle the case where Array is redefined locally, or even
-globally but with a <code>function</code> or <code>var</code> declaration. Therefore, in the
-following cases UglifyJS <b>doesn't touch</b> calls or instantiations of Array:
-</p>
-
-
-
-<pre class="src src-js"><span style="color: #00008b;">// </span><span style="color: #00008b;">case 1. globally declared variable
-</span> <span style="color: #8b0000;">var</span> <span style="color: #8b008b;">Array</span>;
- <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3);
- Array(a, b);
-
- <span style="color: #00008b;">// </span><span style="color: #00008b;">or (can be declared later)
-</span> <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3);
- <span style="color: #8b0000;">var</span> <span style="color: #8b008b;">Array</span>;
-
- <span style="color: #00008b;">// </span><span style="color: #00008b;">or (can be a function)
-</span> <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3);
- <span style="color: #8b0000;">function</span> <span style="color: #8b2323;">Array</span>() { ... }
-
-<span style="color: #00008b;">// </span><span style="color: #00008b;">case 2. declared in a function
-</span> (<span style="color: #8b0000;">function</span>(){
- a = <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3);
- b = Array(5, 6);
- <span style="color: #8b0000;">var</span> <span style="color: #8b008b;">Array</span>;
- })();
-
- <span style="color: #00008b;">// </span><span style="color: #00008b;">or
-</span> (<span style="color: #8b0000;">function</span>(<span style="color: #8b008b;">Array</span>){
- <span style="color: #8b0000;">return</span> Array(5, 6, 7);
- })();
-
- <span style="color: #00008b;">// </span><span style="color: #00008b;">or
-</span> (<span style="color: #8b0000;">function</span>(){
- <span style="color: #8b0000;">return</span> <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3, 4);
- <span style="color: #8b0000;">function</span> <span style="color: #8b2323;">Array</span>() { ... }
- })();
-
- <span style="color: #00008b;">// </span><span style="color: #00008b;">etc.
-</span></pre>
-
-
-
-</div>
-</div>
-
-</div>
-
-<div id="outline-container-1_2" class="outline-3">
-<h3 id="sec-1_2"><span class="section-number-3">1.2</span> Install (NPM) </h3>
-<div class="outline-text-3" id="text-1_2">
-
-
-<p>
-UglifyJS is now available through NPM — <code>npm install uglify-js</code> should do
-the job.
-</p>
-</div>
-
-</div>
-
-<div id="outline-container-1_3" class="outline-3">
-<h3 id="sec-1_3"><span class="section-number-3">1.3</span> Install latest code from GitHub </h3>
-<div class="outline-text-3" id="text-1_3">
-
-
-
-
-
-<pre class="src src-sh"><span style="color: #00008b;">## </span><span style="color: #00008b;">clone the repository
-</span>mkdir -p /where/you/wanna/put/it
-<span style="color: #cd0000;">cd</span> /where/you/wanna/put/it
-git clone git://github.com/mishoo/UglifyJS.git
-
-<span style="color: #00008b;">## </span><span style="color: #00008b;">make the module available to Node
-</span>mkdir -p ~/.node_libraries/
-<span style="color: #cd0000;">cd</span> ~/.node_libraries/
-ln -s /where/you/wanna/put/it/UglifyJS/uglify-js.js
-
-<span style="color: #00008b;">## </span><span style="color: #00008b;">and if you want the CLI script too:
-</span>mkdir -p ~/bin
-<span style="color: #cd0000;">cd</span> ~/bin
-ln -s /where/you/wanna/put/it/UglifyJS/bin/uglifyjs
- <span style="color: #00008b;"># </span><span style="color: #00008b;">(then add ~/bin to your $PATH if it's not there already)
-</span></pre>
-
-
-
-</div>
-
-</div>
-
-<div id="outline-container-1_4" class="outline-3">
-<h3 id="sec-1_4"><span class="section-number-3">1.4</span> Usage </h3>
-<div class="outline-text-3" id="text-1_4">
-
-
-<p>
-There is a command-line tool that exposes the functionality of this library
-for your shell-scripting needs:
-</p>
-
-
-
-<pre class="src src-sh">uglifyjs [ options... ] [ filename ]
-</pre>
-
-
-
-<p>
-<code>filename</code> should be the last argument and should name the file from which
-to read the JavaScript code. If you don't specify it, it will read code
-from STDIN.
-</p>
-<p>
-Supported options:
-</p>
-<ul>
-<li>
-<code>-b</code> or <code>--beautify</code> — output indented code; when passed, additional
-options control the beautifier:
-
-<ul>
-<li>
-<code>-i N</code> or <code>--indent N</code> — indentation level (number of spaces)
-
-</li>
-<li>
-<code>-q</code> or <code>--quote-keys</code> — quote keys in literal objects (by default,
-only keys that cannot be identifier names will be quotes).
-
-</li>
-</ul>
-</li>
-<li>
-<code>--ascii</code> — pass this argument to encode non-ASCII characters as
-<code>\uXXXX</code> sequences. By default UglifyJS won't bother to do it and will
-output Unicode characters instead. (the output is always encoded in UTF8,
-but if you pass this option you'll only get ASCII).
-
-</li>
-<li>
-<code>-nm</code> or <code>--no-mangle</code> — don't mangle variable names
-
-</li>
-<li>
-<code>-ns</code> or <code>--no-squeeze</code> — don't call <code>ast_squeeze()</code> (which does various
-optimizations that result in smaller, less readable code).
-
-</li>
-<li>
-<code>-mt</code> or <code>--mangle-toplevel</code> — mangle names in the toplevel scope too
-(by default we don't do this).
-
-</li>
-<li>
-<code>--no-seqs</code> — when <code>ast_squeeze()</code> is called (thus, unless you pass
-<code>--no-squeeze</code>) it will reduce consecutive statements in blocks into a
-sequence. For example, "a = 10; b = 20; foo();" will be written as
-"a=10,b=20,foo();". In various occasions, this allows us to discard the
-block brackets (since the block becomes a single statement). This is ON
-by default because it seems safe and saves a few hundred bytes on some
-libs that I tested it on, but pass <code>--no-seqs</code> to disable it.
-
-</li>
-<li>
-<code>--no-dead-code</code> — by default, UglifyJS will remove code that is
-obviously unreachable (code that follows a <code>return</code>, <code>throw</code>, <code>break</code> or
-<code>continue</code> statement and is not a function/variable declaration). Pass
-this option to disable this optimization.
-
-</li>
-<li>
-<code>-nc</code> or <code>--no-copyright</code> — by default, <code>uglifyjs</code> will keep the initial
-comment tokens in the generated code (assumed to be copyright information
-etc.). If you pass this it will discard it.
-
-</li>
-<li>
-<code>-o filename</code> or <code>--output filename</code> — put the result in <code>filename</code>. If
-this isn't given, the result goes to standard output (or see next one).
-
-</li>
-<li>
-<code>--overwrite</code> — if the code is read from a file (not from STDIN) and you
-pass <code>--overwrite</code> then the output will be written in the same file.
-
-</li>
-<li>
-<code>--ast</code> — pass this if you want to get the Abstract Syntax Tree instead
-of JavaScript as output. Useful for debugging or learning more about the
-internals.
-
-</li>
-<li>
-<code>-v</code> or <code>--verbose</code> — output some notes on STDERR (for now just how long
-each operation takes).
-
-</li>
-<li>
-<code>--extra</code> — enable additional optimizations that have not yet been
-extensively tested. These might, or might not, break your code. If you
-find a bug using this option, please report a test case.
-
-</li>
-<li>
-<code>--unsafe</code> — enable other additional optimizations that are known to be
-unsafe in some contrived situations, but could still be generally useful.
-For now only this:
-
-<ul>
-<li>
-foo.toString() ==> foo+""
-
-</li>
-</ul>
-</li>
-<li>
-<code>--max-line-len</code> (default 32K characters) — add a newline after around
-32K characters. I've seen both FF and Chrome croak when all the code was
-on a single line of around 670K. Pass –max-line-len 0 to disable this
-safety feature.
-
-</li>
-<li>
-<code>--reserved-names</code> — some libraries rely on certain names to be used, as
-pointed out in issue #92 and #81, so this option allow you to exclude such
-names from the mangler. For example, to keep names <code>require</code> and <code>$super</code>
-intact you'd specify –reserved-names "require,$super".
-
-</li>
-<li>
-<code>--inline-script</code> – when you want to include the output literally in an
-HTML <code><script></code> tag you can use this option to prevent <code></script</code> from
-showing up in the output.
-</li>
-</ul>
-
-
-
-</div>
-
-<div id="outline-container-1_4_1" class="outline-4">
-<h4 id="sec-1_4_1"><span class="section-number-4">1.4.1</span> API </h4>
-<div class="outline-text-4" id="text-1_4_1">
-
-
-<p>
-To use the library from JavaScript, you'd do the following (example for
-NodeJS):
-</p>
-
-
-
-<pre class="src src-js"><span style="color: #8b0000;">var</span> <span style="color: #8b008b;">jsp</span> = require(<span style="color: #008b00;">"uglify-js"</span>).parser;
-<span style="color: #8b0000;">var</span> <span style="color: #8b008b;">pro</span> = require(<span style="color: #008b00;">"uglify-js"</span>).uglify;
-
-<span style="color: #8b0000;">var</span> <span style="color: #8b008b;">orig_code</span> = <span style="color: #008b00;">"... JS code here"</span>;
-<span style="color: #8b0000;">var</span> <span style="color: #8b008b;">ast</span> = jsp.parse(orig_code); <span style="color: #00008b;">// </span><span style="color: #00008b;">parse code and get the initial AST
-</span>ast = pro.ast_mangle(ast); <span style="color: #00008b;">// </span><span style="color: #00008b;">get a new AST with mangled names
-</span>ast = pro.ast_squeeze(ast); <span style="color: #00008b;">// </span><span style="color: #00008b;">get an AST with compression optimizations
-</span><span style="color: #8b0000;">var</span> <span style="color: #8b008b;">final_code</span> = pro.gen_code(ast); <span style="color: #00008b;">// </span><span style="color: #00008b;">compressed code here
-</span></pre>
-
-
-
-<p>
-The above performs the full compression that is possible right now. As you
-can see, there are a sequence of steps which you can apply. For example if
-you want compressed output but for some reason you don't want to mangle
-variable names, you would simply skip the line that calls
-<code>pro.ast_mangle(ast)</code>.
-</p>
-<p>
-Some of these functions take optional arguments. Here's a description:
-</p>
-<ul>
-<li>
-<code>jsp.parse(code, strict_semicolons)</code> – parses JS code and returns an AST.
-<code>strict_semicolons</code> is optional and defaults to <code>false</code>. If you pass
-<code>true</code> then the parser will throw an error when it expects a semicolon and
-it doesn't find it. For most JS code you don't want that, but it's useful
-if you want to strictly sanitize your code.
-
-</li>
-<li>
-<code>pro.ast_mangle(ast, options)</code> – generates a new AST containing mangled
-(compressed) variable and function names. It supports the following
-options:
-
-<ul>
-<li>
-<code>toplevel</code> – mangle toplevel names (by default we don't touch them).
-</li>
-<li>
-<code>except</code> – an array of names to exclude from compression.
-
-</li>
-</ul>
-</li>
-<li>
-<code>pro.ast_squeeze(ast, options)</code> – employs further optimizations designed
-to reduce the size of the code that <code>gen_code</code> would generate from the
-AST. Returns a new AST. <code>options</code> can be a hash; the supported options
-are:
-
-<ul>
-<li>
-<code>make_seqs</code> (default true) which will cause consecutive statements in a
-block to be merged using the "sequence" (comma) operator
-
-</li>
-<li>
-<code>dead_code</code> (default true) which will remove unreachable code.
-
-</li>
-</ul>
-</li>
-<li>
-<code>pro.gen_code(ast, options)</code> – generates JS code from the AST. By
-default it's minified, but using the <code>options</code> argument you can get nicely
-formatted output. <code>options</code> is, well, optional :-) and if you pass it it
-must be an object and supports the following properties (below you can see
-the default values):
-
-<ul>
-<li>
-<code>beautify: false</code> – pass <code>true</code> if you want indented output
-</li>
-<li>
-<code>indent_start: 0</code> (only applies when <code>beautify</code> is <code>true</code>) – initial
-indentation in spaces
-</li>
-<li>
-<code>indent_level: 4</code> (only applies when <code>beautify</code> is <code>true</code>) --
-indentation level, in spaces (pass an even number)
-</li>
-<li>
-<code>quote_keys: false</code> – if you pass <code>true</code> it will quote all keys in
-literal objects
-</li>
-<li>
-<code>space_colon: false</code> (only applies when <code>beautify</code> is <code>true</code>) – wether
-to put a space before the colon in object literals
-</li>
-<li>
-<code>ascii_only: false</code> – pass <code>true</code> if you want to encode non-ASCII
-characters as <code>\uXXXX</code>.
-</li>
-<li>
-<code>inline_script: false</code> – pass <code>true</code> to escape occurrences of
-<code></script</code> in strings
-</li>
-</ul>
-</li>
-</ul>
-
-
-</div>
-
-</div>
-
-<div id="outline-container-1_4_2" class="outline-4">
-<h4 id="sec-1_4_2"><span class="section-number-4">1.4.2</span> Beautifier shortcoming – no more comments </h4>
-<div class="outline-text-4" id="text-1_4_2">
-
-
-<p>
-The beautifier can be used as a general purpose indentation tool. It's
-useful when you want to make a minified file readable. One limitation,
-though, is that it discards all comments, so you don't really want to use it
-to reformat your code, unless you don't have, or don't care about, comments.
-</p>
-<p>
-In fact it's not the beautifier who discards comments — they are dumped at
-the parsing stage, when we build the initial AST. Comments don't really
-make sense in the AST, and while we could add nodes for them, it would be
-inconvenient because we'd have to add special rules to ignore them at all
-the processing stages.
-</p>
-</div>
-</div>
-
-</div>
-
-<div id="outline-container-1_5" class="outline-3">
-<h3 id="sec-1_5"><span class="section-number-3">1.5</span> Compression – how good is it? </h3>
-<div class="outline-text-3" id="text-1_5">
-
-
-<p>
-(XXX: this is somewhat outdated. On the jQuery source code we beat Closure
-by 168 bytes (560 after gzip) and by many seconds.)
-</p>
-<p>
-There are a few popular JS minifiers nowadays – the two most well known
-being the GoogleClosure (GCL) compiler and the YUI compressor. For some
-reason they are both written in Java. I didn't really hope to beat any of
-them, but finally I did – UglifyJS compresses better than the YUI
-compressor, and safer than GoogleClosure.
-</p>
-<p>
-I tested it on two big libraries. <a href="http://www.dynarchlib.com/">DynarchLIB</a> is my own, and it's big enough
-to contain probably all the JavaScript tricks known to mankind. <a href="http://jquery.com/">jQuery</a> is
-definitely the most popular JavaScript library (to some people, it's a
-synonym to JavaScript itself).
-</p>
-<p>
-I cannot swear that there are no bugs in the generated codes, but they
-appear to work fine.
-</p>
-<p>
-Compression results:
-</p>
-<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
-<caption></caption>
-<colgroup><col align="left" /><col align="right" /><col align="right" /><col align="left" /><col align="left" />
-</colgroup>
-<thead>
-<tr><th scope="col">Library</th><th scope="col">Orig. size</th><th scope="col">UglifyJS</th><th scope="col">YUI</th><th scope="col">GCL</th></tr>
-</thead>
-<tbody>
-<tr><td>DynarchLIB</td><td>636896</td><td>241441</td><td>246452 (+5011)</td><td>240439 (-1002) (buggy)</td></tr>
-<tr><td>jQuery</td><td>163855</td><td>72006</td><td>79702 (+7696)</td><td>71858 (-148)</td></tr>
-</tbody>
-</table>
-
-
-<p>
-UglifyJS is the fastest to run. On my laptop UglifyJS takes 1.35s for
-DynarchLIB, while YUI takes 2.7s and GCL takes 6.5s.
-</p>
-<p>
-GoogleClosure does a lot of smart ass optimizations. I had to strive really
-hard to get close to it. It should be possible to even beat it, but then
-again, GCL has a gazillion lines of code and runs terribly slow, so I'm not
-sure it worths spending the effort to save a few bytes. Also, GCL doesn't
-cope with <code>eval()</code> or <code>with{}</code> – it just dumps a warning and proceeds to
-mangle names anyway; my DynarchLIB compiled with it is buggy because of
-this.
-</p>
-<p>
-UglifyJS consists of ~1100 lines of code for the tokenizer/parser, and ~1100
-lines for the compressor and code generator. That should make it very
-maintainable and easily extensible, so I would say it has a good place in
-this field and it's bound to become the de-facto standard JS minifier. And
-I shall rule the world. :-) Use it, and <b>spread the word</b>!
-</p>
-</div>
-
-</div>
-
-<div id="outline-container-1_6" class="outline-3">
-<h3 id="sec-1_6"><span class="section-number-3">1.6</span> Bugs? </h3>
-<div class="outline-text-3" id="text-1_6">
-
-
-<p>
-Unfortunately, for the time being there is no automated test suite. But I
-ran the compressor manually on non-trivial code, and then I tested that the
-generated code works as expected. A few hundred times.
-</p>
-<p>
-DynarchLIB was started in times when there was no good JS minifier.
-Therefore I was quite religious about trying to write short code manually,
-and as such DL contains a lot of syntactic hacks<sup><a class="footref" name="fnr.1" href="#fn.1">1</a></sup> such as “foo == bar ? a
-= 10 : b = 20”, though the more readable version would clearly be to use
-“if/else”.
-</p>
-<p>
-Since the parser/compressor runs fine on DL and jQuery, I'm quite confident
-that it's solid enough for production use. If you can identify any bugs,
-I'd love to hear about them (<a href="http://groups.google.com/group/uglifyjs">use the Google Group</a> or email me directly).
-</p>
-</div>
-
-</div>
-
-<div id="outline-container-1_7" class="outline-3">
-<h3 id="sec-1_7"><span class="section-number-3">1.7</span> Links </h3>
-<div class="outline-text-3" id="text-1_7">
-
-
-<ul>
-<li>
-Project at GitHub: <a href="http://github.com/mishoo/UglifyJS">http://github.com/mishoo/UglifyJS</a>
-</li>
-<li>
-Google Group: <a href="http://groups.google.com/group/uglifyjs">http://groups.google.com/group/uglifyjs</a>
-</li>
-<li>
-Common Lisp JS parser: <a href="http://marijn.haverbeke.nl/parse-js/">http://marijn.haverbeke.nl/parse-js/</a>
-</li>
-<li>
-JS-to-Lisp compiler: <a href="http://github.com/marijnh/js">http://github.com/marijnh/js</a>
-</li>
-<li>
-Common Lisp JS uglifier: <a href="http://github.com/mishoo/cl-uglify-js">http://github.com/mishoo/cl-uglify-js</a>
-</li>
-</ul>
-
-
-</div>
-
-</div>
-
-<div id="outline-container-1_8" class="outline-3">
-<h3 id="sec-1_8"><span class="section-number-3">1.8</span> License </h3>
-<div class="outline-text-3" id="text-1_8">
-
-
-<p>
-UglifyJS is released under the BSD license:
-</p>
-
-
-
-<pre class="example">Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
-Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
- * Redistributions of source code must retain the above
- copyright notice, this list of conditions and the following
- disclaimer.
-
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
-EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
-THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGE.
-</pre>
-
-
-
-
-</div>
-</div>
-</div>
-<div id="footnotes">
-<h2 class="footnotes">Footnotes: </h2>
-<div id="text-footnotes">
-<p class="footnote"><sup><a class="footnum" name="fn.1" href="#fnr.1">1</a></sup> I even reported a few bugs and suggested some fixes in the original
-<a href="http://marijn.haverbeke.nl/parse-js/">parse-js</a> library, and Marijn pushed fixes literally in minutes.
-</p>
-</div>
-</div>
-<div id="postamble">
-<p class="author"> Author: Mihai Bazon
-</p>
-<p class="date"> Date: 2011-07-14 12:50:31 EEST</p>
-<p class="creator">HTML generated by org-mode 7.01trans in emacs 23</p>
-</div>
-</div>
-</body>
-</html>
+++ /dev/null
-#+TITLE: UglifyJS -- a JavaScript parser/compressor/beautifier
-#+KEYWORDS: javascript, js, parser, compiler, compressor, mangle, minify, minifier
-#+DESCRIPTION: a JavaScript parser/compressor/beautifier in JavaScript
-#+STYLE: <link rel="stylesheet" type="text/css" href="docstyle.css" />
-#+AUTHOR: Mihai Bazon
-#+EMAIL: mihai.bazon@gmail.com
-
-* UglifyJS --- a JavaScript parser/compressor/beautifier
-
-This package implements a general-purpose JavaScript
-parser/compressor/beautifier toolkit. It is developed on [[http://nodejs.org/][NodeJS]], but it
-should work on any JavaScript platform supporting the CommonJS module system
-(and if your platform of choice doesn't support CommonJS, you can easily
-implement it, or discard the =exports.*= lines from UglifyJS sources).
-
-The tokenizer/parser generates an abstract syntax tree from JS code. You
-can then traverse the AST to learn more about the code, or do various
-manipulations on it. This part is implemented in [[../lib/parse-js.js][parse-js.js]] and it's a
-port to JavaScript of the excellent [[http://marijn.haverbeke.nl/parse-js/][parse-js]] Common Lisp library from [[http://marijn.haverbeke.nl/][Marijn
-Haverbeke]].
-
-( See [[http://github.com/mishoo/cl-uglify-js][cl-uglify-js]] if you're looking for the Common Lisp version of
-UglifyJS. )
-
-The second part of this package, implemented in [[../lib/process.js][process.js]], inspects and
-manipulates the AST generated by the parser to provide the following:
-
-- ability to re-generate JavaScript code from the AST. Optionally
- indented---you can use this if you want to “beautify” a program that has
- been compressed, so that you can inspect the source. But you can also run
- our code generator to print out an AST without any whitespace, so you
- achieve compression as well.
-
-- shorten variable names (usually to single characters). Our mangler will
- analyze the code and generate proper variable names, depending on scope
- and usage, and is smart enough to deal with globals defined elsewhere, or
- with =eval()= calls or =with{}= statements. In short, if =eval()= or
- =with{}= are used in some scope, then all variables in that scope and any
- variables in the parent scopes will remain unmangled, and any references
- to such variables remain unmangled as well.
-
-- various small optimizations that may lead to faster code but certainly
- lead to smaller code. Where possible, we do the following:
-
- - foo["bar"] ==> foo.bar
-
- - remove block brackets ={}=
-
- - join consecutive var declarations:
- var a = 10; var b = 20; ==> var a=10,b=20;
-
- - resolve simple constant expressions: 1 +2 * 3 ==> 7. We only do the
- replacement if the result occupies less bytes; for example 1/3 would
- translate to 0.333333333333, so in this case we don't replace it.
-
- - consecutive statements in blocks are merged into a sequence; in many
- cases, this leaves blocks with a single statement, so then we can remove
- the block brackets.
-
- - various optimizations for IF statements:
-
- - if (foo) bar(); else baz(); ==> foo?bar():baz();
- - if (!foo) bar(); else baz(); ==> foo?baz():bar();
- - if (foo) bar(); ==> foo&&bar();
- - if (!foo) bar(); ==> foo||bar();
- - if (foo) return bar(); else return baz(); ==> return foo?bar():baz();
- - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
-
- - remove some unreachable code and warn about it (code that follows a
- =return=, =throw=, =break= or =continue= statement, except
- function/variable declarations).
-
-** <<Unsafe transformations>>
-
-UglifyJS tries its best to achieve great compression while leaving the
-semantics of the code intact. In general, if your code logic is broken by
-UglifyJS then it's a bug in UglifyJS and you should report it and I should
-fix it. :-)
-
-However, I opted to include the following potentially unsafe transformations
-as default behavior. Discussion is welcome, if you have ideas of how to
-handle this better, or any objections to these optimizations, please let me
-know.
-
-*** Calls involving the global Array constructor
-
-The following transformations occur:
-
-#+BEGIN_SRC js
-new Array(1, 2, 3, 4) => [1,2,3,4]
-Array(a, b, c) => [a,b,c]
-new Array(5) => Array(5)
-new Array(a) => Array(a)
-#+END_SRC
-
-These are all safe if the Array name isn't redefined. JavaScript does allow
-one to globally redefine Array (and pretty much everything, in fact) but I
-personally don't see why would anyone do that.
-
-UglifyJS does handle the case where Array is redefined locally, or even
-globally but with a =function= or =var= declaration. Therefore, in the
-following cases UglifyJS *doesn't touch* calls or instantiations of Array:
-
-#+BEGIN_SRC js
-// case 1. globally declared variable
- var Array;
- new Array(1, 2, 3);
- Array(a, b);
-
- // or (can be declared later)
- new Array(1, 2, 3);
- var Array;
-
- // or (can be a function)
- new Array(1, 2, 3);
- function Array() { ... }
-
-// case 2. declared in a function
- (function(){
- a = new Array(1, 2, 3);
- b = Array(5, 6);
- var Array;
- })();
-
- // or
- (function(Array){
- return Array(5, 6, 7);
- })();
-
- // or
- (function(){
- return new Array(1, 2, 3, 4);
- function Array() { ... }
- })();
-
- // etc.
-#+END_SRC
-
-** Install (NPM)
-
-UglifyJS is now available through NPM --- =npm install uglify-js= should do
-the job.
-
-** Install latest code from GitHub
-
-#+BEGIN_SRC sh
-## clone the repository
-mkdir -p /where/you/wanna/put/it
-cd /where/you/wanna/put/it
-git clone git://github.com/mishoo/UglifyJS.git
-
-## make the module available to Node
-mkdir -p ~/.node_libraries/
-cd ~/.node_libraries/
-ln -s /where/you/wanna/put/it/UglifyJS/uglify-js.js
-
-## and if you want the CLI script too:
-mkdir -p ~/bin
-cd ~/bin
-ln -s /where/you/wanna/put/it/UglifyJS/bin/uglifyjs
- # (then add ~/bin to your $PATH if it's not there already)
-#+END_SRC
-
-** Usage
-
-There is a command-line tool that exposes the functionality of this library
-for your shell-scripting needs:
-
-#+BEGIN_SRC sh
-uglifyjs [ options... ] [ filename ]
-#+END_SRC
-
-=filename= should be the last argument and should name the file from which
-to read the JavaScript code. If you don't specify it, it will read code
-from STDIN.
-
-Supported options:
-
-- =-b= or =--beautify= --- output indented code; when passed, additional
- options control the beautifier:
-
- - =-i N= or =--indent N= --- indentation level (number of spaces)
-
- - =-q= or =--quote-keys= --- quote keys in literal objects (by default,
- only keys that cannot be identifier names will be quotes).
-
-- =--ascii= --- pass this argument to encode non-ASCII characters as
- =\uXXXX= sequences. By default UglifyJS won't bother to do it and will
- output Unicode characters instead. (the output is always encoded in UTF8,
- but if you pass this option you'll only get ASCII).
-
-- =-nm= or =--no-mangle= --- don't mangle variable names
-
-- =-ns= or =--no-squeeze= --- don't call =ast_squeeze()= (which does various
- optimizations that result in smaller, less readable code).
-
-- =-mt= or =--mangle-toplevel= --- mangle names in the toplevel scope too
- (by default we don't do this).
-
-- =--no-seqs= --- when =ast_squeeze()= is called (thus, unless you pass
- =--no-squeeze=) it will reduce consecutive statements in blocks into a
- sequence. For example, "a = 10; b = 20; foo();" will be written as
- "a=10,b=20,foo();". In various occasions, this allows us to discard the
- block brackets (since the block becomes a single statement). This is ON
- by default because it seems safe and saves a few hundred bytes on some
- libs that I tested it on, but pass =--no-seqs= to disable it.
-
-- =--no-dead-code= --- by default, UglifyJS will remove code that is
- obviously unreachable (code that follows a =return=, =throw=, =break= or
- =continue= statement and is not a function/variable declaration). Pass
- this option to disable this optimization.
-
-- =-nc= or =--no-copyright= --- by default, =uglifyjs= will keep the initial
- comment tokens in the generated code (assumed to be copyright information
- etc.). If you pass this it will discard it.
-
-- =-o filename= or =--output filename= --- put the result in =filename=. If
- this isn't given, the result goes to standard output (or see next one).
-
-- =--overwrite= --- if the code is read from a file (not from STDIN) and you
- pass =--overwrite= then the output will be written in the same file.
-
-- =--ast= --- pass this if you want to get the Abstract Syntax Tree instead
- of JavaScript as output. Useful for debugging or learning more about the
- internals.
-
-- =-v= or =--verbose= --- output some notes on STDERR (for now just how long
- each operation takes).
-
-- =--extra= --- enable additional optimizations that have not yet been
- extensively tested. These might, or might not, break your code. If you
- find a bug using this option, please report a test case.
-
-- =--unsafe= --- enable other additional optimizations that are known to be
- unsafe in some contrived situations, but could still be generally useful.
- For now only this:
-
- - foo.toString() ==> foo+""
-
-- =--max-line-len= (default 32K characters) --- add a newline after around
- 32K characters. I've seen both FF and Chrome croak when all the code was
- on a single line of around 670K. Pass --max-line-len 0 to disable this
- safety feature.
-
-- =--reserved-names= --- some libraries rely on certain names to be used, as
- pointed out in issue #92 and #81, so this option allow you to exclude such
- names from the mangler. For example, to keep names =require= and =$super=
- intact you'd specify --reserved-names "require,$super".
-
-- =--inline-script= -- when you want to include the output literally in an
- HTML =<script>= tag you can use this option to prevent =</script= from
- showing up in the output.
-
-*** API
-
-To use the library from JavaScript, you'd do the following (example for
-NodeJS):
-
-#+BEGIN_SRC js
-var jsp = require("uglify-js").parser;
-var pro = require("uglify-js").uglify;
-
-var orig_code = "... JS code here";
-var ast = jsp.parse(orig_code); // parse code and get the initial AST
-ast = pro.ast_mangle(ast); // get a new AST with mangled names
-ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
-var final_code = pro.gen_code(ast); // compressed code here
-#+END_SRC
-
-The above performs the full compression that is possible right now. As you
-can see, there are a sequence of steps which you can apply. For example if
-you want compressed output but for some reason you don't want to mangle
-variable names, you would simply skip the line that calls
-=pro.ast_mangle(ast)=.
-
-Some of these functions take optional arguments. Here's a description:
-
-- =jsp.parse(code, strict_semicolons)= -- parses JS code and returns an AST.
- =strict_semicolons= is optional and defaults to =false=. If you pass
- =true= then the parser will throw an error when it expects a semicolon and
- it doesn't find it. For most JS code you don't want that, but it's useful
- if you want to strictly sanitize your code.
-
-- =pro.ast_mangle(ast, options)= -- generates a new AST containing mangled
- (compressed) variable and function names. It supports the following
- options:
-
- - =toplevel= -- mangle toplevel names (by default we don't touch them).
- - =except= -- an array of names to exclude from compression.
-
-- =pro.ast_squeeze(ast, options)= -- employs further optimizations designed
- to reduce the size of the code that =gen_code= would generate from the
- AST. Returns a new AST. =options= can be a hash; the supported options
- are:
-
- - =make_seqs= (default true) which will cause consecutive statements in a
- block to be merged using the "sequence" (comma) operator
-
- - =dead_code= (default true) which will remove unreachable code.
-
-- =pro.gen_code(ast, options)= -- generates JS code from the AST. By
- default it's minified, but using the =options= argument you can get nicely
- formatted output. =options= is, well, optional :-) and if you pass it it
- must be an object and supports the following properties (below you can see
- the default values):
-
- - =beautify: false= -- pass =true= if you want indented output
- - =indent_start: 0= (only applies when =beautify= is =true=) -- initial
- indentation in spaces
- - =indent_level: 4= (only applies when =beautify= is =true=) --
- indentation level, in spaces (pass an even number)
- - =quote_keys: false= -- if you pass =true= it will quote all keys in
- literal objects
- - =space_colon: false= (only applies when =beautify= is =true=) -- wether
- to put a space before the colon in object literals
- - =ascii_only: false= -- pass =true= if you want to encode non-ASCII
- characters as =\uXXXX=.
- - =inline_script: false= -- pass =true= to escape occurrences of
- =</script= in strings
-
-*** Beautifier shortcoming -- no more comments
-
-The beautifier can be used as a general purpose indentation tool. It's
-useful when you want to make a minified file readable. One limitation,
-though, is that it discards all comments, so you don't really want to use it
-to reformat your code, unless you don't have, or don't care about, comments.
-
-In fact it's not the beautifier who discards comments --- they are dumped at
-the parsing stage, when we build the initial AST. Comments don't really
-make sense in the AST, and while we could add nodes for them, it would be
-inconvenient because we'd have to add special rules to ignore them at all
-the processing stages.
-
-** Compression -- how good is it?
-
-(XXX: this is somewhat outdated. On the jQuery source code we beat Closure
-by 168 bytes (560 after gzip) and by many seconds.)
-
-There are a few popular JS minifiers nowadays -- the two most well known
-being the GoogleClosure (GCL) compiler and the YUI compressor. For some
-reason they are both written in Java. I didn't really hope to beat any of
-them, but finally I did -- UglifyJS compresses better than the YUI
-compressor, and safer than GoogleClosure.
-
-I tested it on two big libraries. [[http://www.dynarchlib.com/][DynarchLIB]] is my own, and it's big enough
-to contain probably all the JavaScript tricks known to mankind. [[http://jquery.com/][jQuery]] is
-definitely the most popular JavaScript library (to some people, it's a
-synonym to JavaScript itself).
-
-I cannot swear that there are no bugs in the generated codes, but they
-appear to work fine.
-
-Compression results:
-
-| Library | Orig. size | UglifyJS | YUI | GCL |
-|------------+------------+----------+----------------+------------------------|
-| DynarchLIB | 636896 | 241441 | 246452 (+5011) | 240439 (-1002) (buggy) |
-| jQuery | 163855 | 72006 | 79702 (+7696) | 71858 (-148) |
-
-UglifyJS is the fastest to run. On my laptop UglifyJS takes 1.35s for
-DynarchLIB, while YUI takes 2.7s and GCL takes 6.5s.
-
-GoogleClosure does a lot of smart ass optimizations. I had to strive really
-hard to get close to it. It should be possible to even beat it, but then
-again, GCL has a gazillion lines of code and runs terribly slow, so I'm not
-sure it worths spending the effort to save a few bytes. Also, GCL doesn't
-cope with =eval()= or =with{}= -- it just dumps a warning and proceeds to
-mangle names anyway; my DynarchLIB compiled with it is buggy because of
-this.
-
-UglifyJS consists of ~1100 lines of code for the tokenizer/parser, and ~1100
-lines for the compressor and code generator. That should make it very
-maintainable and easily extensible, so I would say it has a good place in
-this field and it's bound to become the de-facto standard JS minifier. And
-I shall rule the world. :-) Use it, and *spread the word*!
-
-** Bugs?
-
-Unfortunately, for the time being there is no automated test suite. But I
-ran the compressor manually on non-trivial code, and then I tested that the
-generated code works as expected. A few hundred times.
-
-DynarchLIB was started in times when there was no good JS minifier.
-Therefore I was quite religious about trying to write short code manually,
-and as such DL contains a lot of syntactic hacks[1] such as “foo == bar ? a
-= 10 : b = 20”, though the more readable version would clearly be to use
-“if/else”.
-
-Since the parser/compressor runs fine on DL and jQuery, I'm quite confident
-that it's solid enough for production use. If you can identify any bugs,
-I'd love to hear about them ([[http://groups.google.com/group/uglifyjs][use the Google Group]] or email me directly).
-
-[1] I even reported a few bugs and suggested some fixes in the original
- [[http://marijn.haverbeke.nl/parse-js/][parse-js]] library, and Marijn pushed fixes literally in minutes.
-
-** Links
-
-- Project at GitHub: [[http://github.com/mishoo/UglifyJS][http://github.com/mishoo/UglifyJS]]
-- Google Group: [[http://groups.google.com/group/uglifyjs][http://groups.google.com/group/uglifyjs]]
-- Common Lisp JS parser: [[http://marijn.haverbeke.nl/parse-js/][http://marijn.haverbeke.nl/parse-js/]]
-- JS-to-Lisp compiler: [[http://github.com/marijnh/js][http://github.com/marijnh/js]]
-- Common Lisp JS uglifier: [[http://github.com/mishoo/cl-uglify-js][http://github.com/mishoo/cl-uglify-js]]
-
-** License
-
-UglifyJS is released under the BSD license:
-
-#+BEGIN_EXAMPLE
-Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
-Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
- * Redistributions of source code must retain the above
- copyright notice, this list of conditions and the following
- disclaimer.
-
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials
- provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
-EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
-THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGE.
-#+END_EXAMPLE
+++ /dev/null
-#! /usr/bin/env node
-// -*- js -*-
-
-global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
-var fs = require("fs");
-var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js
- jsp = uglify.parser,
- pro = uglify.uglify;
-
-var options = {
- ast: false,
- mangle: true,
- mangle_toplevel: false,
- squeeze: true,
- make_seqs: true,
- dead_code: true,
- verbose: false,
- show_copyright: true,
- out_same_file: false,
- max_line_length: 32 * 1024,
- unsafe: false,
- reserved_names: null,
- defines: { },
- codegen_options: {
- ascii_only: false,
- beautify: false,
- indent_level: 4,
- indent_start: 0,
- quote_keys: false,
- space_colon: false,
- inline_script: false
- },
- make: false,
- output: true // stdout
-};
-
-var args = jsp.slice(process.argv, 2);
-var filename;
-
-out: while (args.length > 0) {
- var v = args.shift();
- switch (v) {
- case "-b":
- case "--beautify":
- options.codegen_options.beautify = true;
- break;
- case "-i":
- case "--indent":
- options.codegen_options.indent_level = args.shift();
- break;
- case "-q":
- case "--quote-keys":
- options.codegen_options.quote_keys = true;
- break;
- case "-mt":
- case "--mangle-toplevel":
- options.mangle_toplevel = true;
- break;
- case "--no-mangle":
- case "-nm":
- options.mangle = false;
- break;
- case "--no-squeeze":
- case "-ns":
- options.squeeze = false;
- break;
- case "--no-seqs":
- options.make_seqs = false;
- break;
- case "--no-dead-code":
- options.dead_code = false;
- break;
- case "--no-copyright":
- case "-nc":
- options.show_copyright = false;
- break;
- case "-o":
- case "--output":
- options.output = args.shift();
- break;
- case "--overwrite":
- options.out_same_file = true;
- break;
- case "-v":
- case "--verbose":
- options.verbose = true;
- break;
- case "--ast":
- options.ast = true;
- break;
- case "--unsafe":
- options.unsafe = true;
- break;
- case "--max-line-len":
- options.max_line_length = parseInt(args.shift(), 10);
- break;
- case "--reserved-names":
- options.reserved_names = args.shift().split(",");
- break;
- case "-d":
- case "--define":
- var defarg = args.shift();
- try {
- var defsym = function(sym) {
- // KEYWORDS_ATOM doesn't include NaN or Infinity - should we check
- // for them too ?? We don't check reserved words and the like as the
- // define values are only substituted AFTER parsing
- if (jsp.KEYWORDS_ATOM.hasOwnProperty(sym)) {
- throw "Don't define values for inbuilt constant '"+sym+"'";
- }
- return sym;
- },
- defval = function(v) {
- if (v.match(/^"(.*)"$/) || v.match(/^'(.*)'$/)) {
- return [ "string", RegExp.$1 ];
- }
- else if (!isNaN(parseFloat(v))) {
- return [ "num", parseFloat(v) ];
- }
- else if (v.match(/^[a-z\$_][a-z\$_0-9]*$/i)) {
- return [ "name", v ];
- }
- else if (!v.match(/"/)) {
- return [ "string", v ];
- }
- else if (!v.match(/'/)) {
- return [ "string", v ];
- }
- throw "Can't understand the specified value: "+v;
- };
- if (defarg.match(/^([a-z_\$][a-z_\$0-9]*)(=(.*))?$/i)) {
- var sym = defsym(RegExp.$1),
- val = RegExp.$2 ? defval(RegExp.$2.substr(1)) : [ 'name', 'true' ];
- options.defines[sym] = val;
- }
- else {
- throw "The --define option expects SYMBOL[=value]";
- }
- } catch(ex) {
- sys.print("ERROR: In option --define "+defarg+"\n"+ex+"\n");
- process.exit(1);
- }
- break;
- case "--define-from-module":
- var defmodarg = args.shift(),
- defmodule = require(defmodarg),
- sym,
- val;
- for (sym in defmodule) {
- if (defmodule.hasOwnProperty(sym)) {
- options.defines[sym] = function(val) {
- if (typeof val == "string")
- return [ "string", val ];
- if (typeof val == "number")
- return [ "num", val ];
- if (val === true)
- return [ 'name', 'true' ];
- if (val === false)
- return [ 'name', 'false' ];
- if (val === null)
- return [ 'name', 'null' ];
- if (val === undefined)
- return [ 'name', 'undefined' ];
- sys.print("ERROR: In option --define-from-module "+defmodarg+"\n");
- sys.print("ERROR: Unknown object type for: "+sym+"="+val+"\n");
- process.exit(1);
- return null;
- }(defmodule[sym]);
- }
- }
- break;
- case "--ascii":
- options.codegen_options.ascii_only = true;
- break;
- case "--make":
- options.make = true;
- break;
- case "--inline-script":
- options.codegen_options.inline_script = true;
- break;
- default:
- filename = v;
- break out;
- }
-}
-
-if (options.verbose) {
- pro.set_logger(function(msg){
- sys.debug(msg);
- });
-}
-
-jsp.set_logger(function(msg){
- sys.debug(msg);
-});
-
-if (options.make) {
- options.out_same_file = false; // doesn't make sense in this case
- var makefile = JSON.parse(fs.readFileSync(filename || "Makefile.uglify.js").toString());
- output(makefile.files.map(function(file){
- var code = fs.readFileSync(file.name);
- if (file.module) {
- code = "!function(exports, global){global = this;\n" + code + "\n;this." + file.module + " = exports;}({})";
- }
- else if (file.hide) {
- code = "(function(){" + code + "}());";
- }
- return squeeze_it(code);
- }).join("\n"));
-}
-else if (filename) {
- fs.readFile(filename, "utf8", function(err, text){
- if (err) throw err;
- output(squeeze_it(text));
- });
-}
-else {
- var stdin = process.openStdin();
- stdin.setEncoding("utf8");
- var text = "";
- stdin.on("data", function(chunk){
- text += chunk;
- });
- stdin.on("end", function() {
- output(squeeze_it(text));
- });
-}
-
-function output(text) {
- var out;
- if (options.out_same_file && filename)
- options.output = filename;
- if (options.output === true) {
- out = process.stdout;
- } else {
- out = fs.createWriteStream(options.output, {
- flags: "w",
- encoding: "utf8",
- mode: 0644
- });
- }
- out.write(text);
- if (options.output !== true) {
- out.end();
- }
-};
-
-// --------- main ends here.
-
-function show_copyright(comments) {
- var ret = "";
- for (var i = 0; i < comments.length; ++i) {
- var c = comments[i];
- if (c.type == "comment1") {
- ret += "//" + c.value + "\n";
- } else {
- ret += "/*" + c.value + "*/";
- }
- }
- return ret;
-};
-
-function squeeze_it(code) {
- var result = "";
- if (options.show_copyright) {
- var tok = jsp.tokenizer(code), c;
- c = tok();
- result += show_copyright(c.comments_before);
- }
- try {
- var ast = time_it("parse", function(){ return jsp.parse(code); });
- if (options.mangle) ast = time_it("mangle", function(){
- return pro.ast_mangle(ast, {
- toplevel: options.mangle_toplevel,
- defines: options.defines,
- except: options.reserved_names
- });
- });
- if (options.squeeze) ast = time_it("squeeze", function(){
- ast = pro.ast_squeeze(ast, {
- make_seqs : options.make_seqs,
- dead_code : options.dead_code,
- keep_comps : !options.unsafe
- });
- if (options.unsafe)
- ast = pro.ast_squeeze_more(ast);
- return ast;
- });
- if (options.ast)
- return sys.inspect(ast, null, null);
- result += time_it("generate", function(){ return pro.gen_code(ast, options.codegen_options) });
- if (!options.codegen_options.beautify && options.max_line_length) {
- result = time_it("split", function(){ return pro.split_lines(result, options.max_line_length) });
- }
- return result;
- } catch(ex) {
- sys.debug(ex.stack);
- sys.debug(sys.inspect(ex));
- sys.debug(JSON.stringify(ex));
- }
-};
-
-function time_it(name, cont) {
- if (!options.verbose)
- return cont();
- var t1 = new Date().getTime();
- try { return cont(); }
- finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
-};
+++ /dev/null
-html { font-family: "Lucida Grande","Trebuchet MS",sans-serif; font-size: 12pt; }
-body { max-width: 60em; }
-.title { text-align: center; }
-.todo { color: red; }
-.done { color: green; }
-.tag { background-color:lightblue; font-weight:normal }
-.target { }
-.timestamp { color: grey }
-.timestamp-kwd { color: CadetBlue }
-p.verse { margin-left: 3% }
-pre {
- border: 1pt solid #AEBDCC;
- background-color: #F3F5F7;
- padding: 5pt;
- font-family: monospace;
- font-size: 90%;
- overflow:auto;
-}
-pre.src {
- background-color: #eee; color: #112; border: 1px solid #000;
-}
-table { border-collapse: collapse; }
-td, th { vertical-align: top; }
-dt { font-weight: bold; }
-div.figure { padding: 0.5em; }
-div.figure p { text-align: center; }
-.linenr { font-size:smaller }
-.code-highlighted {background-color:#ffff00;}
-.org-info-js_info-navigation { border-style:none; }
-#org-info-js_console-label { font-size:10px; font-weight:bold;
- white-space:nowrap; }
-.org-info-js_search-highlight {background-color:#ffff00; color:#000000;
- font-weight:bold; }
-
-sup {
- vertical-align: baseline;
- position: relative;
- top: -0.5em;
- font-size: 80%;
-}
-
-sup a:link, sup a:visited {
- text-decoration: none;
- color: #c00;
-}
-
-sup a:before { content: "["; color: #999; }
-sup a:after { content: "]"; color: #999; }
-
-h1.title { border-bottom: 4px solid #000; padding-bottom: 5px; margin-bottom: 2em; }
-
-#postamble {
- color: #777;
- font-size: 90%;
- padding-top: 1em; padding-bottom: 1em; border-top: 1px solid #999;
- margin-top: 2em;
- padding-left: 2em;
- padding-right: 2em;
- text-align: right;
-}
-
-#postamble p { margin: 0; }
-
-#footnotes { border-top: 1px solid #000; }
-
-h1 { font-size: 200% }
-h2 { font-size: 175% }
-h3 { font-size: 150% }
-h4 { font-size: 125% }
-
-h1, h2, h3, h4 { font-family: "Bookman",Georgia,"Times New Roman",serif; font-weight: normal; }
-
-@media print {
- html { font-size: 11pt; }
-}
+++ /dev/null
-/***********************************************************************
-
- A JavaScript tokenizer / parser / beautifier / compressor.
-
- This version is suitable for Node.js. With minimal changes (the
- exports stuff) it should work on any JS platform.
-
- This file contains the tokenizer/parser. It is a port to JavaScript
- of parse-js [1], a JavaScript parser library written in Common Lisp
- by Marijn Haverbeke. Thank you Marijn!
-
- [1] http://marijn.haverbeke.nl/parse-js/
-
- Exported functions:
-
- - tokenizer(code) -- returns a function. Call the returned
- function to fetch the next token.
-
- - parse(code) -- returns an AST of the given JavaScript code.
-
- -------------------------------- (C) ---------------------------------
-
- Author: Mihai Bazon
- <mihai.bazon@gmail.com>
- http://mihai.bazon.net/blog
-
- Distributed under the BSD license:
-
- Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
- Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
-
- * Redistributions of source code must retain the above
- copyright notice, this list of conditions and the following
- disclaimer.
-
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials
- provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- SUCH DAMAGE.
-
- ***********************************************************************/
-
-/* -----[ Tokenizer (constants) ]----- */
-
-var KEYWORDS = array_to_hash([
- "break",
- "case",
- "catch",
- "const",
- "continue",
- "default",
- "delete",
- "do",
- "else",
- "finally",
- "for",
- "function",
- "if",
- "in",
- "instanceof",
- "new",
- "return",
- "switch",
- "throw",
- "try",
- "typeof",
- "var",
- "void",
- "while",
- "with"
-]);
-
-var RESERVED_WORDS = array_to_hash([
- "abstract",
- "boolean",
- "byte",
- "char",
- "class",
- "debugger",
- "double",
- "enum",
- "export",
- "extends",
- "final",
- "float",
- "goto",
- "implements",
- "import",
- "int",
- "interface",
- "long",
- "native",
- "package",
- "private",
- "protected",
- "public",
- "short",
- "static",
- "super",
- "synchronized",
- "throws",
- "transient",
- "volatile"
-]);
-
-var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([
- "return",
- "new",
- "delete",
- "throw",
- "else",
- "case"
-]);
-
-var KEYWORDS_ATOM = array_to_hash([
- "false",
- "null",
- "true",
- "undefined"
-]);
-
-var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^"));
-
-var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
-var RE_OCT_NUMBER = /^0[0-7]+$/;
-var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
-
-var OPERATORS = array_to_hash([
- "in",
- "instanceof",
- "typeof",
- "new",
- "void",
- "delete",
- "++",
- "--",
- "+",
- "-",
- "!",
- "~",
- "&",
- "|",
- "^",
- "*",
- "/",
- "%",
- ">>",
- "<<",
- ">>>",
- "<",
- ">",
- "<=",
- ">=",
- "==",
- "===",
- "!=",
- "!==",
- "?",
- "=",
- "+=",
- "-=",
- "/=",
- "*=",
- "%=",
- ">>=",
- "<<=",
- ">>>=",
- "|=",
- "^=",
- "&=",
- "&&",
- "||"
-]);
-
-var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\v\u200b"));
-
-var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
-
-var PUNC_CHARS = array_to_hash(characters("[]{}(),;:"));
-
-var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy"));
-
-/* -----[ Tokenizer ]----- */
-
-// regexps adapted from http://xregexp.com/plugins/#unicode
-var UNICODE = {
- letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"),
- non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"),
- space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"),
- connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]")
-};
-
-function is_letter(ch) {
- return UNICODE.letter.test(ch);
-};
-
-function is_digit(ch) {
- ch = ch.charCodeAt(0);
- return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9
-};
-
-function is_alphanumeric_char(ch) {
- return is_digit(ch) || is_letter(ch);
-};
-
-function is_unicode_combining_mark(ch) {
- return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch);
-};
-
-function is_unicode_connector_punctuation(ch) {
- return UNICODE.connector_punctuation.test(ch);
-};
-
-function is_identifier_start(ch) {
- return ch == "$" || ch == "_" || is_letter(ch);
-};
-
-function is_identifier_char(ch) {
- return is_identifier_start(ch)
- || is_unicode_combining_mark(ch)
- || is_digit(ch)
- || is_unicode_connector_punctuation(ch)
- || ch == "\u200c" // zero-width non-joiner <ZWNJ>
- || ch == "\u200d" // zero-width joiner <ZWJ> (in my ECMA-262 PDF, this is also 200c)
- ;
-};
-
-function parse_js_number(num) {
- if (RE_HEX_NUMBER.test(num)) {
- return parseInt(num.substr(2), 16);
- } else if (RE_OCT_NUMBER.test(num)) {
- return parseInt(num.substr(1), 8);
- } else if (RE_DEC_NUMBER.test(num)) {
- return parseFloat(num);
- }
-};
-
-function JS_Parse_Error(message, line, col, pos) {
- this.message = message;
- this.line = line;
- this.col = col;
- this.pos = pos;
- try {
- ({})();
- } catch(ex) {
- this.stack = ex.stack;
- };
-};
-
-JS_Parse_Error.prototype.toString = function() {
- return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack;
-};
-
-function js_error(message, line, col, pos) {
- throw new JS_Parse_Error(message, line, col, pos);
-};
-
-function is_token(token, type, val) {
- return token.type == type && (val == null || token.value == val);
-};
-
-var EX_EOF = {};
-
-function tokenizer($TEXT) {
-
- var S = {
- text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''),
- pos : 0,
- tokpos : 0,
- line : 0,
- tokline : 0,
- col : 0,
- tokcol : 0,
- newline_before : false,
- regex_allowed : false,
- comments_before : []
- };
-
- function peek() { return S.text.charAt(S.pos); };
-
- function next(signal_eof) {
- var ch = S.text.charAt(S.pos++);
- if (signal_eof && !ch)
- throw EX_EOF;
- if (ch == "\n") {
- S.newline_before = true;
- ++S.line;
- S.col = 0;
- } else {
- ++S.col;
- }
- return ch;
- };
-
- function eof() {
- return !S.peek();
- };
-
- function find(what, signal_eof) {
- var pos = S.text.indexOf(what, S.pos);
- if (signal_eof && pos == -1) throw EX_EOF;
- return pos;
- };
-
- function start_token() {
- S.tokline = S.line;
- S.tokcol = S.col;
- S.tokpos = S.pos;
- };
-
- function token(type, value, is_comment) {
- S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) ||
- (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) ||
- (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value)));
- var ret = {
- type : type,
- value : value,
- line : S.tokline,
- col : S.tokcol,
- pos : S.tokpos,
- nlb : S.newline_before
- };
- if (!is_comment) {
- ret.comments_before = S.comments_before;
- S.comments_before = [];
- }
- S.newline_before = false;
- return ret;
- };
-
- function skip_whitespace() {
- while (HOP(WHITESPACE_CHARS, peek()))
- next();
- };
-
- function read_while(pred) {
- var ret = "", ch = peek(), i = 0;
- while (ch && pred(ch, i++)) {
- ret += next();
- ch = peek();
- }
- return ret;
- };
-
- function parse_error(err) {
- js_error(err, S.tokline, S.tokcol, S.tokpos);
- };
-
- function read_num(prefix) {
- var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
- var num = read_while(function(ch, i){
- if (ch == "x" || ch == "X") {
- if (has_x) return false;
- return has_x = true;
- }
- if (!has_x && (ch == "E" || ch == "e")) {
- if (has_e) return false;
- return has_e = after_e = true;
- }
- if (ch == "-") {
- if (after_e || (i == 0 && !prefix)) return true;
- return false;
- }
- if (ch == "+") return after_e;
- after_e = false;
- if (ch == ".") {
- if (!has_dot && !has_x)
- return has_dot = true;
- return false;
- }
- return is_alphanumeric_char(ch);
- });
- if (prefix)
- num = prefix + num;
- var valid = parse_js_number(num);
- if (!isNaN(valid)) {
- return token("num", valid);
- } else {
- parse_error("Invalid syntax: " + num);
- }
- };
-
- function read_escaped_char() {
- var ch = next(true);
- switch (ch) {
- case "n" : return "\n";
- case "r" : return "\r";
- case "t" : return "\t";
- case "b" : return "\b";
- case "v" : return "\v";
- case "f" : return "\f";
- case "0" : return "\0";
- case "x" : return String.fromCharCode(hex_bytes(2));
- case "u" : return String.fromCharCode(hex_bytes(4));
- default : return ch;
- }
- };
-
- function hex_bytes(n) {
- var num = 0;
- for (; n > 0; --n) {
- var digit = parseInt(next(true), 16);
- if (isNaN(digit))
- parse_error("Invalid hex-character pattern in string");
- num = (num << 4) | digit;
- }
- return num;
- };
-
- function read_string() {
- return with_eof_error("Unterminated string constant", function(){
- var quote = next(), ret = "";
- for (;;) {
- var ch = next(true);
- if (ch == "\\") {
- // read OctalEscapeSequence (XXX: deprecated if "strict mode")
- // https://github.com/mishoo/UglifyJS/issues/178
- var octal_len = 0, first = null;
- ch = read_while(function(ch){
- if (ch >= "0" && ch <= "7") {
- if (!first) {
- first = ch;
- return ++octal_len;
- }
- else if (first <= "3" && octal_len <= 2) return ++octal_len;
- else if (first >= "4" && octal_len <= 1) return ++octal_len;
- }
- return false;
- });
- if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8));
- else ch = read_escaped_char();
- }
- else if (ch == quote) break;
- ret += ch;
- }
- return token("string", ret);
- });
- };
-
- function read_line_comment() {
- next();
- var i = find("\n"), ret;
- if (i == -1) {
- ret = S.text.substr(S.pos);
- S.pos = S.text.length;
- } else {
- ret = S.text.substring(S.pos, i);
- S.pos = i;
- }
- return token("comment1", ret, true);
- };
-
- function read_multiline_comment() {
- next();
- return with_eof_error("Unterminated multiline comment", function(){
- var i = find("*/", true),
- text = S.text.substring(S.pos, i),
- tok = token("comment2", text, true);
- S.pos = i + 2;
- S.line += text.split("\n").length - 1;
- S.newline_before = text.indexOf("\n") >= 0;
-
- // https://github.com/mishoo/UglifyJS/issues/#issue/100
- if (/^@cc_on/i.test(text)) {
- warn("WARNING: at line " + S.line);
- warn("*** Found \"conditional comment\": " + text);
- warn("*** UglifyJS DISCARDS ALL COMMENTS. This means your code might no longer work properly in Internet Explorer.");
- }
-
- return tok;
- });
- };
-
- function read_name() {
- var backslash = false, name = "", ch;
- while ((ch = peek()) != null) {
- if (!backslash) {
- if (ch == "\\") backslash = true, next();
- else if (is_identifier_char(ch)) name += next();
- else break;
- }
- else {
- if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX");
- ch = read_escaped_char();
- if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier");
- name += ch;
- backslash = false;
- }
- }
- return name;
- };
-
- function read_regexp() {
- return with_eof_error("Unterminated regular expression", function(){
- var prev_backslash = false, regexp = "", ch, in_class = false;
- while ((ch = next(true))) if (prev_backslash) {
- regexp += "\\" + ch;
- prev_backslash = false;
- } else if (ch == "[") {
- in_class = true;
- regexp += ch;
- } else if (ch == "]" && in_class) {
- in_class = false;
- regexp += ch;
- } else if (ch == "/" && !in_class) {
- break;
- } else if (ch == "\\") {
- prev_backslash = true;
- } else {
- regexp += ch;
- }
- var mods = read_name();
- return token("regexp", [ regexp, mods ]);
- });
- };
-
- function read_operator(prefix) {
- function grow(op) {
- if (!peek()) return op;
- var bigger = op + peek();
- if (HOP(OPERATORS, bigger)) {
- next();
- return grow(bigger);
- } else {
- return op;
- }
- };
- return token("operator", grow(prefix || next()));
- };
-
- function handle_slash() {
- next();
- var regex_allowed = S.regex_allowed;
- switch (peek()) {
- case "/":
- S.comments_before.push(read_line_comment());
- S.regex_allowed = regex_allowed;
- return next_token();
- case "*":
- S.comments_before.push(read_multiline_comment());
- S.regex_allowed = regex_allowed;
- return next_token();
- }
- return S.regex_allowed ? read_regexp() : read_operator("/");
- };
-
- function handle_dot() {
- next();
- return is_digit(peek())
- ? read_num(".")
- : token("punc", ".");
- };
-
- function read_word() {
- var word = read_name();
- return !HOP(KEYWORDS, word)
- ? token("name", word)
- : HOP(OPERATORS, word)
- ? token("operator", word)
- : HOP(KEYWORDS_ATOM, word)
- ? token("atom", word)
- : token("keyword", word);
- };
-
- function with_eof_error(eof_error, cont) {
- try {
- return cont();
- } catch(ex) {
- if (ex === EX_EOF) parse_error(eof_error);
- else throw ex;
- }
- };
-
- function next_token(force_regexp) {
- if (force_regexp)
- return read_regexp();
- skip_whitespace();
- start_token();
- var ch = peek();
- if (!ch) return token("eof");
- if (is_digit(ch)) return read_num();
- if (ch == '"' || ch == "'") return read_string();
- if (HOP(PUNC_CHARS, ch)) return token("punc", next());
- if (ch == ".") return handle_dot();
- if (ch == "/") return handle_slash();
- if (HOP(OPERATOR_CHARS, ch)) return read_operator();
- if (ch == "\\" || is_identifier_start(ch)) return read_word();
- parse_error("Unexpected character '" + ch + "'");
- };
-
- next_token.context = function(nc) {
- if (nc) S = nc;
- return S;
- };
-
- return next_token;
-
-};
-
-/* -----[ Parser (constants) ]----- */
-
-var UNARY_PREFIX = array_to_hash([
- "typeof",
- "void",
- "delete",
- "--",
- "++",
- "!",
- "~",
- "-",
- "+"
-]);
-
-var UNARY_POSTFIX = array_to_hash([ "--", "++" ]);
-
-var ASSIGNMENT = (function(a, ret, i){
- while (i < a.length) {
- ret[a[i]] = a[i].substr(0, a[i].length - 1);
- i++;
- }
- return ret;
-})(
- ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="],
- { "=": true },
- 0
-);
-
-var PRECEDENCE = (function(a, ret){
- for (var i = 0, n = 1; i < a.length; ++i, ++n) {
- var b = a[i];
- for (var j = 0; j < b.length; ++j) {
- ret[b[j]] = n;
- }
- }
- return ret;
-})(
- [
- ["||"],
- ["&&"],
- ["|"],
- ["^"],
- ["&"],
- ["==", "===", "!=", "!=="],
- ["<", ">", "<=", ">=", "in", "instanceof"],
- [">>", "<<", ">>>"],
- ["+", "-"],
- ["*", "/", "%"]
- ],
- {}
-);
-
-var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]);
-
-var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]);
-
-/* -----[ Parser ]----- */
-
-function NodeWithToken(str, start, end) {
- this.name = str;
- this.start = start;
- this.end = end;
-};
-
-NodeWithToken.prototype.toString = function() { return this.name; };
-
-function parse($TEXT, exigent_mode, embed_tokens) {
-
- var S = {
- input : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT,
- token : null,
- prev : null,
- peeked : null,
- in_function : 0,
- in_loop : 0,
- labels : []
- };
-
- S.token = next();
-
- function is(type, value) {
- return is_token(S.token, type, value);
- };
-
- function peek() { return S.peeked || (S.peeked = S.input()); };
-
- function next() {
- S.prev = S.token;
- if (S.peeked) {
- S.token = S.peeked;
- S.peeked = null;
- } else {
- S.token = S.input();
- }
- return S.token;
- };
-
- function prev() {
- return S.prev;
- };
-
- function croak(msg, line, col, pos) {
- var ctx = S.input.context();
- js_error(msg,
- line != null ? line : ctx.tokline,
- col != null ? col : ctx.tokcol,
- pos != null ? pos : ctx.tokpos);
- };
-
- function token_error(token, msg) {
- croak(msg, token.line, token.col);
- };
-
- function unexpected(token) {
- if (token == null)
- token = S.token;
- token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
- };
-
- function expect_token(type, val) {
- if (is(type, val)) {
- return next();
- }
- token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type);
- };
-
- function expect(punc) { return expect_token("punc", punc); };
-
- function can_insert_semicolon() {
- return !exigent_mode && (
- S.token.nlb || is("eof") || is("punc", "}")
- );
- };
-
- function semicolon() {
- if (is("punc", ";")) next();
- else if (!can_insert_semicolon()) unexpected();
- };
-
- function as() {
- return slice(arguments);
- };
-
- function parenthesised() {
- expect("(");
- var ex = expression();
- expect(")");
- return ex;
- };
-
- function add_tokens(str, start, end) {
- return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end);
- };
-
- function maybe_embed_tokens(parser) {
- if (embed_tokens) return function() {
- var start = S.token;
- var ast = parser.apply(this, arguments);
- ast[0] = add_tokens(ast[0], start, prev());
- return ast;
- };
- else return parser;
- };
-
- var statement = maybe_embed_tokens(function() {
- if (is("operator", "/")) {
- S.peeked = null;
- S.token = S.input(true); // force regexp
- }
- switch (S.token.type) {
- case "num":
- case "string":
- case "regexp":
- case "operator":
- case "atom":
- return simple_statement();
-
- case "name":
- return is_token(peek(), "punc", ":")
- ? labeled_statement(prog1(S.token.value, next, next))
- : simple_statement();
-
- case "punc":
- switch (S.token.value) {
- case "{":
- return as("block", block_());
- case "[":
- case "(":
- return simple_statement();
- case ";":
- next();
- return as("block");
- default:
- unexpected();
- }
-
- case "keyword":
- switch (prog1(S.token.value, next)) {
- case "break":
- return break_cont("break");
-
- case "continue":
- return break_cont("continue");
-
- case "debugger":
- semicolon();
- return as("debugger");
-
- case "do":
- return (function(body){
- expect_token("keyword", "while");
- return as("do", prog1(parenthesised, semicolon), body);
- })(in_loop(statement));
-
- case "for":
- return for_();
-
- case "function":
- return function_(true);
-
- case "if":
- return if_();
-
- case "return":
- if (S.in_function == 0)
- croak("'return' outside of function");
- return as("return",
- is("punc", ";")
- ? (next(), null)
- : can_insert_semicolon()
- ? null
- : prog1(expression, semicolon));
-
- case "switch":
- return as("switch", parenthesised(), switch_block_());
-
- case "throw":
- return as("throw", prog1(expression, semicolon));
-
- case "try":
- return try_();
-
- case "var":
- return prog1(var_, semicolon);
-
- case "const":
- return prog1(const_, semicolon);
-
- case "while":
- return as("while", parenthesised(), in_loop(statement));
-
- case "with":
- return as("with", parenthesised(), statement());
-
- default:
- unexpected();
- }
- }
- });
-
- function labeled_statement(label) {
- S.labels.push(label);
- var start = S.token, stat = statement();
- if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0]))
- unexpected(start);
- S.labels.pop();
- return as("label", label, stat);
- };
-
- function simple_statement() {
- return as("stat", prog1(expression, semicolon));
- };
-
- function break_cont(type) {
- var name;
- if (!can_insert_semicolon()) {
- name = is("name") ? S.token.value : null;
- }
- if (name != null) {
- next();
- if (!member(name, S.labels))
- croak("Label " + name + " without matching loop or statement");
- }
- else if (S.in_loop == 0)
- croak(type + " not inside a loop or switch");
- semicolon();
- return as(type, name);
- };
-
- function for_() {
- expect("(");
- var init = null;
- if (!is("punc", ";")) {
- init = is("keyword", "var")
- ? (next(), var_(true))
- : expression(true, true);
- if (is("operator", "in"))
- return for_in(init);
- }
- return regular_for(init);
- };
-
- function regular_for(init) {
- expect(";");
- var test = is("punc", ";") ? null : expression();
- expect(";");
- var step = is("punc", ")") ? null : expression();
- expect(")");
- return as("for", init, test, step, in_loop(statement));
- };
-
- function for_in(init) {
- var lhs = init[0] == "var" ? as("name", init[1][0]) : init;
- next();
- var obj = expression();
- expect(")");
- return as("for-in", init, lhs, obj, in_loop(statement));
- };
-
- var function_ = maybe_embed_tokens(function(in_statement) {
- var name = is("name") ? prog1(S.token.value, next) : null;
- if (in_statement && !name)
- unexpected();
- expect("(");
- return as(in_statement ? "defun" : "function",
- name,
- // arguments
- (function(first, a){
- while (!is("punc", ")")) {
- if (first) first = false; else expect(",");
- if (!is("name")) unexpected();
- a.push(S.token.value);
- next();
- }
- next();
- return a;
- })(true, []),
- // body
- (function(){
- ++S.in_function;
- var loop = S.in_loop;
- S.in_loop = 0;
- var a = block_();
- --S.in_function;
- S.in_loop = loop;
- return a;
- })());
- });
-
- function if_() {
- var cond = parenthesised(), body = statement(), belse;
- if (is("keyword", "else")) {
- next();
- belse = statement();
- }
- return as("if", cond, body, belse);
- };
-
- function block_() {
- expect("{");
- var a = [];
- while (!is("punc", "}")) {
- if (is("eof")) unexpected();
- a.push(statement());
- }
- next();
- return a;
- };
-
- var switch_block_ = curry(in_loop, function(){
- expect("{");
- var a = [], cur = null;
- while (!is("punc", "}")) {
- if (is("eof")) unexpected();
- if (is("keyword", "case")) {
- next();
- cur = [];
- a.push([ expression(), cur ]);
- expect(":");
- }
- else if (is("keyword", "default")) {
- next();
- expect(":");
- cur = [];
- a.push([ null, cur ]);
- }
- else {
- if (!cur) unexpected();
- cur.push(statement());
- }
- }
- next();
- return a;
- });
-
- function try_() {
- var body = block_(), bcatch, bfinally;
- if (is("keyword", "catch")) {
- next();
- expect("(");
- if (!is("name"))
- croak("Name expected");
- var name = S.token.value;
- next();
- expect(")");
- bcatch = [ name, block_() ];
- }
- if (is("keyword", "finally")) {
- next();
- bfinally = block_();
- }
- if (!bcatch && !bfinally)
- croak("Missing catch/finally blocks");
- return as("try", body, bcatch, bfinally);
- };
-
- function vardefs(no_in) {
- var a = [];
- for (;;) {
- if (!is("name"))
- unexpected();
- var name = S.token.value;
- next();
- if (is("operator", "=")) {
- next();
- a.push([ name, expression(false, no_in) ]);
- } else {
- a.push([ name ]);
- }
- if (!is("punc", ","))
- break;
- next();
- }
- return a;
- };
-
- function var_(no_in) {
- return as("var", vardefs(no_in));
- };
-
- function const_() {
- return as("const", vardefs());
- };
-
- function new_() {
- var newexp = expr_atom(false), args;
- if (is("punc", "(")) {
- next();
- args = expr_list(")");
- } else {
- args = [];
- }
- return subscripts(as("new", newexp, args), true);
- };
-
- var expr_atom = maybe_embed_tokens(function(allow_calls) {
- if (is("operator", "new")) {
- next();
- return new_();
- }
- if (is("punc")) {
- switch (S.token.value) {
- case "(":
- next();
- return subscripts(prog1(expression, curry(expect, ")")), allow_calls);
- case "[":
- next();
- return subscripts(array_(), allow_calls);
- case "{":
- next();
- return subscripts(object_(), allow_calls);
- }
- unexpected();
- }
- if (is("keyword", "function")) {
- next();
- return subscripts(function_(false), allow_calls);
- }
- if (HOP(ATOMIC_START_TOKEN, S.token.type)) {
- var atom = S.token.type == "regexp"
- ? as("regexp", S.token.value[0], S.token.value[1])
- : as(S.token.type, S.token.value);
- return subscripts(prog1(atom, next), allow_calls);
- }
- unexpected();
- });
-
- function expr_list(closing, allow_trailing_comma, allow_empty) {
- var first = true, a = [];
- while (!is("punc", closing)) {
- if (first) first = false; else expect(",");
- if (allow_trailing_comma && is("punc", closing)) break;
- if (is("punc", ",") && allow_empty) {
- a.push([ "atom", "undefined" ]);
- } else {
- a.push(expression(false));
- }
- }
- next();
- return a;
- };
-
- function array_() {
- return as("array", expr_list("]", !exigent_mode, true));
- };
-
- function object_() {
- var first = true, a = [];
- while (!is("punc", "}")) {
- if (first) first = false; else expect(",");
- if (!exigent_mode && is("punc", "}"))
- // allow trailing comma
- break;
- var type = S.token.type;
- var name = as_property_name();
- if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) {
- a.push([ as_name(), function_(false), name ]);
- } else {
- expect(":");
- a.push([ name, expression(false) ]);
- }
- }
- next();
- return as("object", a);
- };
-
- function as_property_name() {
- switch (S.token.type) {
- case "num":
- case "string":
- return prog1(S.token.value, next);
- }
- return as_name();
- };
-
- function as_name() {
- switch (S.token.type) {
- case "name":
- case "operator":
- case "keyword":
- case "atom":
- return prog1(S.token.value, next);
- default:
- unexpected();
- }
- };
-
- function subscripts(expr, allow_calls) {
- if (is("punc", ".")) {
- next();
- return subscripts(as("dot", expr, as_name()), allow_calls);
- }
- if (is("punc", "[")) {
- next();
- return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls);
- }
- if (allow_calls && is("punc", "(")) {
- next();
- return subscripts(as("call", expr, expr_list(")")), true);
- }
- return expr;
- };
-
- function maybe_unary(allow_calls) {
- if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
- return make_unary("unary-prefix",
- prog1(S.token.value, next),
- maybe_unary(allow_calls));
- }
- var val = expr_atom(allow_calls);
- while (is("operator") && HOP(UNARY_POSTFIX, S.token.value) && !S.token.nlb) {
- val = make_unary("unary-postfix", S.token.value, val);
- next();
- }
- return val;
- };
-
- function make_unary(tag, op, expr) {
- if ((op == "++" || op == "--") && !is_assignable(expr))
- croak("Invalid use of " + op + " operator");
- return as(tag, op, expr);
- };
-
- function expr_op(left, min_prec, no_in) {
- var op = is("operator") ? S.token.value : null;
- if (op && op == "in" && no_in) op = null;
- var prec = op != null ? PRECEDENCE[op] : null;
- if (prec != null && prec > min_prec) {
- next();
- var right = expr_op(maybe_unary(true), prec, no_in);
- return expr_op(as("binary", op, left, right), min_prec, no_in);
- }
- return left;
- };
-
- function expr_ops(no_in) {
- return expr_op(maybe_unary(true), 0, no_in);
- };
-
- function maybe_conditional(no_in) {
- var expr = expr_ops(no_in);
- if (is("operator", "?")) {
- next();
- var yes = expression(false);
- expect(":");
- return as("conditional", expr, yes, expression(false, no_in));
- }
- return expr;
- };
-
- function is_assignable(expr) {
- if (!exigent_mode) return true;
- switch (expr[0]) {
- case "dot":
- case "sub":
- case "new":
- case "call":
- return true;
- case "name":
- return expr[1] != "this";
- }
- };
-
- function maybe_assign(no_in) {
- var left = maybe_conditional(no_in), val = S.token.value;
- if (is("operator") && HOP(ASSIGNMENT, val)) {
- if (is_assignable(left)) {
- next();
- return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in));
- }
- croak("Invalid assignment");
- }
- return left;
- };
-
- var expression = maybe_embed_tokens(function(commas, no_in) {
- if (arguments.length == 0)
- commas = true;
- var expr = maybe_assign(no_in);
- if (commas && is("punc", ",")) {
- next();
- return as("seq", expr, expression(true, no_in));
- }
- return expr;
- });
-
- function in_loop(cont) {
- try {
- ++S.in_loop;
- return cont();
- } finally {
- --S.in_loop;
- }
- };
-
- return as("toplevel", (function(a){
- while (!is("eof"))
- a.push(statement());
- return a;
- })([]));
-
-};
-
-/* -----[ Utilities ]----- */
-
-function curry(f) {
- var args = slice(arguments, 1);
- return function() { return f.apply(this, args.concat(slice(arguments))); };
-};
-
-function prog1(ret) {
- if (ret instanceof Function)
- ret = ret();
- for (var i = 1, n = arguments.length; --n > 0; ++i)
- arguments[i]();
- return ret;
-};
-
-function array_to_hash(a) {
- var ret = {};
- for (var i = 0; i < a.length; ++i)
- ret[a[i]] = true;
- return ret;
-};
-
-function slice(a, start) {
- return Array.prototype.slice.call(a, start == null ? 0 : start);
-};
-
-function characters(str) {
- return str.split("");
-};
-
-function member(name, array) {
- for (var i = array.length; --i >= 0;)
- if (array[i] === name)
- return true;
- return false;
-};
-
-function HOP(obj, prop) {
- return Object.prototype.hasOwnProperty.call(obj, prop);
-};
-
-var warn = function() {};
-
-/* -----[ Exports ]----- */
-
-exports.tokenizer = tokenizer;
-exports.parse = parse;
-exports.slice = slice;
-exports.curry = curry;
-exports.member = member;
-exports.array_to_hash = array_to_hash;
-exports.PRECEDENCE = PRECEDENCE;
-exports.KEYWORDS_ATOM = KEYWORDS_ATOM;
-exports.RESERVED_WORDS = RESERVED_WORDS;
-exports.KEYWORDS = KEYWORDS;
-exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN;
-exports.OPERATORS = OPERATORS;
-exports.is_alphanumeric_char = is_alphanumeric_char;
-exports.set_logger = function(logger) {
- warn = logger;
-};
+++ /dev/null
-/***********************************************************************
-
- A JavaScript tokenizer / parser / beautifier / compressor.
-
- This version is suitable for Node.js. With minimal changes (the
- exports stuff) it should work on any JS platform.
-
- This file implements some AST processors. They work on data built
- by parse-js.
-
- Exported functions:
-
- - ast_mangle(ast, options) -- mangles the variable/function names
- in the AST. Returns an AST.
-
- - ast_squeeze(ast) -- employs various optimizations to make the
- final generated code even smaller. Returns an AST.
-
- - gen_code(ast, options) -- generates JS code from the AST. Pass
- true (or an object, see the code for some options) as second
- argument to get "pretty" (indented) code.
-
- -------------------------------- (C) ---------------------------------
-
- Author: Mihai Bazon
- <mihai.bazon@gmail.com>
- http://mihai.bazon.net/blog
-
- Distributed under the BSD license:
-
- Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
-
- * Redistributions of source code must retain the above
- copyright notice, this list of conditions and the following
- disclaimer.
-
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials
- provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- SUCH DAMAGE.
-
- ***********************************************************************/
-
-var jsp = require("./parse-js"),
- slice = jsp.slice,
- member = jsp.member,
- PRECEDENCE = jsp.PRECEDENCE,
- OPERATORS = jsp.OPERATORS;
-
-/* -----[ helper for AST traversal ]----- */
-
-function ast_walker(ast) {
- function _vardefs(defs) {
- return [ this[0], MAP(defs, function(def){
- var a = [ def[0] ];
- if (def.length > 1)
- a[1] = walk(def[1]);
- return a;
- }) ];
- };
- function _block(statements) {
- var out = [ this[0] ];
- if (statements != null)
- out.push(MAP(statements, walk));
- return out;
- };
- var walkers = {
- "string": function(str) {
- return [ this[0], str ];
- },
- "num": function(num) {
- return [ this[0], num ];
- },
- "name": function(name) {
- return [ this[0], name ];
- },
- "toplevel": function(statements) {
- return [ this[0], MAP(statements, walk) ];
- },
- "block": _block,
- "splice": _block,
- "var": _vardefs,
- "const": _vardefs,
- "try": function(t, c, f) {
- return [
- this[0],
- MAP(t, walk),
- c != null ? [ c[0], MAP(c[1], walk) ] : null,
- f != null ? MAP(f, walk) : null
- ];
- },
- "throw": function(expr) {
- return [ this[0], walk(expr) ];
- },
- "new": function(ctor, args) {
- return [ this[0], walk(ctor), MAP(args, walk) ];
- },
- "switch": function(expr, body) {
- return [ this[0], walk(expr), MAP(body, function(branch){
- return [ branch[0] ? walk(branch[0]) : null,
- MAP(branch[1], walk) ];
- }) ];
- },
- "break": function(label) {
- return [ this[0], label ];
- },
- "continue": function(label) {
- return [ this[0], label ];
- },
- "conditional": function(cond, t, e) {
- return [ this[0], walk(cond), walk(t), walk(e) ];
- },
- "assign": function(op, lvalue, rvalue) {
- return [ this[0], op, walk(lvalue), walk(rvalue) ];
- },
- "dot": function(expr) {
- return [ this[0], walk(expr) ].concat(slice(arguments, 1));
- },
- "call": function(expr, args) {
- return [ this[0], walk(expr), MAP(args, walk) ];
- },
- "function": function(name, args, body) {
- return [ this[0], name, args.slice(), MAP(body, walk) ];
- },
- "defun": function(name, args, body) {
- return [ this[0], name, args.slice(), MAP(body, walk) ];
- },
- "if": function(conditional, t, e) {
- return [ this[0], walk(conditional), walk(t), walk(e) ];
- },
- "for": function(init, cond, step, block) {
- return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
- },
- "for-in": function(vvar, key, hash, block) {
- return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ];
- },
- "while": function(cond, block) {
- return [ this[0], walk(cond), walk(block) ];
- },
- "do": function(cond, block) {
- return [ this[0], walk(cond), walk(block) ];
- },
- "return": function(expr) {
- return [ this[0], walk(expr) ];
- },
- "binary": function(op, left, right) {
- return [ this[0], op, walk(left), walk(right) ];
- },
- "unary-prefix": function(op, expr) {
- return [ this[0], op, walk(expr) ];
- },
- "unary-postfix": function(op, expr) {
- return [ this[0], op, walk(expr) ];
- },
- "sub": function(expr, subscript) {
- return [ this[0], walk(expr), walk(subscript) ];
- },
- "object": function(props) {
- return [ this[0], MAP(props, function(p){
- return p.length == 2
- ? [ p[0], walk(p[1]) ]
- : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
- }) ];
- },
- "regexp": function(rx, mods) {
- return [ this[0], rx, mods ];
- },
- "array": function(elements) {
- return [ this[0], MAP(elements, walk) ];
- },
- "stat": function(stat) {
- return [ this[0], walk(stat) ];
- },
- "seq": function() {
- return [ this[0] ].concat(MAP(slice(arguments), walk));
- },
- "label": function(name, block) {
- return [ this[0], name, walk(block) ];
- },
- "with": function(expr, block) {
- return [ this[0], walk(expr), walk(block) ];
- },
- "atom": function(name) {
- return [ this[0], name ];
- }
- };
-
- var user = {};
- var stack = [];
- function walk(ast) {
- if (ast == null)
- return null;
- try {
- stack.push(ast);
- var type = ast[0];
- var gen = user[type];
- if (gen) {
- var ret = gen.apply(ast, ast.slice(1));
- if (ret != null)
- return ret;
- }
- gen = walkers[type];
- return gen.apply(ast, ast.slice(1));
- } finally {
- stack.pop();
- }
- };
-
- function with_walkers(walkers, cont){
- var save = {}, i;
- for (i in walkers) if (HOP(walkers, i)) {
- save[i] = user[i];
- user[i] = walkers[i];
- }
- var ret = cont();
- for (i in save) if (HOP(save, i)) {
- if (!save[i]) delete user[i];
- else user[i] = save[i];
- }
- return ret;
- };
-
- return {
- walk: walk,
- with_walkers: with_walkers,
- parent: function() {
- return stack[stack.length - 2]; // last one is current node
- },
- stack: function() {
- return stack;
- }
- };
-};
-
-/* -----[ Scope and mangling ]----- */
-
-function Scope(parent) {
- this.names = {}; // names defined in this scope
- this.mangled = {}; // mangled names (orig.name => mangled)
- this.rev_mangled = {}; // reverse lookup (mangled => orig.name)
- this.cname = -1; // current mangled name
- this.refs = {}; // names referenced from this scope
- this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes
- this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes
- this.parent = parent; // parent scope
- this.children = []; // sub-scopes
- if (parent) {
- this.level = parent.level + 1;
- parent.children.push(this);
- } else {
- this.level = 0;
- }
-};
-
-var base54 = (function(){
- var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
- return function(num) {
- var ret = "";
- do {
- ret = DIGITS.charAt(num % 54) + ret;
- num = Math.floor(num / 54);
- } while (num > 0);
- return ret;
- };
-})();
-
-Scope.prototype = {
- has: function(name) {
- for (var s = this; s; s = s.parent)
- if (HOP(s.names, name))
- return s;
- },
- has_mangled: function(mname) {
- for (var s = this; s; s = s.parent)
- if (HOP(s.rev_mangled, mname))
- return s;
- },
- toJSON: function() {
- return {
- names: this.names,
- uses_eval: this.uses_eval,
- uses_with: this.uses_with
- };
- },
-
- next_mangled: function() {
- // we must be careful that the new mangled name:
- //
- // 1. doesn't shadow a mangled name from a parent
- // scope, unless we don't reference the original
- // name from this scope OR from any sub-scopes!
- // This will get slow.
- //
- // 2. doesn't shadow an original name from a parent
- // scope, in the event that the name is not mangled
- // in the parent scope and we reference that name
- // here OR IN ANY SUBSCOPES!
- //
- // 3. doesn't shadow a name that is referenced but not
- // defined (possibly global defined elsewhere).
- for (;;) {
- var m = base54(++this.cname), prior;
-
- // case 1.
- prior = this.has_mangled(m);
- if (prior && this.refs[prior.rev_mangled[m]] === prior)
- continue;
-
- // case 2.
- prior = this.has(m);
- if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
- continue;
-
- // case 3.
- if (HOP(this.refs, m) && this.refs[m] == null)
- continue;
-
- // I got "do" once. :-/
- if (!is_identifier(m))
- continue;
-
- return m;
- }
- },
- set_mangle: function(name, m) {
- this.rev_mangled[m] = name;
- return this.mangled[name] = m;
- },
- get_mangled: function(name, newMangle) {
- if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
- var s = this.has(name);
- if (!s) return name; // not in visible scope, no mangle
- if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
- if (!newMangle) return name; // not found and no mangling requested
- return s.set_mangle(name, s.next_mangled());
- },
- define: function(name) {
- if (name != null)
- return this.names[name] = name;
- }
-};
-
-function ast_add_scope(ast) {
-
- var current_scope = null;
- var w = ast_walker(), walk = w.walk;
- var having_eval = [];
-
- function with_new_scope(cont) {
- current_scope = new Scope(current_scope);
- var ret = current_scope.body = cont();
- ret.scope = current_scope;
- current_scope = current_scope.parent;
- return ret;
- };
-
- function define(name) {
- return current_scope.define(name);
- };
-
- function reference(name) {
- current_scope.refs[name] = true;
- };
-
- function _lambda(name, args, body) {
- var is_defun = this[0] == "defun";
- return [ this[0], is_defun ? define(name) : name, args, with_new_scope(function(){
- if (!is_defun) define(name);
- MAP(args, define);
- return MAP(body, walk);
- })];
- };
-
- return with_new_scope(function(){
- // process AST
- var ret = w.with_walkers({
- "function": _lambda,
- "defun": _lambda,
- "with": function(expr, block) {
- for (var s = current_scope; s; s = s.parent)
- s.uses_with = true;
- },
- "var": function(defs) {
- MAP(defs, function(d){ define(d[0]) });
- },
- "const": function(defs) {
- MAP(defs, function(d){ define(d[0]) });
- },
- "try": function(t, c, f) {
- if (c != null) return [
- this[0],
- MAP(t, walk),
- [ define(c[0]), MAP(c[1], walk) ],
- f != null ? MAP(f, walk) : null
- ];
- },
- "name": function(name) {
- if (name == "eval")
- having_eval.push(current_scope);
- reference(name);
- }
- }, function(){
- return walk(ast);
- });
-
- // the reason why we need an additional pass here is
- // that names can be used prior to their definition.
-
- // scopes where eval was detected and their parents
- // are marked with uses_eval, unless they define the
- // "eval" name.
- MAP(having_eval, function(scope){
- if (!scope.has("eval")) while (scope) {
- scope.uses_eval = true;
- scope = scope.parent;
- }
- });
-
- // for referenced names it might be useful to know
- // their origin scope. current_scope here is the
- // toplevel one.
- function fixrefs(scope, i) {
- // do children first; order shouldn't matter
- for (i = scope.children.length; --i >= 0;)
- fixrefs(scope.children[i]);
- for (i in scope.refs) if (HOP(scope.refs, i)) {
- // find origin scope and propagate the reference to origin
- for (var origin = scope.has(i), s = scope; s; s = s.parent) {
- s.refs[i] = origin;
- if (s === origin) break;
- }
- }
- };
- fixrefs(current_scope);
-
- return ret;
- });
-
-};
-
-/* -----[ mangle names ]----- */
-
-function ast_mangle(ast, options) {
- var w = ast_walker(), walk = w.walk, scope;
- options = options || {};
-
- function get_mangled(name, newMangle) {
- if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel
- if (options.except && member(name, options.except))
- return name;
- return scope.get_mangled(name, newMangle);
- };
-
- function get_define(name) {
- if (options.defines) {
- // we always lookup a defined symbol for the current scope FIRST, so declared
- // vars trump a DEFINE symbol, but if no such var is found, then match a DEFINE value
- if (!scope.has(name)) {
- if (HOP(options.defines, name)) {
- return options.defines[name];
- }
- }
- return null;
- }
- };
-
- function _lambda(name, args, body) {
- var is_defun = this[0] == "defun", extra;
- if (name) {
- if (is_defun) name = get_mangled(name);
- else {
- extra = {};
- if (!(scope.uses_eval || scope.uses_with))
- name = extra[name] = scope.next_mangled();
- else
- extra[name] = name;
- }
- }
- body = with_scope(body.scope, function(){
- args = MAP(args, function(name){ return get_mangled(name) });
- return MAP(body, walk);
- }, extra);
- return [ this[0], name, args, body ];
- };
-
- function with_scope(s, cont, extra) {
- var _scope = scope;
- scope = s;
- if (extra) for (var i in extra) if (HOP(extra, i)) {
- s.set_mangle(i, extra[i]);
- }
- for (var i in s.names) if (HOP(s.names, i)) {
- get_mangled(i, true);
- }
- var ret = cont();
- ret.scope = s;
- scope = _scope;
- return ret;
- };
-
- function _vardefs(defs) {
- return [ this[0], MAP(defs, function(d){
- return [ get_mangled(d[0]), walk(d[1]) ];
- }) ];
- };
-
- return w.with_walkers({
- "function": _lambda,
- "defun": function() {
- // move function declarations to the top when
- // they are not in some block.
- var ast = _lambda.apply(this, arguments);
- switch (w.parent()[0]) {
- case "toplevel":
- case "function":
- case "defun":
- return MAP.at_top(ast);
- }
- return ast;
- },
- "var": _vardefs,
- "const": _vardefs,
- "name": function(name) {
- return get_define(name) || [ this[0], get_mangled(name) ];
- },
- "try": function(t, c, f) {
- return [ this[0],
- MAP(t, walk),
- c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
- f != null ? MAP(f, walk) : null ];
- },
- "toplevel": function(body) {
- var self = this;
- return with_scope(self.scope, function(){
- return [ self[0], MAP(body, walk) ];
- });
- }
- }, function() {
- return walk(ast_add_scope(ast));
- });
-};
-
-/* -----[
- - compress foo["bar"] into foo.bar,
- - remove block brackets {} where possible
- - join consecutive var declarations
- - various optimizations for IFs:
- - if (cond) foo(); else bar(); ==> cond?foo():bar();
- - if (cond) foo(); ==> cond&&foo();
- - if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); // also for throw
- - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
- ]----- */
-
-var warn = function(){};
-
-function best_of(ast1, ast2) {
- return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
-};
-
-function last_stat(b) {
- if (b[0] == "block" && b[1] && b[1].length > 0)
- return b[1][b[1].length - 1];
- return b;
-}
-
-function aborts(t) {
- if (t) {
- t = last_stat(t);
- if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
- return true;
- }
-};
-
-function boolean_expr(expr) {
- return ( (expr[0] == "unary-prefix"
- && member(expr[1], [ "!", "delete" ])) ||
-
- (expr[0] == "binary"
- && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
-
- (expr[0] == "binary"
- && member(expr[1], [ "&&", "||" ])
- && boolean_expr(expr[2])
- && boolean_expr(expr[3])) ||
-
- (expr[0] == "conditional"
- && boolean_expr(expr[2])
- && boolean_expr(expr[3])) ||
-
- (expr[0] == "assign"
- && expr[1] === true
- && boolean_expr(expr[3])) ||
-
- (expr[0] == "seq"
- && boolean_expr(expr[expr.length - 1]))
- );
-};
-
-function make_conditional(c, t, e) {
- var make_real_conditional = function() {
- if (c[0] == "unary-prefix" && c[1] == "!") {
- return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
- } else {
- return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ];
- }
- };
- // shortcut the conditional if the expression has a constant value
- return when_constant(c, function(ast, val){
- warn_unreachable(val ? e : t);
- return (val ? t : e);
- }, make_real_conditional);
-};
-
-function empty(b) {
- return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
-};
-
-function is_string(node) {
- return (node[0] == "string" ||
- node[0] == "unary-prefix" && node[1] == "typeof" ||
- node[0] == "binary" && node[1] == "+" &&
- (is_string(node[2]) || is_string(node[3])));
-};
-
-var when_constant = (function(){
-
- var $NOT_CONSTANT = {};
-
- // this can only evaluate constant expressions. If it finds anything
- // not constant, it throws $NOT_CONSTANT.
- function evaluate(expr) {
- switch (expr[0]) {
- case "string":
- case "num":
- return expr[1];
- case "name":
- case "atom":
- switch (expr[1]) {
- case "true": return true;
- case "false": return false;
- }
- break;
- case "unary-prefix":
- switch (expr[1]) {
- case "!": return !evaluate(expr[2]);
- case "typeof": return typeof evaluate(expr[2]);
- case "~": return ~evaluate(expr[2]);
- case "-": return -evaluate(expr[2]);
- case "+": return +evaluate(expr[2]);
- }
- break;
- case "binary":
- var left = expr[2], right = expr[3];
- switch (expr[1]) {
- case "&&" : return evaluate(left) && evaluate(right);
- case "||" : return evaluate(left) || evaluate(right);
- case "|" : return evaluate(left) | evaluate(right);
- case "&" : return evaluate(left) & evaluate(right);
- case "^" : return evaluate(left) ^ evaluate(right);
- case "+" : return evaluate(left) + evaluate(right);
- case "*" : return evaluate(left) * evaluate(right);
- case "/" : return evaluate(left) / evaluate(right);
- case "-" : return evaluate(left) - evaluate(right);
- case "<<" : return evaluate(left) << evaluate(right);
- case ">>" : return evaluate(left) >> evaluate(right);
- case ">>>" : return evaluate(left) >>> evaluate(right);
- case "==" : return evaluate(left) == evaluate(right);
- case "===" : return evaluate(left) === evaluate(right);
- case "!=" : return evaluate(left) != evaluate(right);
- case "!==" : return evaluate(left) !== evaluate(right);
- case "<" : return evaluate(left) < evaluate(right);
- case "<=" : return evaluate(left) <= evaluate(right);
- case ">" : return evaluate(left) > evaluate(right);
- case ">=" : return evaluate(left) >= evaluate(right);
- case "in" : return evaluate(left) in evaluate(right);
- case "instanceof" : return evaluate(left) instanceof evaluate(right);
- }
- }
- throw $NOT_CONSTANT;
- };
-
- return function(expr, yes, no) {
- try {
- var val = evaluate(expr), ast;
- switch (typeof val) {
- case "string": ast = [ "string", val ]; break;
- case "number": ast = [ "num", val ]; break;
- case "boolean": ast = [ "name", String(val) ]; break;
- default: throw new Error("Can't handle constant of type: " + (typeof val));
- }
- return yes.call(expr, ast, val);
- } catch(ex) {
- if (ex === $NOT_CONSTANT) {
- if (expr[0] == "binary"
- && (expr[1] == "===" || expr[1] == "!==")
- && ((is_string(expr[2]) && is_string(expr[3]))
- || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) {
- expr[1] = expr[1].substr(0, 2);
- }
- else if (no && expr[0] == "binary"
- && (expr[1] == "||" || expr[1] == "&&")) {
- // the whole expression is not constant but the lval may be...
- try {
- var lval = evaluate(expr[2]);
- expr = ((expr[1] == "&&" && (lval ? expr[3] : lval)) ||
- (expr[1] == "||" && (lval ? lval : expr[3])) ||
- expr);
- } catch(ex2) {
- // IGNORE... lval is not constant
- }
- }
- return no ? no.call(expr, expr) : null;
- }
- else throw ex;
- }
- };
-
-})();
-
-function warn_unreachable(ast) {
- if (!empty(ast))
- warn("Dropping unreachable code: " + gen_code(ast, true));
-};
-
-function prepare_ifs(ast) {
- var w = ast_walker(), walk = w.walk;
- // In this first pass, we rewrite ifs which abort with no else with an
- // if-else. For example:
- //
- // if (x) {
- // blah();
- // return y;
- // }
- // foobar();
- //
- // is rewritten into:
- //
- // if (x) {
- // blah();
- // return y;
- // } else {
- // foobar();
- // }
- function redo_if(statements) {
- statements = MAP(statements, walk);
-
- for (var i = 0; i < statements.length; ++i) {
- var fi = statements[i];
- if (fi[0] != "if") continue;
-
- if (fi[3] && walk(fi[3])) continue;
-
- var t = walk(fi[2]);
- if (!aborts(t)) continue;
-
- var conditional = walk(fi[1]);
-
- var e_body = statements.slice(i + 1);
- var e;
- if (e_body.length == 1) e = e_body[0];
- else e = [ "block", e_body ];
-
- var ret = statements.slice(0, i).concat([ [
- fi[0], // "if"
- conditional, // conditional
- t, // then
- e // else
- ] ]);
-
- return redo_if(ret);
- }
-
- return statements;
- };
-
- function redo_if_lambda(name, args, body) {
- body = redo_if(body);
- return [ this[0], name, args.slice(), body ];
- };
-
- function redo_if_block(statements) {
- var out = [ this[0] ];
- if (statements != null)
- out.push(redo_if(statements));
- return out;
- };
-
- return w.with_walkers({
- "defun": redo_if_lambda,
- "function": redo_if_lambda,
- "block": redo_if_block,
- "splice": redo_if_block,
- "toplevel": function(statements) {
- return [ this[0], redo_if(statements) ];
- },
- "try": function(t, c, f) {
- return [
- this[0],
- redo_if(t),
- c != null ? [ c[0], redo_if(c[1]) ] : null,
- f != null ? redo_if(f) : null
- ];
- }
- }, function() {
- return walk(ast);
- });
-};
-
-function ast_squeeze(ast, options) {
- options = defaults(options, {
- make_seqs : true,
- dead_code : true,
- keep_comps : true,
- no_warnings : false
- });
-
- var w = ast_walker(), walk = w.walk, scope;
-
- function negate(c) {
- var not_c = [ "unary-prefix", "!", c ];
- switch (c[0]) {
- case "unary-prefix":
- return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
- case "seq":
- c = slice(c);
- c[c.length - 1] = negate(c[c.length - 1]);
- return c;
- case "conditional":
- return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
- case "binary":
- var op = c[1], left = c[2], right = c[3];
- if (!options.keep_comps) switch (op) {
- case "<=" : return [ "binary", ">", left, right ];
- case "<" : return [ "binary", ">=", left, right ];
- case ">=" : return [ "binary", "<", left, right ];
- case ">" : return [ "binary", "<=", left, right ];
- }
- switch (op) {
- case "==" : return [ "binary", "!=", left, right ];
- case "!=" : return [ "binary", "==", left, right ];
- case "===" : return [ "binary", "!==", left, right ];
- case "!==" : return [ "binary", "===", left, right ];
- case "&&" : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
- case "||" : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
- }
- break;
- }
- return not_c;
- };
-
- function with_scope(s, cont) {
- var _scope = scope;
- scope = s;
- var ret = cont();
- ret.scope = s;
- scope = _scope;
- return ret;
- };
-
- function rmblock(block) {
- if (block != null && block[0] == "block" && block[1]) {
- if (block[1].length == 1)
- block = block[1][0];
- else if (block[1].length == 0)
- block = [ "block" ];
- }
- return block;
- };
-
- function _lambda(name, args, body) {
- var is_defun = this[0] == "defun";
- body = with_scope(body.scope, function(){
- var ret = tighten(MAP(body, walk), "lambda");
- if (!is_defun && name && !HOP(scope.refs, name))
- name = null;
- return ret;
- });
- return [ this[0], name, args, body ];
- };
-
- // we get here for blocks that have been already transformed.
- // this function does a few things:
- // 1. discard useless blocks
- // 2. join consecutive var declarations
- // 3. remove obviously dead code
- // 4. transform consecutive statements using the comma operator
- // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
- function tighten(statements, block_type) {
- statements = statements.reduce(function(a, stat){
- if (stat[0] == "block") {
- if (stat[1]) {
- a.push.apply(a, stat[1]);
- }
- } else {
- a.push(stat);
- }
- return a;
- }, []);
-
- statements = (function(a, prev){
- statements.forEach(function(cur){
- if (prev && ((cur[0] == "var" && prev[0] == "var") ||
- (cur[0] == "const" && prev[0] == "const"))) {
- prev[1] = prev[1].concat(cur[1]);
- } else {
- a.push(cur);
- prev = cur;
- }
- });
- return a;
- })([]);
-
- if (options.dead_code) statements = (function(a, has_quit){
- statements.forEach(function(st){
- if (has_quit) {
- if (member(st[0], [ "function", "defun" , "var", "const" ])) {
- a.push(st);
- }
- else if (!options.no_warnings)
- warn_unreachable(st);
- }
- else {
- a.push(st);
- if (member(st[0], [ "return", "throw", "break", "continue" ]))
- has_quit = true;
- }
- });
- return a;
- })([]);
-
- if (options.make_seqs) statements = (function(a, prev) {
- statements.forEach(function(cur){
- if (prev && prev[0] == "stat" && cur[0] == "stat") {
- prev[1] = [ "seq", prev[1], cur[1] ];
- } else {
- a.push(cur);
- prev = cur;
- }
- });
- return a;
- })([]);
-
- if (block_type == "lambda") statements = (function(i, a, stat){
- while (i < statements.length) {
- stat = statements[i++];
- if (stat[0] == "if" && !stat[3]) {
- if (stat[2][0] == "return" && stat[2][1] == null) {
- a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
- break;
- }
- var last = last_stat(stat[2]);
- if (last[0] == "return" && last[1] == null) {
- a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
- break;
- }
- }
- a.push(stat);
- }
- return a;
- })(0, []);
-
- return statements;
- };
-
- function make_if(c, t, e) {
- return when_constant(c, function(ast, val){
- if (val) {
- warn_unreachable(e);
- return t;
- } else {
- warn_unreachable(t);
- return e;
- }
- }, function() {
- return make_real_if(c, t, e);
- });
- };
-
- function make_real_if(c, t, e) {
- c = walk(c);
- t = walk(t);
- e = walk(e);
-
- if (empty(t)) {
- c = negate(c);
- t = e;
- e = null;
- } else if (empty(e)) {
- e = null;
- } else {
- // if we have both else and then, maybe it makes sense to switch them?
- (function(){
- var a = gen_code(c);
- var n = negate(c);
- var b = gen_code(n);
- if (b.length < a.length) {
- var tmp = t;
- t = e;
- e = tmp;
- c = n;
- }
- })();
- }
- if (empty(e) && empty(t))
- return [ "stat", c ];
- var ret = [ "if", c, t, e ];
- if (t[0] == "if" && empty(t[3]) && empty(e)) {
- ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ]));
- }
- else if (t[0] == "stat") {
- if (e) {
- if (e[0] == "stat") {
- ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
- }
- }
- else {
- ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
- }
- }
- else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) {
- ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]);
- }
- else if (e && aborts(t)) {
- ret = [ [ "if", c, t ] ];
- if (e[0] == "block") {
- if (e[1]) ret = ret.concat(e[1]);
- }
- else {
- ret.push(e);
- }
- ret = walk([ "block", ret ]);
- }
- else if (t && aborts(e)) {
- ret = [ [ "if", negate(c), e ] ];
- if (t[0] == "block") {
- if (t[1]) ret = ret.concat(t[1]);
- } else {
- ret.push(t);
- }
- ret = walk([ "block", ret ]);
- }
- return ret;
- };
-
- function _do_while(cond, body) {
- return when_constant(cond, function(cond, val){
- if (!val) {
- warn_unreachable(body);
- return [ "block" ];
- } else {
- return [ "for", null, null, null, walk(body) ];
- }
- });
- };
-
- ast = prepare_ifs(ast);
- ast = ast_add_scope(ast);
-
- return w.with_walkers({
- "sub": function(expr, subscript) {
- if (subscript[0] == "string") {
- var name = subscript[1];
- if (is_identifier(name))
- return [ "dot", walk(expr), name ];
- else if (/^[1-9][0-9]*$/.test(name) || name === "0")
- return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ];
- }
- },
- "if": make_if,
- "toplevel": function(body) {
- return [ "toplevel", with_scope(this.scope, function(){
- return tighten(MAP(body, walk));
- }) ];
- },
- "switch": function(expr, body) {
- var last = body.length - 1;
- return [ "switch", walk(expr), MAP(body, function(branch, i){
- var block = tighten(MAP(branch[1], walk));
- if (i == last && block.length > 0) {
- var node = block[block.length - 1];
- if (node[0] == "break" && !node[1])
- block.pop();
- }
- return [ branch[0] ? walk(branch[0]) : null, block ];
- }) ];
- },
- "function": _lambda,
- "defun": _lambda,
- "block": function(body) {
- if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]);
- },
- "binary": function(op, left, right) {
- return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
- return best_of(walk(c), this);
- }, function no() {
- return this;
- });
- },
- "conditional": function(c, t, e) {
- return make_conditional(walk(c), walk(t), walk(e));
- },
- "try": function(t, c, f) {
- return [
- "try",
- tighten(MAP(t, walk)),
- c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null,
- f != null ? tighten(MAP(f, walk)) : null
- ];
- },
- "unary-prefix": function(op, expr) {
- expr = walk(expr);
- var ret = [ "unary-prefix", op, expr ];
- if (op == "!")
- ret = best_of(ret, negate(expr));
- return when_constant(ret, function(ast, val){
- return walk(ast); // it's either true or false, so minifies to !0 or !1
- }, function() { return ret });
- },
- "name": function(name) {
- switch (name) {
- case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
- case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
- }
- },
- "new": function(ctor, args) {
- if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) {
- if (args.length != 1) {
- return [ "array", args ];
- } else {
- return [ "call", [ "name", "Array" ], args ];
- }
- }
- },
- "call": function(expr, args) {
- if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
- return [ "array", args ];
- }
- },
- "while": _do_while
- }, function() {
- return walk(ast);
- });
-};
-
-/* -----[ re-generate code from the AST ]----- */
-
-var DOT_CALL_NO_PARENS = jsp.array_to_hash([
- "name",
- "array",
- "object",
- "string",
- "dot",
- "sub",
- "call",
- "regexp"
-]);
-
-function make_string(str, ascii_only) {
- var dq = 0, sq = 0;
- str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029]/g, function(s){
- switch (s) {
- case "\\": return "\\\\";
- case "\b": return "\\b";
- case "\f": return "\\f";
- case "\n": return "\\n";
- case "\r": return "\\r";
- case "\t": return "\\t";
- case "\u2028": return "\\u2028";
- case "\u2029": return "\\u2029";
- case '"': ++dq; return '"';
- case "'": ++sq; return "'";
- }
- return s;
- });
- if (ascii_only) str = to_ascii(str);
- if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
- else return '"' + str.replace(/\x22/g, '\\"') + '"';
-};
-
-function to_ascii(str) {
- return str.replace(/[\u0080-\uffff]/g, function(ch) {
- var code = ch.charCodeAt(0).toString(16);
- while (code.length < 4) code = "0" + code;
- return "\\u" + code;
- });
-};
-
-var SPLICE_NEEDS_BRACKETS = jsp.array_to_hash([ "if", "while", "do", "for", "for-in", "with" ]);
-
-function gen_code(ast, options) {
- options = defaults(options, {
- indent_start : 0,
- indent_level : 4,
- quote_keys : false,
- space_colon : false,
- beautify : false,
- ascii_only : false,
- inline_script: false
- });
- var beautify = !!options.beautify;
- var indentation = 0,
- newline = beautify ? "\n" : "",
- space = beautify ? " " : "";
-
- function encode_string(str) {
- var ret = make_string(str, options.ascii_only);
- if (options.inline_script)
- ret = ret.replace(/<\x2fscript([>/\t\n\f\r ])/gi, "<\\/script$1");
- return ret;
- };
-
- function make_name(name) {
- name = name.toString();
- if (options.ascii_only)
- name = to_ascii(name);
- return name;
- };
-
- function indent(line) {
- if (line == null)
- line = "";
- if (beautify)
- line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line;
- return line;
- };
-
- function with_indent(cont, incr) {
- if (incr == null) incr = 1;
- indentation += incr;
- try { return cont.apply(null, slice(arguments, 1)); }
- finally { indentation -= incr; }
- };
-
- function add_spaces(a) {
- if (beautify)
- return a.join(" ");
- var b = [];
- for (var i = 0; i < a.length; ++i) {
- var next = a[i + 1];
- b.push(a[i]);
- if (next &&
- ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) ||
- (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) {
- b.push(" ");
- }
- }
- return b.join("");
- };
-
- function add_commas(a) {
- return a.join("," + space);
- };
-
- function parenthesize(expr) {
- var gen = make(expr);
- for (var i = 1; i < arguments.length; ++i) {
- var el = arguments[i];
- if ((el instanceof Function && el(expr)) || expr[0] == el)
- return "(" + gen + ")";
- }
- return gen;
- };
-
- function best_of(a) {
- if (a.length == 1) {
- return a[0];
- }
- if (a.length == 2) {
- var b = a[1];
- a = a[0];
- return a.length <= b.length ? a : b;
- }
- return best_of([ a[0], best_of(a.slice(1)) ]);
- };
-
- function needs_parens(expr) {
- if (expr[0] == "function" || expr[0] == "object") {
- // dot/call on a literal function requires the
- // function literal itself to be parenthesized
- // only if it's the first "thing" in a
- // statement. This means that the parent is
- // "stat", but it could also be a "seq" and
- // we're the first in this "seq" and the
- // parent is "stat", and so on. Messy stuff,
- // but it worths the trouble.
- var a = slice($stack), self = a.pop(), p = a.pop();
- while (p) {
- if (p[0] == "stat") return true;
- if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
- ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) {
- self = p;
- p = a.pop();
- } else {
- return false;
- }
- }
- }
- return !HOP(DOT_CALL_NO_PARENS, expr[0]);
- };
-
- function make_num(num) {
- var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m;
- if (Math.floor(num) === num) {
- a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
- "0" + num.toString(8)); // same.
- if ((m = /^(.*?)(0+)$/.exec(num))) {
- a.push(m[1] + "e" + m[2].length);
- }
- } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
- a.push(m[2] + "e-" + (m[1].length + m[2].length),
- str.substr(str.indexOf(".")));
- }
- return best_of(a);
- };
-
- var generators = {
- "string": encode_string,
- "num": make_num,
- "name": make_name,
- "toplevel": function(statements) {
- return make_block_statements(statements)
- .join(newline + newline);
- },
- "splice": function(statements) {
- var parent = $stack[$stack.length - 2][0];
- if (HOP(SPLICE_NEEDS_BRACKETS, parent)) {
- // we need block brackets in this case
- return make_block.apply(this, arguments);
- } else {
- return MAP(make_block_statements(statements, true),
- function(line, i) {
- // the first line is already indented
- return i > 0 ? indent(line) : line;
- }).join(newline);
- }
- },
- "block": make_block,
- "var": function(defs) {
- return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
- },
- "const": function(defs) {
- return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
- },
- "try": function(tr, ca, fi) {
- var out = [ "try", make_block(tr) ];
- if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1]));
- if (fi) out.push("finally", make_block(fi));
- return add_spaces(out);
- },
- "throw": function(expr) {
- return add_spaces([ "throw", make(expr) ]) + ";";
- },
- "new": function(ctor, args) {
- args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : "";
- return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
- var w = ast_walker(), has_call = {};
- try {
- w.with_walkers({
- "call": function() { throw has_call },
- "function": function() { return this }
- }, function(){
- w.walk(expr);
- });
- } catch(ex) {
- if (ex === has_call)
- return true;
- throw ex;
- }
- }) + args ]);
- },
- "switch": function(expr, body) {
- return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
- },
- "break": function(label) {
- var out = "break";
- if (label != null)
- out += " " + make_name(label);
- return out + ";";
- },
- "continue": function(label) {
- var out = "continue";
- if (label != null)
- out += " " + make_name(label);
- return out + ";";
- },
- "conditional": function(co, th, el) {
- return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
- parenthesize(th, "seq"), ":",
- parenthesize(el, "seq") ]);
- },
- "assign": function(op, lvalue, rvalue) {
- if (op && op !== true) op += "=";
- else op = "=";
- return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
- },
- "dot": function(expr) {
- var out = make(expr), i = 1;
- if (expr[0] == "num") {
- if (!/\./.test(expr[1]))
- out += ".";
- } else if (needs_parens(expr))
- out = "(" + out + ")";
- while (i < arguments.length)
- out += "." + make_name(arguments[i++]);
- return out;
- },
- "call": function(func, args) {
- var f = make(func);
- if (needs_parens(func))
- f = "(" + f + ")";
- return f + "(" + add_commas(MAP(args, function(expr){
- return parenthesize(expr, "seq");
- })) + ")";
- },
- "function": make_function,
- "defun": make_function,
- "if": function(co, th, el) {
- var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ];
- if (el) {
- out.push("else", make(el));
- }
- return add_spaces(out);
- },
- "for": function(init, cond, step, block) {
- var out = [ "for" ];
- init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space);
- cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space);
- step = (step != null ? make(step) : "").replace(/;*\s*$/, "");
- var args = init + cond + step;
- if (args == "; ; ") args = ";;";
- out.push("(" + args + ")", make(block));
- return add_spaces(out);
- },
- "for-in": function(vvar, key, hash, block) {
- return add_spaces([ "for", "(" +
- (vvar ? make(vvar).replace(/;+$/, "") : make(key)),
- "in",
- make(hash) + ")", make(block) ]);
- },
- "while": function(condition, block) {
- return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
- },
- "do": function(condition, block) {
- return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
- },
- "return": function(expr) {
- var out = [ "return" ];
- if (expr != null) out.push(make(expr));
- return add_spaces(out) + ";";
- },
- "binary": function(operator, lvalue, rvalue) {
- var left = make(lvalue), right = make(rvalue);
- // XXX: I'm pretty sure other cases will bite here.
- // we need to be smarter.
- // adding parens all the time is the safest bet.
- if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
- lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) {
- left = "(" + left + ")";
- }
- if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
- rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
- !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
- right = "(" + right + ")";
- }
- else if (!beautify && options.inline_script && (operator == "<" || operator == "<<")
- && rvalue[0] == "regexp" && /^script/i.test(rvalue[1])) {
- right = " " + right;
- }
- return add_spaces([ left, operator, right ]);
- },
- "unary-prefix": function(operator, expr) {
- var val = make(expr);
- if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
- val = "(" + val + ")";
- return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val;
- },
- "unary-postfix": function(operator, expr) {
- var val = make(expr);
- if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
- val = "(" + val + ")";
- return val + operator;
- },
- "sub": function(expr, subscript) {
- var hash = make(expr);
- if (needs_parens(expr))
- hash = "(" + hash + ")";
- return hash + "[" + make(subscript) + "]";
- },
- "object": function(props) {
- if (props.length == 0)
- return "{}";
- return "{" + newline + with_indent(function(){
- return MAP(props, function(p){
- if (p.length == 3) {
- // getter/setter. The name is in p[0], the arg.list in p[1][2], the
- // body in p[1][3] and type ("get" / "set") in p[2].
- return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
- }
- var key = p[0], val = make(p[1]);
- if (options.quote_keys) {
- key = encode_string(key);
- } else if ((typeof key == "number" || !beautify && +key + "" == key)
- && parseFloat(key) >= 0) {
- key = make_num(+key);
- } else if (!is_identifier(key)) {
- key = encode_string(key);
- }
- return indent(add_spaces(beautify && options.space_colon
- ? [ key, ":", val ]
- : [ key + ":", val ]));
- }).join("," + newline);
- }) + newline + indent("}");
- },
- "regexp": function(rx, mods) {
- return "/" + rx + "/" + mods;
- },
- "array": function(elements) {
- if (elements.length == 0) return "[]";
- return add_spaces([ "[", add_commas(MAP(elements, function(el){
- if (!beautify && el[0] == "atom" && el[1] == "undefined") return "";
- return parenthesize(el, "seq");
- })), "]" ]);
- },
- "stat": function(stmt) {
- return make(stmt).replace(/;*\s*$/, ";");
- },
- "seq": function() {
- return add_commas(MAP(slice(arguments), make));
- },
- "label": function(name, block) {
- return add_spaces([ make_name(name), ":", make(block) ]);
- },
- "with": function(expr, block) {
- return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
- },
- "atom": function(name) {
- return make_name(name);
- }
- };
-
- // The squeezer replaces "block"-s that contain only a single
- // statement with the statement itself; technically, the AST
- // is correct, but this can create problems when we output an
- // IF having an ELSE clause where the THEN clause ends in an
- // IF *without* an ELSE block (then the outer ELSE would refer
- // to the inner IF). This function checks for this case and
- // adds the block brackets if needed.
- function make_then(th) {
- if (th[0] == "do") {
- // https://github.com/mishoo/UglifyJS/issues/#issue/57
- // IE croaks with "syntax error" on code like this:
- // if (foo) do ... while(cond); else ...
- // we need block brackets around do/while
- return make([ "block", [ th ]]);
- }
- var b = th;
- while (true) {
- var type = b[0];
- if (type == "if") {
- if (!b[3])
- // no else, we must add the block
- return make([ "block", [ th ]]);
- b = b[3];
- }
- else if (type == "while" || type == "do") b = b[2];
- else if (type == "for" || type == "for-in") b = b[4];
- else break;
- }
- return make(th);
- };
-
- function make_function(name, args, body, keyword) {
- var out = keyword || "function";
- if (name) {
- out += " " + make_name(name);
- }
- out += "(" + add_commas(MAP(args, make_name)) + ")";
- return add_spaces([ out, make_block(body) ]);
- };
-
- function make_block_statements(statements, noindent) {
- for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
- var stat = statements[i];
- var code = make(stat);
- if (code != ";") {
- if (!beautify && i == last) {
- if ((stat[0] == "while" && empty(stat[2])) ||
- (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) ||
- (stat[0] == "if" && empty(stat[2]) && !stat[3]) ||
- (stat[0] == "if" && stat[3] && empty(stat[3]))) {
- code = code.replace(/;*\s*$/, ";");
- } else {
- code = code.replace(/;+\s*$/, "");
- }
- }
- a.push(code);
- }
- }
- return noindent ? a : MAP(a, indent);
- };
-
- function make_switch_block(body) {
- var n = body.length;
- if (n == 0) return "{}";
- return "{" + newline + MAP(body, function(branch, i){
- var has_body = branch[1].length > 0, code = with_indent(function(){
- return indent(branch[0]
- ? add_spaces([ "case", make(branch[0]) + ":" ])
- : "default:");
- }, 0.5) + (has_body ? newline + with_indent(function(){
- return make_block_statements(branch[1]).join(newline);
- }) : "");
- if (!beautify && has_body && i < n - 1)
- code += ";";
- return code;
- }).join(newline) + newline + indent("}");
- };
-
- function make_block(statements) {
- if (!statements) return ";";
- if (statements.length == 0) return "{}";
- return "{" + newline + with_indent(function(){
- return make_block_statements(statements).join(newline);
- }) + newline + indent("}");
- };
-
- function make_1vardef(def) {
- var name = def[0], val = def[1];
- if (val != null)
- name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
- return name;
- };
-
- var $stack = [];
-
- function make(node) {
- var type = node[0];
- var gen = generators[type];
- if (!gen)
- throw new Error("Can't find generator for \"" + type + "\"");
- $stack.push(node);
- var ret = gen.apply(type, node.slice(1));
- $stack.pop();
- return ret;
- };
-
- return make(ast);
-};
-
-function split_lines(code, max_line_length) {
- var splits = [ 0 ];
- jsp.parse(function(){
- var next_token = jsp.tokenizer(code);
- var last_split = 0;
- var prev_token;
- function current_length(tok) {
- return tok.pos - last_split;
- };
- function split_here(tok) {
- last_split = tok.pos;
- splits.push(last_split);
- };
- function custom(){
- var tok = next_token.apply(this, arguments);
- out: {
- if (prev_token) {
- if (prev_token.type == "keyword") break out;
- }
- if (current_length(tok) > max_line_length) {
- switch (tok.type) {
- case "keyword":
- case "atom":
- case "name":
- case "punc":
- split_here(tok);
- break out;
- }
- }
- }
- prev_token = tok;
- return tok;
- };
- custom.context = function() {
- return next_token.context.apply(this, arguments);
- };
- return custom;
- }());
- return splits.map(function(pos, i){
- return code.substring(pos, splits[i + 1] || code.length);
- }).join("\n");
-};
-
-/* -----[ Utilities ]----- */
-
-function repeat_string(str, i) {
- if (i <= 0) return "";
- if (i == 1) return str;
- var d = repeat_string(str, i >> 1);
- d += d;
- if (i & 1) d += str;
- return d;
-};
-
-function defaults(args, defs) {
- var ret = {};
- if (args === true)
- args = {};
- for (var i in defs) if (HOP(defs, i)) {
- ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
- }
- return ret;
-};
-
-function is_identifier(name) {
- return /^[a-z_$][a-z0-9_$]*$/i.test(name)
- && name != "this"
- && !HOP(jsp.KEYWORDS_ATOM, name)
- && !HOP(jsp.RESERVED_WORDS, name)
- && !HOP(jsp.KEYWORDS, name);
-};
-
-function HOP(obj, prop) {
- return Object.prototype.hasOwnProperty.call(obj, prop);
-};
-
-// some utilities
-
-var MAP;
-
-(function(){
- MAP = function(a, f, o) {
- var ret = [];
- for (var i = 0; i < a.length; ++i) {
- var val = f.call(o, a[i], i);
- if (val instanceof AtTop) ret.unshift(val.v);
- else ret.push(val);
- }
- return ret;
- };
- MAP.at_top = function(val) { return new AtTop(val) };
- function AtTop(val) { this.v = val };
-})();
-
-/* -----[ Exports ]----- */
-
-exports.ast_walker = ast_walker;
-exports.ast_mangle = ast_mangle;
-exports.ast_squeeze = ast_squeeze;
-exports.gen_code = gen_code;
-exports.ast_add_scope = ast_add_scope;
-exports.set_logger = function(logger) { warn = logger };
-exports.make_string = make_string;
-exports.split_lines = split_lines;
-exports.MAP = MAP;
-
-// keep this last!
-exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;
+++ /dev/null
-var jsp = require("./parse-js"),
- pro = require("./process"),
- slice = jsp.slice,
- member = jsp.member,
- PRECEDENCE = jsp.PRECEDENCE,
- OPERATORS = jsp.OPERATORS;
-
-function ast_squeeze_more(ast) {
- var w = pro.ast_walker(), walk = w.walk;
- return w.with_walkers({
- "call": function(expr, args) {
- if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
- // foo.toString() ==> foo+""
- return [ "binary", "+", expr[1], [ "string", "" ]];
- }
- }
- }, function() {
- return walk(ast);
- });
-};
-
-exports.ast_squeeze_more = ast_squeeze_more;
+++ /dev/null
-{
- "name" : "uglify-js",
-
- "author" : {
- "name" : "Mihai Bazon",
- "email" : "mihai.bazon@gmail.com",
- "url" : "http://mihai.bazon.net/blog"
- },
-
- "version" : "1.0.6",
-
- "main" : "./uglify-js.js",
-
- "bin" : {
- "uglifyjs" : "./bin/uglifyjs"
- },
-
- "repository": {
- "type": "git",
- "url": "git@github.com:mishoo/UglifyJS.git"
- }
-}
+++ /dev/null
-#! /usr/bin/env node
-
-global.sys = require("sys");
-var fs = require("fs");
-
-var jsp = require("../lib/parse-js");
-var pro = require("../lib/process");
-
-var filename = process.argv[2];
-fs.readFile(filename, "utf8", function(err, text){
- try {
- var ast = time_it("parse", function(){ return jsp.parse(text); });
- ast = time_it("mangle", function(){ return pro.ast_mangle(ast); });
- ast = time_it("squeeze", function(){ return pro.ast_squeeze(ast); });
- var gen = time_it("generate", function(){ return pro.gen_code(ast, false); });
- sys.puts(gen);
- } catch(ex) {
- sys.debug(ex.stack);
- sys.debug(sys.inspect(ex));
- sys.debug(JSON.stringify(ex));
- }
-});
-
-function time_it(name, cont) {
- var t1 = new Date().getTime();
- try { return cont(); }
- finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
-};
+++ /dev/null
-#! /usr/bin/env node
-
-var parseJS = require("../lib/parse-js");
-var sys = require("sys");
-
-// write debug in a very straightforward manner
-var debug = function(){
- sys.log(Array.prototype.slice.call(arguments).join(', '));
-};
-
-ParserTestSuite(function(i, input, desc){
- try {
- parseJS.parse(input);
- debug("ok " + i + ": " + desc);
- } catch(e){
- debug("FAIL " + i + " " + desc + " (" + e + ")");
- }
-});
-
-function ParserTestSuite(callback){
- var inps = [
- ["var abc;", "Regular variable statement w/o assignment"],
- ["var abc = 5;", "Regular variable statement with assignment"],
- ["/* */;", "Multiline comment"],
- ['/** **/;', 'Double star multiline comment'],
- ["var f = function(){;};", "Function expression in var assignment"],
- ['hi; // moo\n;', 'single line comment'],
- ['var varwithfunction;', 'Dont match keywords as substrings'], // difference between `var withsomevar` and `"str"` (local search and lits)
- ['a + b;', 'addition'],
- ["'a';", 'single string literal'],
- ["'a\\n';", 'single string literal with escaped return'],
- ['"a";', 'double string literal'],
- ['"a\\n";', 'double string literal with escaped return'],
- ['"var";', 'string is a keyword'],
- ['"variable";', 'string starts with a keyword'],
- ['"somevariable";', 'string contains a keyword'],
- ['"somevar";', 'string ends with a keyword'],
- ['500;', 'int literal'],
- ['500.;', 'float literal w/o decimals'],
- ['500.432;', 'float literal with decimals'],
- ['.432432;', 'float literal w/o int'],
- ['(a,b,c);', 'parens and comma'],
- ['[1,2,abc];', 'array literal'],
- ['var o = {a:1};', 'object literal unquoted key'],
- ['var o = {"b":2};', 'object literal quoted key'], // opening curly may not be at the start of a statement...
- ['var o = {c:c};', 'object literal keyname is identifier'],
- ['var o = {a:1,"b":2,c:c};', 'object literal combinations'],
- ['var x;\nvar y;', 'two lines'],
- ['var x;\nfunction n(){; }', 'function def'],
- ['var x;\nfunction n(abc){; }', 'function def with arg'],
- ['var x;\nfunction n(abc, def){ ;}', 'function def with args'],
- ['function n(){ "hello"; }', 'function def with body'],
- ['/a/;', 'regex literal'],
- ['/a/b;', 'regex literal with flag'],
- ['/a/ / /b/;', 'regex div regex'],
- ['a/b/c;', 'triple division looks like regex'],
- ['+function(){/regex/;};', 'regex at start of function body'],
- // http://code.google.com/p/es-lab/source/browse/trunk/tests/parser/parsertests.js?r=86
- // http://code.google.com/p/es-lab/source/browse/trunk/tests/parser/parsertests.js?r=430
-
- // first tests for the lexer, should also parse as program (when you append a semi)
-
- // comments
- ['//foo!@#^&$1234\nbar;', 'single line comment'],
- ['/* abcd!@#@$* { } && null*/;', 'single line multi line comment'],
- ['/*foo\nbar*/;','multi line comment'],
- ['/*x*x*/;','multi line comment with *'],
- ['/**/;','empty comment'],
- // identifiers
- ["x;",'1 identifier'],
- ["_x;",'2 identifier'],
- ["xyz;",'3 identifier'],
- ["$x;",'4 identifier'],
- ["x$;",'5 identifier'],
- ["_;",'6 identifier'],
- ["x5;",'7 identifier'],
- ["x_y;",'8 identifier'],
- ["x+5;",'9 identifier'],
- ["xyz123;",'10 identifier'],
- ["x1y1z1;",'11 identifier'],
- ["foo\\u00D8bar;",'12 identifier unicode escape'],
- //["foo�bar;",'13 identifier unicode embedded (might fail)'],
- // numbers
- ["5;", '1 number'],
- ["5.5;", '2 number'],
- ["0;", '3 number'],
- ["0.0;", '4 number'],
- ["0.001;", '5 number'],
- ["1.e2;", '6 number'],
- ["1.e-2;", '7 number'],
- ["1.E2;", '8 number'],
- ["1.E-2;", '9 number'],
- [".5;", '10 number'],
- [".5e3;", '11 number'],
- [".5e-3;", '12 number'],
- ["0.5e3;", '13 number'],
- ["55;", '14 number'],
- ["123;", '15 number'],
- ["55.55;", '16 number'],
- ["55.55e10;", '17 number'],
- ["123.456;", '18 number'],
- ["1+e;", '20 number'],
- ["0x01;", '22 number'],
- ["0XCAFE;", '23 number'],
- ["0x12345678;", '24 number'],
- ["0x1234ABCD;", '25 number'],
- ["0x0001;", '26 number'],
- // strings
- ["\"foo\";", '1 string'],
- ["\'foo\';", '2 string'],
- ["\"x\";", '3 string'],
- ["\'\';", '4 string'],
- ["\"foo\\tbar\";", '5 string'],
- ["\"!@#$%^&*()_+{}[]\";", '6 string'],
- ["\"/*test*/\";", '7 string'],
- ["\"//test\";", '8 string'],
- ["\"\\\\\";", '9 string'],
- ["\"\\u0001\";", '10 string'],
- ["\"\\uFEFF\";", '11 string'],
- ["\"\\u10002\";", '12 string'],
- ["\"\\x55\";", '13 string'],
- ["\"\\x55a\";", '14 string'],
- ["\"a\\\\nb\";", '15 string'],
- ['";"', '16 string: semi in a string'],
- ['"a\\\nb";', '17 string: line terminator escape'],
- // literals
- ["null;", "null"],
- ["true;", "true"],
- ["false;", "false"],
- // regex
- ["/a/;", "1 regex"],
- ["/abc/;", "2 regex"],
- ["/abc[a-z]*def/g;", "3 regex"],
- ["/\\b/;", "4 regex"],
- ["/[a-zA-Z]/;", "5 regex"],
-
- // program tests (for as far as they havent been covered above)
-
- // regexp
- ["/foo(.*)/g;", "another regexp"],
- // arrays
- ["[];", "1 array"],
- ["[ ];", "2 array"],
- ["[1];", "3 array"],
- ["[1,2];", "4 array"],
- ["[1,2,,];", "5 array"],
- ["[1,2,3];", "6 array"],
- ["[1,2,3,,,];", "7 array"],
- // objects
- ["{};", "1 object"],
- ["({x:5});", "2 object"],
- ["({x:5,y:6});", "3 object"],
- ["({x:5,});", "4 object"],
- ["({if:5});", "5 object"],
- ["({ get x() {42;} });", "6 object"],
- ["({ set y(a) {1;} });", "7 object"],
- // member expression
- ["o.m;", "1 member expression"],
- ["o['m'];", "2 member expression"],
- ["o['n']['m'];", "3 member expression"],
- ["o.n.m;", "4 member expression"],
- ["o.if;", "5 member expression"],
- // call and invoke expressions
- ["f();", "1 call/invoke expression"],
- ["f(x);", "2 call/invoke expression"],
- ["f(x,y);", "3 call/invoke expression"],
- ["o.m();", "4 call/invoke expression"],
- ["o['m'];", "5 call/invoke expression"],
- ["o.m(x);", "6 call/invoke expression"],
- ["o['m'](x);", "7 call/invoke expression"],
- ["o.m(x,y);", "8 call/invoke expression"],
- ["o['m'](x,y);", "9 call/invoke expression"],
- ["f(x)(y);", "10 call/invoke expression"],
- ["f().x;", "11 call/invoke expression"],
-
- // eval
- ["eval('x');", "1 eval"],
- ["(eval)('x');", "2 eval"],
- ["(1,eval)('x');", "3 eval"],
- ["eval(x,y);", "4 eval"],
- // new expression
- ["new f();", "1 new expression"],
- ["new o;", "2 new expression"],
- ["new o.m;", "3 new expression"],
- ["new o.m(x);", "4 new expression"],
- ["new o.m(x,y);", "5 new expression"],
- // prefix/postfix
- ["++x;", "1 pre/postfix"],
- ["x++;", "2 pre/postfix"],
- ["--x;", "3 pre/postfix"],
- ["x--;", "4 pre/postfix"],
- ["x ++;", "5 pre/postfix"],
- ["x /* comment */ ++;", "6 pre/postfix"],
- ["++ /* comment */ x;", "7 pre/postfix"],
- // unary operators
- ["delete x;", "1 unary operator"],
- ["void x;", "2 unary operator"],
- ["+ x;", "3 unary operator"],
- ["-x;", "4 unary operator"],
- ["~x;", "5 unary operator"],
- ["!x;", "6 unary operator"],
- // meh
- ["new Date++;", "new date ++"],
- ["+x++;", " + x ++"],
- // expression expressions
- ["1 * 2;", "1 expression expressions"],
- ["1 / 2;", "2 expression expressions"],
- ["1 % 2;", "3 expression expressions"],
- ["1 + 2;", "4 expression expressions"],
- ["1 - 2;", "5 expression expressions"],
- ["1 << 2;", "6 expression expressions"],
- ["1 >>> 2;", "7 expression expressions"],
- ["1 >> 2;", "8 expression expressions"],
- ["1 * 2 + 3;", "9 expression expressions"],
- ["(1+2)*3;", "10 expression expressions"],
- ["1*(2+3);", "11 expression expressions"],
- ["x<y;", "12 expression expressions"],
- ["x>y;", "13 expression expressions"],
- ["x<=y;", "14 expression expressions"],
- ["x>=y;", "15 expression expressions"],
- ["x instanceof y;", "16 expression expressions"],
- ["x in y;", "17 expression expressions"],
- ["x&y;", "18 expression expressions"],
- ["x^y;", "19 expression expressions"],
- ["x|y;", "20 expression expressions"],
- ["x+y<z;", "21 expression expressions"],
- ["x<y+z;", "22 expression expressions"],
- ["x+y+z;", "23 expression expressions"],
- ["x+y<z;", "24 expression expressions"],
- ["x<y+z;", "25 expression expressions"],
- ["x&y|z;", "26 expression expressions"],
- ["x&&y;", "27 expression expressions"],
- ["x||y;", "28 expression expressions"],
- ["x&&y||z;", "29 expression expressions"],
- ["x||y&&z;", "30 expression expressions"],
- ["x<y?z:w;", "31 expression expressions"],
- // assignment
- ["x >>>= y;", "1 assignment"],
- ["x <<= y;", "2 assignment"],
- ["x = y;", "3 assignment"],
- ["x += y;", "4 assignment"],
- ["x /= y;", "5 assignment"],
- // comma
- ["x, y;", "comma"],
- // block
- ["{};", "1 block"],
- ["{x;};", "2 block"],
- ["{x;y;};", "3 block"],
- // vars
- ["var x;", "1 var"],
- ["var x,y;", "2 var"],
- ["var x=1,y=2;", "3 var"],
- ["var x,y=2;", "4 var"],
- // empty
- [";", "1 empty"],
- ["\n;", "2 empty"],
- // expression statement
- ["x;", "1 expression statement"],
- ["5;", "2 expression statement"],
- ["1+2;", "3 expression statement"],
- // if
- ["if (c) x; else y;", "1 if statement"],
- ["if (c) x;", "2 if statement"],
- ["if (c) {} else {};", "3 if statement"],
- ["if (c1) if (c2) s1; else s2;", "4 if statement"],
- // while
- ["do s; while (e);", "1 while statement"],
- ["do { s; } while (e);", "2 while statement"],
- ["while (e) s;", "3 while statement"],
- ["while (e) { s; };", "4 while statement"],
- // for
- ["for (;;) ;", "1 for statement"],
- ["for (;c;x++) x;", "2 for statement"],
- ["for (i;i<len;++i){};", "3 for statement"],
- ["for (var i=0;i<len;++i) {};", "4 for statement"],
- ["for (var i=0,j=0;;){};", "5 for statement"],
- //["for (x in b; c; u) {};", "6 for statement"],
- ["for ((x in b); c; u) {};", "7 for statement"],
- ["for (x in a);", "8 for statement"],
- ["for (var x in a){};", "9 for statement"],
- ["for (var x=5 in a) {};", "10 for statement"],
- ["for (var x = a in b in c) {};", "11 for statement"],
- ["for (var x=function(){a+b;}; a<b; ++i) some;", "11 for statement, testing for parsingForHeader reset with the function"],
- ["for (var x=function(){for (x=0; x<15; ++x) alert(foo); }; a<b; ++i) some;", "11 for statement, testing for parsingForHeader reset with the function"],
- // flow statements
- ["while(1){ continue; }", "1 flow statement"],
- ["label: while(1){ continue label; }", "2 flow statement"],
- ["while(1){ break; }", "3 flow statement"],
- ["somewhere: while(1){ break somewhere; }", "4 flow statement"],
- ["while(1){ continue /* comment */ ; }", "5 flow statement"],
- ["while(1){ continue \n; }", "6 flow statement"],
- ["(function(){ return; })()", "7 flow statement"],
- ["(function(){ return 0; })()", "8 flow statement"],
- ["(function(){ return 0 + \n 1; })()", "9 flow statement"],
- // with
- ["with (e) s;", "with statement"],
- // switch
- ["switch (e) { case x: s; };", "1 switch statement"],
- ["switch (e) { case x: s1;s2; default: s3; case y: s4; };", "2 switch statement"],
- ["switch (e) { default: s1; case x: s2; case y: s3; };", "3 switch statement"],
- ["switch (e) { default: s; };", "4 switch statement"],
- ["switch (e) { case x: s1; case y: s2; };", "5 switch statement"],
- // labels
- ["foo : x;", " flow statement"],
- // throw
- ["throw x;", "1 throw statement"],
- ["throw x\n;", "2 throw statement"],
- // try catch finally
- ["try { s1; } catch (e) { s2; };", "1 trycatchfinally statement"],
- ["try { s1; } finally { s2; };", "2 trycatchfinally statement"],
- ["try { s1; } catch (e) { s2; } finally { s3; };", "3 trycatchfinally statement"],
- // debugger
- ["debugger;", "debuger statement"],
- // function decl
- ["function f(x) { e; return x; };", "1 function declaration"],
- ["function f() { x; y; };", "2 function declaration"],
- ["function f(x,y) { var z; return x; };", "3 function declaration"],
- // function exp
- ["(function f(x) { return x; });", "1 function expression"],
- ["(function empty() {;});", "2 function expression"],
- ["(function empty() {;});", "3 function expression"],
- ["(function (x) {; });", "4 function expression"],
- // program
- ["var x; function f(){;}; null;", "1 program"],
- [";;", "2 program"],
- ["{ x; y; z; }", "3 program"],
- ["function f(){ function g(){;}};", "4 program"],
- ["x;\n/*foo*/\n ;", "5 program"],
-
- // asi
- ["foo: while(1){ continue \n foo; }", "1 asi"],
- ["foo: while(1){ break \n foo; }", "2 asi"],
- ["(function(){ return\nfoo; })()", "3 asi"],
- ["var x; { 1 \n 2 } 3", "4 asi"],
- ["ab /* hi */\ncd", "5 asi"],
- ["ab/*\n*/cd", "6 asi (multi line multilinecomment counts as eol)"],
- ["foo: while(1){ continue /* wtf \n busta */ foo; }", "7 asi illegal with multi line comment"],
- ["function f() { s }", "8 asi"],
- ["function f() { return }", "9 asi"],
-
- // use strict
- // XXX: some of these should actually fail?
- // no support for "use strict" yet...
- ['"use strict"; \'bla\'\n; foo;', "1 directive"],
- ['(function() { "use strict"; \'bla\';\n foo; });', "2 directive"],
- ['"use\\n strict";', "3 directive"],
- ['foo; "use strict";', "4 directive"],
-
- // tests from http://es5conform.codeplex.com/
-
- ['"use strict"; var o = { eval: 42};', "8.7.2-3-1-s: the use of eval as property name is allowed"],
- ['({foo:0,foo:1});', 'Duplicate property name allowed in not strict mode'],
- ['function foo(a,a){}', 'Duplicate parameter name allowed in not strict mode'],
- ['(function foo(eval){})', 'Eval allowed as parameter name in non strict mode'],
- ['(function foo(arguments){})', 'Arguments allowed as parameter name in non strict mode'],
-
- // empty programs
-
- ['', '1 Empty program'],
- ['// test', '2 Empty program'],
- ['//test\n', '3 Empty program'],
- ['\n// test', '4 Empty program'],
- ['\n// test\n', '5 Empty program'],
- ['/* */', '6 Empty program'],
- ['/*\ns,fd\n*/', '7 Empty program'],
- ['/*\ns,fd\n*/\n', '8 Empty program'],
- [' ', '9 Empty program'],
- [' /*\nsmeh*/ \n ', '10 Empty program'],
-
- // trailing whitespace
-
- ['a ', '1 Trailing whitespace'],
- ['a /* something */', '2 Trailing whitespace'],
- ['a\n // hah', '3 Trailing whitespace'],
- ['/abc/de//f', '4 Trailing whitespace'],
- ['/abc/de/*f*/\n ', '5 Trailing whitespace'],
-
- // things the parser tripped over at one point or the other (prevents regression bugs)
- ['for (x;function(){ a\nb };z) x;', 'for header with function body forcing ASI'],
- ['c=function(){return;return};', 'resetting noAsi after literal'],
- ['d\nd()', 'asi exception causing token overflow'],
- ['for(;;){x=function(){}}', 'function expression in a for header'],
- ['for(var k;;){}', 'parser failing due to ASI accepting the incorrect "for" rule'],
- ['({get foo(){ }})', 'getter with empty function body'],
- ['\nreturnr', 'eol causes return statement to ignore local search requirement'],
- [' / /', '1 whitespace before regex causes regex to fail?'],
- ['/ // / /', '2 whitespace before regex causes regex to fail?'],
- ['/ / / / /', '3 whitespace before regex causes regex to fail?'],
-
- ['\n\t// Used for trimming whitespace\n\ttrimLeft = /^\\s+/;\n\ttrimRight = /\\s+$/;\t\n','turned out this didnt crash (the test below did), but whatever.'],
- ['/[\\/]/;', 'escaped forward slash inside class group (would choke on fwd slash)'],
- ['/[/]/;', 'also broke but is valid in es5 (not es3)'],
- ['({get:5});','get property name thats not a getter'],
- ['({set:5});','set property name thats not a setter'],
- ['l !== "px" && (d.style(h, c, (k || 1) + l), j = (k || 1) / f.cur() * j, d.style(h, c, j + l)), i[1] && (k = (i[1] === "-=" ? -1 : 1) * k + j), f.custom(j, k, l)', 'this choked regex/div at some point'],
- ['(/\'/g, \'\\\\\\\'\') + "\'";', 'the sequence of escaped characters confused the tokenizer']
- ];
-
- for (var i=0; i<inps.length; ++i) {
- callback(i, inps[i][0], inps[i][1]);
- };
-};
+++ /dev/null
-[],Array(1),[1,2,3]
+++ /dev/null
-(function(){var a=function(){};return new a(1,2,3,4)})()
+++ /dev/null
-(function(){function a(){}return new a(1,2,3,4)})()
+++ /dev/null
-(function(){function a(){}(function(){return new a(1,2,3)})()})()
+++ /dev/null
-a=1,b=a,c=1,d=b,e=d,longname=2;if(longname+1){x=3;if(x)var z=7}z=1,y=1,x=1,g+=1,h=g,++i,j=i,i++,j=i+17
\ No newline at end of file
+++ /dev/null
-var a=a+"a"+"b"+1+c,b=a+"c"+"ds"+123+c,c=a+"c"+123+d+"ds"+c
\ No newline at end of file
+++ /dev/null
-var a=13,b=1/3
\ No newline at end of file
+++ /dev/null
-function mak(){for(;;);}function foo(){while(bar());}function bar(){return--x}var x=5
+++ /dev/null
-a=func(),b=z;for(a++;i<10;i++)alert(i);var z=1;g=2;for(;i<10;i++)alert(i);var a=2;for(var i=1;i<10;i++)alert(i)
\ No newline at end of file
+++ /dev/null
-var a=1;a==1?a=2:a=17
\ No newline at end of file
+++ /dev/null
-function a(a){return a==1?2:17}
\ No newline at end of file
+++ /dev/null
-function y(a){return typeof a=="object"?a:null}function x(a){return typeof a=="object"?a:a===42?0:a*2}
+++ /dev/null
-function f(){var a;return(a="a")?a:a}f()
\ No newline at end of file
+++ /dev/null
-new(A,B),new(A||B),new(X?A:B)
\ No newline at end of file
+++ /dev/null
-var a=/^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#])(?::(\d))?)?(..?$|(?:[^?#\/]\/))([^?#]*)(?:\?([^#]))?(?:#(.))?/
\ No newline at end of file
+++ /dev/null
-var a={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"}
+++ /dev/null
-var a=3250441966
\ No newline at end of file
+++ /dev/null
-var a=function(b){b(),a()}
\ No newline at end of file
+++ /dev/null
-a:1
\ No newline at end of file
+++ /dev/null
-var a=0;switch(a){case 0:a++}
\ No newline at end of file
+++ /dev/null
-label1:{label2:break label2;console.log(1)}
\ No newline at end of file
+++ /dev/null
-(a?b:c)?d:e
\ No newline at end of file
+++ /dev/null
-o={".5":.5},o={.5:.5},o={.5:.5}
\ No newline at end of file
+++ /dev/null
-result=function(){return 1}()
\ No newline at end of file
+++ /dev/null
-var a=8,b=4,c=4
\ No newline at end of file
+++ /dev/null
-var a={};a["this"]=1,a.that=2
\ No newline at end of file
+++ /dev/null
-var a=2e3,b=.002,c=2e-5
\ No newline at end of file
+++ /dev/null
-var s,i;s="",i=0
\ No newline at end of file
+++ /dev/null
-function bar(a){try{foo()}catch(b){alert("Exception caught (foo not defined)")}alert(a)}bar(10)
+++ /dev/null
-foo+"",a.toString(16),b.toString.call(c)
+++ /dev/null
-function f(){function b(){}a||b()}
+++ /dev/null
-var a={a:1,b:2}
\ No newline at end of file
+++ /dev/null
-(function(){var a=function b(a,b,c){return b}})()
+++ /dev/null
-typeof a=="string",b+""!=c+"",d<e==f<g
\ No newline at end of file
+++ /dev/null
-var a=1,b=2
\ No newline at end of file
+++ /dev/null
-new Array();
-new Array(1);
-new Array(1, 2, 3);
+++ /dev/null
-(function(){
- var Array = function(){};
- return new Array(1, 2, 3, 4);
-})();
+++ /dev/null
-(function(){
- return new Array(1, 2, 3, 4);
- function Array() {};
-})();
+++ /dev/null
-(function(){
- (function(){
- return new Array(1, 2, 3);
- })();
- function Array(){};
-})();
+++ /dev/null
-a=1;
-b=a;
-c=1;
-d=b;
-e=d;
-longname=2;
-if (longname+1) {
- x=3;
- if (x) var z = 7;
-}
-z=1,y=1,x=1
-
-g+=1;
-h=g;
-
-++i;
-j=i;
-
-i++;
-j=i+17;
\ No newline at end of file
+++ /dev/null
-var a = a + "a" + "b" + 1 + c;
-var b = a + "c" + "ds" + 123 + c;
-var c = a + "c" + 123 + d + "ds" + c;
\ No newline at end of file
+++ /dev/null
-// test that the calculation is fold to 13\r
-var a = 1 + 2 * 6;\r
-\r
-// test that it isn't replaced with 0.3333 because that is more characters\r
-var b = 1/3;
\ No newline at end of file
+++ /dev/null
-var x = 5;
-function bar() { return --x; }
-function foo() { while (bar()); }
-function mak() { for(;;); }
+++ /dev/null
-a=func();
-b=z;
-for (a++; i < 10; i++) { alert(i); }
-
-var z=1;
-g=2;
-for (; i < 10; i++) { alert(i); }
-
-var a = 2;
-for (var i = 1; i < 10; i++) { alert(i); }
+++ /dev/null
-var a = 1;\r
-if (a == 1) {\r
- a = 2;\r
-} else {\r
- a = 17;\r
-}\r
+++ /dev/null
-function a(b) {\r
- if (b == 1) {\r
- return 2;\r
- } else {\r
- return 17;\r
- }\r
-\r
- return 3;\r
-}
\ No newline at end of file
+++ /dev/null
-function x(a) {
- if (typeof a === 'object')
- return a;
-
- if (a === 42)
- return 0;
-
- return a * 2;
-}
-
-function y(a) {
- if (typeof a === 'object')
- return a;
-
- return null;
-};
+++ /dev/null
-function f() { var a; if (a = 'a') { return a; } else { return a; } }; f();
\ No newline at end of file
+++ /dev/null
-new (A, B)
-new (A || B)
-new (X ? A : B)
\ No newline at end of file
+++ /dev/null
-var a = /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#])(?::(\d))?)?(..?$|(?:[^?#\/]\/))([^?#]*)(?:\?([^#]))?(?:#(.))?/;
\ No newline at end of file
+++ /dev/null
-var a = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
\ No newline at end of file
+++ /dev/null
-var a = 0xC1BDCEEE;
\ No newline at end of file
+++ /dev/null
-var a = function(b) {
- b();
- a()
-}
+++ /dev/null
-{a: 1}
\ No newline at end of file
+++ /dev/null
-var a = 0;
-switch(a) {
- case 0:
- a++;
- break;
-}
\ No newline at end of file
+++ /dev/null
-label1 : {
- label2 : {
- break label2;
- console.log(2);
- }
- console.log(1);
-}
\ No newline at end of file
+++ /dev/null
-(a ? b : c) ? d : e
\ No newline at end of file
+++ /dev/null
-o = {'.5':.5}
-o = {'0.5':.5}
-o = {0.5:.5}
\ No newline at end of file
+++ /dev/null
-result=(function(){ return 1;})()
\ No newline at end of file
+++ /dev/null
-var a = 1 << 3;
-var b = 8 >> 1;
-var c = 8 >>> 1;
\ No newline at end of file
+++ /dev/null
-var a = {};
-a["this"] = 1;
-a["that"] = 2;
\ No newline at end of file
+++ /dev/null
-var a = 2e3;
-var b = 2e-3;
-var c = 2e-5;
\ No newline at end of file
+++ /dev/null
-var s, i; s = ''; i = 0;
\ No newline at end of file
+++ /dev/null
-function bar(a) {
- try {
- foo();
- } catch(e) {
- alert("Exception caught (foo not defined)");
- }
- alert(a); // 10 in FF, "[object Error]" in IE
-}
-bar(10);
+++ /dev/null
-x = (y, z)
+++ /dev/null
-foo.toString();
-a.toString(16);
-b.toString.call(c);
+++ /dev/null
-function f() {
- if (a) return;
- g();
- function g(){}
-};
+++ /dev/null
-var a = {
- a: 1,
- b: 2, // <-- trailing comma
-};
+++ /dev/null
-(function() {
- var x = function fun(a, fun, b) {
- return fun;
- };
-}());
+++ /dev/null
-typeof a === 'string'
-b + "" !== c + ""
-d < e === f < g
+++ /dev/null
-// var declarations after each other should be combined\r
-var a = 1;\r
-var b = 2;
\ No newline at end of file
+++ /dev/null
-with({}) {
-};
+++ /dev/null
-var fs = require('fs'),
- uglify = require('uglify-js'),
- jsp = uglify.parser,
- nodeunit = require('nodeunit'),
- path = require('path'),
- pro = uglify.uglify;
-
-var Script = process.binding('evals').Script;
-
-var scriptsPath = __dirname;
-
-function compress(code) {
- var ast = jsp.parse(code);
- ast = pro.ast_mangle(ast);
- ast = pro.ast_squeeze(ast, {no_warnings: true, extra: true});
- ast = pro.ast_squeeze_more(ast);
- return pro.gen_code(ast);
-};
-
-var testDir = path.join(scriptsPath, "compress", "test");
-var expectedDir = path.join(scriptsPath, "compress", "expected");
-
-function getTester(script) {
- return function(test) {
- var testPath = path.join(testDir, script);
- var expectedPath = path.join(expectedDir, script);
- var content = fs.readFileSync(testPath, 'utf-8');
- var outputCompress = compress(content);
-
- // Check if the noncompressdata is larger or same size as the compressed data
- test.ok(content.length >= outputCompress.length);
-
- // Check that a recompress gives the same result
- var outputReCompress = compress(content);
- test.equal(outputCompress, outputReCompress);
-
- // Check if the compressed output is what is expected
- var expected = fs.readFileSync(expectedPath, 'utf-8');
- test.equal(outputCompress, expected.replace(/(\r?\n)+$/, ""));
-
- test.done();
- };
-};
-
-var tests = {};
-
-var scripts = fs.readdirSync(testDir);
-for (var i in scripts) {
- var script = scripts[i];
- if (/\.js$/.test(script)) {
- tests[script] = getTester(script);
- }
-}
-
-module.exports = nodeunit.testCase(tests);
+++ /dev/null
-// sample on how to use the parser and walker API to instrument some code
-
-var jsp = require("uglify-js").parser;
-var pro = require("uglify-js").uglify;
-
-function instrument(code) {
- var ast = jsp.parse(code, false, true); // true for the third arg specifies that we want
- // to have start/end tokens embedded in the
- // statements
- var w = pro.ast_walker();
-
- // we're gonna need this to push elements that we're currently looking at, to avoid
- // endless recursion.
- var analyzing = [];
- function do_stat() {
- var ret;
- if (this[0].start && analyzing.indexOf(this) < 0) {
- // without the `analyzing' hack, w.walk(this) would re-enter here leading
- // to infinite recursion
- analyzing.push(this);
- ret = [ "splice", // XXX: "block" is safer
- [ [ "stat",
- [ "call", [ "name", "trace" ],
- [ [ "string", this[0].toString() ],
- [ "num", this[0].start.line ],
- [ "num", this[0].start.col ],
- [ "num", this[0].end.line ],
- [ "num", this[0].end.col ]]]],
- w.walk(this) ]];
- analyzing.pop(this);
- }
- return ret;
- };
- var new_ast = w.with_walkers({
- "stat" : do_stat,
- "label" : do_stat,
- "break" : do_stat,
- "continue" : do_stat,
- "debugger" : do_stat,
- "var" : do_stat,
- "const" : do_stat,
- "return" : do_stat,
- "throw" : do_stat,
- "try" : do_stat,
- "defun" : do_stat,
- "if" : do_stat,
- "while" : do_stat,
- "do" : do_stat,
- "for" : do_stat,
- "for-in" : do_stat,
- "switch" : do_stat,
- "with" : do_stat
- }, function(){
- return w.walk(ast);
- });
- return pro.gen_code(new_ast, { beautify: true });
-}
-
-
-
-
-////// test code follows.
-
-var code = instrument(test.toString());
-console.log(code);
-
-function test() {
- // simple stats
- a = 5;
- c += a + b;
- "foo";
-
- // var
- var foo = 5;
- const bar = 6, baz = 7;
-
- // switch block. note we can't track case lines the same way.
- switch ("foo") {
- case "foo":
- return 1;
- case "bar":
- return 2;
- }
-
- // for/for in
- for (var i = 0; i < 5; ++i) {
- console.log("Hello " + i);
- }
- for (var i in [ 1, 2, 3]) {
- console.log(i);
- }
-
- // note however that the following is broken. I guess we
- // should add the block brackets in this case...
- for (var i = 0; i < 5; ++i)
- console.log("foo");
-}
+++ /dev/null
-// sample on how to use the parser and walker API to instrument some code
-
-var jsp = require("uglify-js").parser;
-var pro = require("uglify-js").uglify;
-
-function instrument(code) {
- var ast = jsp.parse(code, false, true); // true for the third arg specifies that we want
- // to have start/end tokens embedded in the
- // statements
- var w = pro.ast_walker();
-
- function trace (line, comment) {
- var code = pro.gen_code(line, { beautify: true });
- var data = line[0]
-
- var args = []
- if (!comment) comment = ""
- if (typeof data === "object") {
- code = code.split(/\n/).shift()
- args = [ [ "string", data.toString() ],
- [ "string", code ],
- [ "num", data.start.line ],
- [ "num", data.start.col ],
- [ "num", data.end.line ],
- [ "num", data.end.col ]]
- } else {
- args = [ [ "string", data ],
- [ "string", code ]]
-
- }
- return [ "call", [ "name", "trace" ], args ];
- }
-
- // we're gonna need this to push elements that we're currently looking at, to avoid
- // endless recursion.
- var analyzing = [];
- function do_stat() {
- var ret;
- if (this[0].start && analyzing.indexOf(this) < 0) {
- // without the `analyzing' hack, w.walk(this) would re-enter here leading
- // to infinite recursion
- analyzing.push(this);
- ret = [ "splice",
- [ [ "stat", trace(this) ],
- w.walk(this) ]];
- analyzing.pop(this);
- }
- return ret;
- }
-
- function do_cond(c, t, f) {
- return [ this[0], w.walk(c),
- ["seq", trace(t), w.walk(t) ],
- ["seq", trace(f), w.walk(f) ]];
- }
-
- function do_binary(c, l, r) {
- if (c !== "&&" && c !== "||") {
- return [this[0], c, w.walk(l), w.walk(r)];
- }
- return [ this[0], c,
- ["seq", trace(l), w.walk(l) ],
- ["seq", trace(r), w.walk(r) ]];
- }
-
- var new_ast = w.with_walkers({
- "stat" : do_stat,
- "label" : do_stat,
- "break" : do_stat,
- "continue" : do_stat,
- "debugger" : do_stat,
- "var" : do_stat,
- "const" : do_stat,
- "return" : do_stat,
- "throw" : do_stat,
- "try" : do_stat,
- "defun" : do_stat,
- "if" : do_stat,
- "while" : do_stat,
- "do" : do_stat,
- "for" : do_stat,
- "for-in" : do_stat,
- "switch" : do_stat,
- "with" : do_stat,
- "conditional" : do_cond,
- "binary" : do_binary
- }, function(){
- return w.walk(ast);
- });
- return pro.gen_code(new_ast, { beautify: true });
-}
-
-
-////// test code follows.
-
-var code = instrument(test.toString());
-console.log(code);
-
-function test() {
- // simple stats
- a = 5;
- c += a + b;
- "foo";
-
- // var
- var foo = 5;
- const bar = 6, baz = 7;
-
- // switch block. note we can't track case lines the same way.
- switch ("foo") {
- case "foo":
- return 1;
- case "bar":
- return 2;
- }
-
- // for/for in
- for (var i = 0; i < 5; ++i) {
- console.log("Hello " + i);
- }
- for (var i in [ 1, 2, 3]) {
- console.log(i);
- }
-
- for (var i = 0; i < 5; ++i)
- console.log("foo");
-
- for (var i = 0; i < 5; ++i) {
- console.log("foo");
- }
-
- var k = plurp() ? 1 : 0;
- var x = a ? doX(y) && goZoo("zoo")
- : b ? blerg({ x: y })
- : null;
-
- var x = X || Y;
-}
+++ /dev/null
-//convienence function(src, [options]);
-function uglify(orig_code, options){
- options || (options = {});
- var jsp = uglify.parser;
- var pro = uglify.uglify;
-
- var ast = jsp.parse(orig_code, options.strict_semicolons); // parse code and get the initial AST
- ast = pro.ast_mangle(ast, options.mangle_options); // get a new AST with mangled names
- ast = pro.ast_squeeze(ast, options.squeeze_options); // get an AST with compression optimizations
- var final_code = pro.gen_code(ast, options.gen_options); // compressed code here
- return final_code;
-};
-
-uglify.parser = require("./lib/parse-js");
-uglify.uglify = require("./lib/process");
-
-module.exports = uglify
\ No newline at end of file
+0.15.4 / 2011-09-05
+==================
+
+ * Fixed script template html. Closes #316
+ * Revert "Fixed script() tag with trailing ".". Closes #314"
+
+0.15.3 / 2011-08-30
+==================
+
+ * Added Makefile example. Closes #312
+ * Fixed script() tag with trailing ".". Closes #314
+
+0.15.2 / 2011-08-26
+==================
+
+ * Fixed new conditional boundaries. Closes #307
+
+0.15.1 / 2011-08-26
+==================
+
+ * Fixed jade(1) support due to `res.render()` removal
+ * Removed --watch support (use a makefile + watch...)
+
+0.15.0 / 2011-08-26
+==================
+
+ * Added `client` option to reference runtime helpers
+ * Added `Array.isArray()` for runtime.js as well
+ * Added `Object.keys()` for the client-side runtime
+ * Added first-class `if`, `unless`, `else` and `else if` support
+ * Added first-class `each` / `for` support
+ * Added `make benchmark` for continuous-bench
+ * Removed `inline` option, SS helpers are no longer inlined either
+ * Removed `Parser#debug()`
+ * Removed `jade.render()` and `jade.renderFile()`
+ * Fixed runtime.js `escape()` bug causing window.escape to be used
+ * Fixed a bunch of tests
+
+0.14.2 / 2011-08-16
+==================
+
+ * Added `include` support for non-jade files
+ * Fixed code indentation when followed by newline(s). Closes #295 [reported by masylum]
+
+0.14.1 / 2011-08-14
+==================
+
+ * Added `colons` option for everyone stuck with ":". Closes #231
+ * Optimization: consecutive lines are merged in compiled js
+
+0.14.0 / 2011-08-08
+==================
+
+ * Added array iteration with index example. Closes #276
+ * Added _runtime.js_
+ * Added `compileDebug` option to enable lineno instrumentation
+ * Added `inline` option to disable inlining of helpers (for client-side)
+
0.13.0 / 2011-07-13
==================
SRC = $(shell find lib -name "*.js" -type f)
UGLIFY_FLAGS = --no-mangle
+all: jade.min.js runtime.min.js
+
test:
@./node_modules/.bin/expresso \
-I node_modules \
$(TESTS)
benchmark:
- @node benchmarks/jade.js \
- && node benchmarks/jade-self.js \
- && node benchmarks/haml.js \
- && node benchmarks/haml2.js \
- && node benchmarks/ejs.js
+ @node support/benchmark
jade.js: $(SRC)
@node support/compile.js $^
&& du jade.min.js \
&& du jade.js
+runtime.js: lib/runtime.js
+ @cat support/head.js $< support/foot.js > $@
+
+runtime.min.js: runtime.js
+ @uglifyjs $(UGLIFY_FLAGS) $< > $@ \
+ && du runtime.min.js \
+ && du runtime.js
+
clean:
rm -f jade.js
rm -f jade.min.js
+ rm -f runtime.js
+ rm -f runtime.min.js
.PHONY: test benchmark clean
-
# Jade - template engine
Jade is a high performance template engine heavily influenced by [Haml](http://haml-lang.com)
- combine dynamic and static tag classes
- parse tree manipulation via _filters_
- supports [Express JS](http://expressjs.com) out of the box
- - transparent iteration over objects, arrays, and even non-enumerables via `- each`
+ - transparent iteration over objects, arrays, and even non-enumerables via `each`
- block comments
- no tag prefix
- AST filters
$ make jade.js
- Alternatively, if uglifyjs is installed via npm (`npm install uglify-js`) you may execute the following which will create both files.
+ Alternatively, if uglifyjs is installed via npm (`npm install uglify-js`) you may execute the following which will create both files. However each release builds these for you.
$ make jade.min.js
+ By default Jade instruments templates with line number statements such as `__.lineno = 3` for debugging purposes. When used in a browser it's useful to minimize this boiler plate, you can do so by passing the option `{ compileDebug: false }`. The following template
+
+ p Hello #{name}
+
+ Can then be as small as the following generated function:
+
+```js
+function anonymous(locals, attrs, escape, rethrow) {
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ buf.push('\n<p>Hello ' + escape((interp = name) == null ? '' : interp) + '\n</p>');
+ }
+ return buf.join("");
+}
+```
+
+ Through the use of Jade's `./runtime.js` you may utilize these pre-compiled templates on the client-side _without_ Jade itself, all you need is the associated utility functions (in runtime.js), which are then available as `jade.attrs`, `jade.escape` etc. To enable this you should pass `{ client: true }` to `jade.compile()` to tell Jade to reference the helper functions
+ via `jade.attrs`, `jade.escape` etc.
+
+```js
+function anonymous(locals, attrs, escape, rethrow) {
+ var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ buf.push('\n<p>Hello ' + escape((interp = name) == null ? '' : interp) + '\n</p>');
+ }
+ return buf.join("");
+}
+```
+
## Public API
```javascript
var jade = require('jade');
- // Render a string
- jade.render('string of jade', { options: 'here' });
-
- // Render a file
- jade.renderFile('path/to/some.jade', { options: 'here' }, function(err, html){
- // options are optional,
- // the callback can be the second arg
- });
-
// Compile a function
var fn = jade.compile('string of jade', options);
- fn.call(scope, locals);
+ fn(locals);
```
### Options
- - `scope` Evaluation scope (`this`)
- `self` Use a `self` namespace to hold the locals. _false by default_
- `locals` Local variable object
- `filename` Used in exceptions, and required when using includes
- `debug` Outputs tokens and function body generated
- `compiler` Compiler to replace jade's default
+ - `compileDebug` When `false` no debug instrumentation is compiled
## Syntax
renders `<p>foo bar baz rawr.....</p>`
interpolation? yup! both types of text can utilize interpolation,
-if we passed `{ locals: { name: 'tj', email: 'tj@vision-media.ca' }}` to `render()`
-we can do the following:
+if we passed `{ name: 'tj', email: 'tj@vision-media.ca' }` to the compiled function we can do the following:
#user #{name} <#{email}>
<p>.</p>
+
+It should be noted that text blocks should be doubled escaped. For example if you desire the following output.
+
+ </p>foo\bar</p>
+
+use:
+
+ p.
+ foo\\bar
+
### Comments
Single line comments currently look the same as JavaScript comments,
a(href='/login', title='View login page') Login
+When a value is `undefined` or `null` the attribute is _not_ added,
+so this is fine, it will not compile 'something="null"'.
+
+ div(something=null)
+
Boolean attributes are also supported:
input(type="checkbox", checked)
Boolean attributes with code will only output the attribute when `true`:
- input(type="checkbox", checked= someValue)
+ input(type="checkbox", checked=someValue)
Multiple lines work too:
a(href='/user/' + user.id)= user.name
-or we could use jade's interpolation:
+or we could use jade's interpolation, which I added because everyone
+using Ruby or CoffeeScript seems to think this is legal js..:
a(href='/user/#{user.id}')= user.name
body(class=bodyClasses)
+### HTML
+
+ Inline html is fine, we can use the pipe syntax to
+ write arbitrary text, in this case some html:
+
+```
+html
+ body
+ | <h1>Title</h1>
+ | <p>foo bar baz</p>
+```
+
+ Or we can use the trailing `.` to indicate to Jade that we
+ only want text in this block, allowing us to omit the pipes:
+
+```
+html
+ body.
+ <h1>Title</h1>
+ <p>foo bar baz</p>
+```
+
+ Both of these examples yield the same result:
+
+```
+<html><body><h1>Title</h1>
+<p>foo bar baz</p>
+</body></html>
+```
+
+ The same rule applies for anywhere you can have text
+ in jade, raw html is fine:
+
+```
+html
+ body
+ h1 User <em>#{name}</em>
+```
+
### Doctypes
To add a doctype simply use `!!!`, or `doctype` followed by an optional value:
p!= aVarContainingMoreHTML
-The on exception made in terms of allowing "vanilla" JavaScript, is
-the `- each` token. This takes the form of:
+## Iteration
- - each VAL[, KEY] in OBJ
+ Along with vanilla JavaScript Jade also supports a subset of
+ constructs that allow you to create more designer-friendly templates,
+ one of these constructs is `each`, taking the form:
+
+ each VAL[, KEY] in OBJ
An example iterating over an array:
- var items = ["one", "two", "three"]
- - each item in items
+ each item in items
li= item
outputs:
<li>two</li>
<li>three</li>
+iterating an array with index:
+
+ - var items = ["one", "two", "three"]
+ each item, i in items
+ li #{item}: #{i}
+
+outputs:
+
+ <li>one: 0</li>
+ <li>two: 1</li>
+ <li>three: 2</li>
+
iterating an object's keys and values:
- var obj = { foo: 'bar' }
- - each val, key in obj
+ each val, key in obj
li #{key}: #{val}
would output `<li>foo: bar</li>`
-You can also nest these!
+Internally Jade converts these statements to regular
+JavaScript loops such as `users.forEach(function(user){`,
+so lexical scope and nesting applies as it would with regular
+JavaScript:
- - each user in users
- - each role in user.roles
+ each user in users
+ each role in user.roles
li= role
-When a property is undefined, Jade will output an empty string. For example:
+ You may also use `for` if you prefer:
+
+ for user in users
+ for role in user.roles
+ li= role
+
+## Conditionals
+
+ Jade conditionals are equivalent to those using the code (`-`) prefix,
+ however allow you to ditch parenthesis to become more designer friendly,
+ however keep in mind the expression given is _regular_ JavaScript:
- textarea= user.signature
+ for user in users
+ if user.role == 'admin'
+ p #{user.name} is an admin
+ else
+ p= user.name
-when undefined would normally output "undefined" in your html, however recent
-versions of Jade will simply render:
+ is equivalent to the following using vanilla JavaScript literals:
- <textarea></textarea>
+ for user in users
+ - if (user.role == 'admin')
+ p #{user.name} is an admin
+ - else
+ p= user.name
+
+ Jade also provides have `unless` which is equivalent to `if (!(expr))`:
+
+ for user in users
+ unless user.isAnonymous
+ p
+ | Click to view
+ a(href='/users/' + user.id)= user.name
## Includes
both includes _includes/head_ and _includes/foot_ are
read relative to the `filename` option given to _layout.jade_,
-which should be an absolute path to this file, however Express
-and the `renderFile()` method do this for you. Include then parses
-these files, and injects the AST produced to render what you would expect:
+which should be an absolute path to this file, however Express does this for you. Include then parses these files, and injects the AST produced to render what you would expect:
```html
<html>
</div>
```
-## bin/jade
+## Generated Output
+
+ Suppose we have the following Jade:
+
+```
+- var title = 'yay'
+h1.title #{title}
+p Just an example
+```
+
+ When the `compileDebug` option is not explicitly `false`, Jade
+ will compile the function instrumented with `__.lineno = n;`, which
+ in the event of an exception is passed to `rethrow()` which constructs
+ a useful message relative to the initial Jade input.
+
+```js
+function anonymous(locals) {
+ var __ = { lineno: 1, input: "- var title = 'yay'\nh1.title #{title}\np Just an example", filename: "testing/test.js" };
+ var rethrow = jade.rethrow;
+ try {
+ var attrs = jade.attrs, escape = jade.escape;
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ __.lineno = 1;
+ var title = 'yay'
+ __.lineno = 2;
+ buf.push('<h1');
+ buf.push(attrs({ "class": ('title') }));
+ buf.push('>');
+ buf.push('' + escape((interp = title) == null ? '' : interp) + '');
+ buf.push('</h1>');
+ __.lineno = 3;
+ buf.push('<p>');
+ buf.push('Just an example');
+ buf.push('</p>');
+ }
+ return buf.join("");
+ } catch (err) {
+ rethrow(err, __.input, __.filename, __.lineno);
+ }
+}
+```
+
+When the `compileDebug` option _is_ explicitly `false`, this instrumentation
+is stripped, which is very helpful for light-weight client-side templates. Combining Jade's options with the `./runtime.js` file in this repo allows you
+to toString() compiled templates and avoid running the entire Jade library on
+the client, increasing performance, and decreasing the amount of JavaScript
+required.
+
+```js
+function anonymous(locals) {
+ var attrs = jade.attrs, escape = jade.escape;
+ var buf = [];
+ with (locals || {}) {
+ var interp;
+ var title = 'yay'
+ buf.push('<h1');
+ buf.push(attrs({ "class": ('title') }));
+ buf.push('>');
+ buf.push('' + escape((interp = title) == null ? '' : interp) + '');
+ buf.push('</h1>');
+ buf.push('<p>');
+ buf.push('Just an example');
+ buf.push('</p>');
+ }
+ return buf.join("");
+}
+```
-Output html to _stdout_:
+## Example Makefile
- jade < my.jade > my.html
+ Below is an example Makefile used to compile _pages/*.jade_
+ into _pages/*.html_ files by simply executing `make`.
+
+```make
+JADE = $(shell find pages/*.jade)
+HTML = $(JADE:.jade=.html)
-Generate _examples/*.html_:
+all: $(HTML)
+
+%.html: %.jade
+ jade < $< > $@
- jade examples/*.jade
+clean:
+ rm -f $(HTML)
+
+.PHONY: clean
+```
-Pass options:
+this can be combined with the `watch(1)` command to produce
+a watcher-like behaviour:
+
+ $ watch make
+
+## jade(1)
+
+```
- jade examples/layout.jade --options '{ locals: { title: "foo" }}'
+Usage: jade [options] [dir|file ...]
-Usage info:
+Options:
- Usage: jade [options]
- [path ...]
- < in.jade > out.jade
- Options:
- -o, --options <str> JavaScript options object passed
- -h, --help Output help information
- -w, --watch Watch file(s) or folder(s) for changes and re-compile
- -v, --version Output jade version
- --out <dir> Output the compiled html to <dir>
+ -h, --help output usage information
+ -v, --version output the version number
+ -o, --obj <str> javascript options object
+ -O, --out <dir> output the compiled html to <dir>
+
+Examples:
+
+ # translate jade the templates dir
+ $ jade templates
+
+ # create {foo,bar}.html
+ $ jade {foo,bar}.jade
+
+ # jade over stdio
+ $ jade < my.jade > my.html
+
+ # jade over stdio
+ $ echo "h1 Jade!" | jade
+
+ # foo, bar dirs rendering to /tmp
+ $ jade foo bar --out /tmp
+
+```
## License
+++ /dev/null
-
-/**
- * Module dependencies.
- */
-
-var sys = require('sys');
-
-var currentLabel,
- startTime;
-
-exports.times = 2000;
-
-exports.start = function(label){
- currentLabel = label;
- startTime = new Date;
- sys.print(' - \x1b[33m' + currentLabel + '\x1b[0m: ');
-};
-
-exports.stop = function(){
- var stopTime = new Date,
- duration = stopTime - startTime;
- sys.print(duration + ' ms\n');
-};
-
-exports.locals = {
- one: 'one',
- two: 'two',
- three: 'three',
- items: Array(200).join('test ').split(' ')
-};
-
-console.log('\nbenchmarking %d times\n', exports.times);
\ No newline at end of file
+++ /dev/null
-
-/**
- * Module dependencies.
- */
-
-var bm = require('./common'),
- ejs = require('./ejs/lib/ejs'),
- fs = require('fs');
-
-var str = fs.readFileSync(__dirname + '/example.ejs', 'ascii');
-
-var n = bm.times;
-bm.start('ejs compilation');
-while (n--) {
- ejs.render(str, { locals: bm.locals });
-}
-bm.stop();
-
-var n = bm.times;
-bm.start('ejs execution');
-while (n--) {
- ejs.render(str, { locals: bm.locals, cache: true, filename: 'example.ejs' });
-}
-bm.stop();
\ No newline at end of file
+++ /dev/null
-# ignore any vim files:
-*.sw[a-z]
-vim/.netrwhist
+++ /dev/null
-[submodule "support/expresso"]
- path = support/expresso
- url = http://github.com/visionmedia/expresso.git
+++ /dev/null
-
-0.3.1 / 2011-02-23
-==================
-
- * Fixed optional `compile()` options
-
-0.3.0 / 2011-02-14
-==================
-
- * Added 'json' filter [Yuriy Bogdanov]
- * Use exported version of parse function to allow monkey-patching [Anatoliy Chakkaev]
-
-0.2.1 / 2010-10-07
-==================
-
- * Added filter support
- * Fixed _cache_ option. ~4x performance increase
-
-0.2.0 / 2010-08-05
-==================
-
- * Added support for global tag config
- * Added custom tag support. Closes #5
- * Fixed whitespace bug. Closes #4
-
-0.1.0 / 2010-08-04
-==================
-
- * Faster implementation [ashleydev]
-
-0.0.4 / 2010-08-02
-==================
-
- * Fixed single quotes for content outside of template tags. [aniero]
- * Changed; `exports.compile()` now expects only "locals"
-
-0.0.3 / 2010-07-15
-==================
-
- * Fixed single quotes
-
-0.0.2 / 2010-07-09
-==================
-
- * Fixed newline preservation
-
-0.0.1 / 2010-07-09
-==================
-
- * Initial release
+++ /dev/null
-
-test:
- @./support/expresso/bin/expresso -I lib test/*.test.js
-
-.PHONY: test
\ No newline at end of file
+++ /dev/null
-
-# EJS
-
-Embedded JavaScript templates.
-
-## Installation
-
- $ npm install ejs
-
-## Features
-
- * Complies with the [Express](http://expressjs.com) view system
- * Static caching of intermediate JavaScript
- * Unbuffered code for conditionals etc `<% code %>`
- * Escapes html by default with `<%= code %>`
- * Unescaped buffering with `<%- code %>`
- * Supports tag customization
- * Filter support for designer-friendly templates
-
-## Example
-
- <% if (user) { %>
- <h2><%= user.name %></h2>
- <% } %>
-
-## Usage
-
- ejs.compile(str, options);
- // => Function
-
- ejs.render(str, options);
- // => str
-
-## Options
-
- - `locals` Local variables object
- - `cache` Compiled functions are cached, requires `filename`
- - `filename` Used by `cache` to key caches
- - `scope` Function execution context
- - `debug` Output generated function body
- - `open` Open tag, defaulting to "<%"
- - `close` Closing tag, defaulting to "%>"
-
-## Custom Tags
-
-Custom tags can also be applied globally:
-
- var ejs = require('ejs');
- ejs.open = '{{';
- ejs.close = '}}';
-
-Which would make the following a valid template:
-
- <h1>{{= title }}</h1>
-
-## Filters
-
-EJS conditionally supports the concept of "filters". A "filter chain"
-is a designer friendly api for manipulating data, without writing JavaScript.
-
-Filters can be applied by supplying the _:_ modifier, so for example if we wish to take the array `[{ name: 'tj' }, { name: 'mape' }, { name: 'guillermo' }]` and output a list of names we can do this simply with filters:
-
-Template:
-
- <p><%=: users | map:'name' | join %></p>
-
-Output:
-
- <p>Tj, Mape, Guillermo</p>
-
-Render call:
-
- ejs.render(str, {
- locals: {
- users: [
- { name: 'tj' },
- { name: 'mape' },
- { name: 'guillermo' }
- ]
- }
- });
-
-Or perhaps capitalize the first user's name for display:
-
- <p><%=: users | first | capitalize %></p>
-
-## Filter List
-
-Currently these filters are available:
-
- - first
- - last
- - capitalize
- - downcase
- - upcase
- - sort
- - sort_by:'prop'
- - size
- - length
- - plus:n
- - minus:n
- - times:n
- - divided_by:n
- - join:'val'
- - truncate:n
- - truncate_words:n
- - replace:pattern,substitution
- - prepend:val
- - append:val
- - map:'prop'
- - reverse
- - get:'prop'
-
-## License
-
-(The MIT License)
-
-Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca>
-
-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.
+++ /dev/null
-
-
-var ejs = require('./lib/ejs'),
- str = '<% if (foo) { %><p><%= foo %></p><% } %>',
- times = 50000;
-
-console.log('rendering ' + times + ' times');
-
-var start = new Date;
-while (times--) {
- ejs.render(str, { cache: true, filename: 'test', locals: { foo: 'bar' }});
-}
-
-console.log('took ' + (new Date - start) + 'ms');
\ No newline at end of file
+++ /dev/null
-<% if (names.length) { %>
- <ul>
- <% names.forEach(function(name){ %>
- <li><%= name %></li>
- <% }) %>
- </ul>
-<% } %>
\ No newline at end of file
+++ /dev/null
-
-/**
- * Module dependencies.
- */
-
-var ejs = require('../')
- , fs = require('fs')
- , str = fs.readFileSync(__dirname + '/list.ejs', 'utf8');
-
-var ret = ejs.render(str, {
- locals: {
- names: ['foo', 'bar', 'baz']
- }
-});
-
-console.log(ret);
\ No newline at end of file
+++ /dev/null
-
-module.exports = require('./lib/ejs');
\ No newline at end of file
+++ /dev/null
-
-/*!
- * EJS
- * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var sys = require('sys')
- , utils = require('./utils');
-
-/**
- * Library version.
- */
-
-exports.version = '0.3.0';
-
-/**
- * Filters.
- *
- * @type Object
- */
-
-var filters = exports.filters = require('./filters');
-
-/**
- * Intermediate js cache.
- *
- * @type Object
- */
-
-var cache = {};
-
-/**
- * Clear intermediate js cache.
- *
- * @api public
- */
-
-exports.clearCache = function(){
- cache = {};
-};
-
-/**
- * Translate filtered code into function calls.
- *
- * @param {String} js
- * @return {String}
- * @api private
- */
-
-function filtered(js) {
- return js.substr(1).split('|').reduce(function(js, filter){
- var parts = filter.split(':')
- , name = parts.shift()
- , args = parts.shift() || '';
- if (args) args = ', ' + args;
- return 'filters.' + name + '(' + js + args + ')';
- });
-};
-
-/**
- * Parse the given `str` of ejs, returning the function body.
- *
- * @param {String} str
- * @return {String}
- * @api public
- */
-
-var parse = exports.parse = function(str, options){
- var options = options || {}
- , open = options.open || exports.open || '<%'
- , close = options.close || exports.close || '%>';
-
- var buf = [
- "var buf = [];"
- , "\nwith (locals) {"
- , "\n buf.push('"
- ];
-
- for (var i = 0, len = str.length; i < len; ++i) {
- if (str.slice(i, open.length + i) == open) {
- i += open.length
-
- var prefix, postfix;
- switch (str[i]) {
- case '=':
- prefix = "', escape(";
- postfix = "), '";
- ++i;
- break;
- case '-':
- prefix = "', ";
- postfix = ", '";
- ++i;
- break;
- default:
- prefix = "'); ";
- postfix = "; buf.push('";
- }
-
- var start = i;
- var end = str.indexOf(close, i);
- var js = str.substring(i, end);
- if (js[0] == ':') js = filtered(js);
- buf.push(prefix, js, postfix);
- i += end - start + close.length - 1;
-
- } else if (str[i] == "\\") {
- buf.push("\\\\");
- } else if (str[i] == "'") {
- buf.push("\\'");
- } else if (str[i] == "\r") {
- buf.push(" ");
- } else if (str[i] == "\n") {
- buf.push("\\n");
- } else {
- buf.push(str[i]);
- }
- }
- buf.push("');\n}\nreturn buf.join('');");
- return buf.join('');
-};
-
-/**
- * Compile the given `str` of ejs into a `Function`.
- *
- * @param {String} str
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-var compile = exports.compile = function(str, options){
- options = options || {};
- if (options.debug) sys.puts(exports.parse(str));
- var fn = new Function('locals, filters, escape', exports.parse(str, options));
- return function(locals){
- return fn.call(this, locals, filters, utils.escape);
- }
-};
-
-/**
- * Render the given `str` of ejs.
- *
- * Options:
- *
- * - `locals` Local variables object
- * - `cache` Compiled functions are cached, requires `filename`
- * - `filename` Used by `cache` to key caches
- * - `scope` Function execution context
- * - `debug` Output generated function body
- * - `open` Open tag, defaulting to "<%"
- * - `close` Closing tag, defaulting to "%>"
- *
- * @param {String} str
- * @param {Object} options
- * @return {String}
- * @api public
- */
-
-exports.render = function(str, options){
- var fn
- , options = options || {};
- if (options.cache) {
- if (options.filename) {
- fn = cache[options.filename] || (cache[options.filename] = compile(str, options));
- } else {
- throw new Error('"cache" option requires "filename".');
- }
- } else {
- fn = compile(str, options);
- }
- return fn.call(options.scope, options.locals || {});
-};
-
-/**
- * Expose to require().
- */
-
-if (require.extensions) {
- require.extensions['.ejs'] = function(module, filename) {
- source = require('fs').readFileSync(filename, 'utf-8');
- module._compile(compile(source, {}), filename);
- };
-} else if (require.registerExtension) {
- require.registerExtension('.ejs', function(src) {
- return compile(src, {});
- });
-}
+++ /dev/null
-
-/*!
- * EJS - Filters
- * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
- * MIT Licensed
- */
-
-/**
- * First element of the target `obj`.
- */
-
-exports.first = function(obj) {
- return obj[0];
-};
-
-/**
- * Last element of the target `obj`.
- */
-
-exports.last = function(obj) {
- return obj[obj.length - 1];
-};
-
-/**
- * Capitalize the first letter of the target `str`.
- */
-
-exports.capitalize = function(str){
- str = String(str);
- return str[0].toUpperCase() + str.substr(1, str.length);
-};
-
-/**
- * Downcase the target `str`.
- */
-
-exports.downcase = function(str){
- return String(str).toLowerCase();
-};
-
-/**
- * Uppercase the target `str`.
- */
-
-exports.upcase = function(str){
- return String(str).toUpperCase();
-};
-
-/**
- * Sort the target `obj`.
- */
-
-exports.sort = function(obj){
- return Object.create(obj).sort();
-};
-
-/**
- * Sort the target `obj` by the given `prop` ascending.
- */
-
-exports.sort_by = function(obj, prop){
- return Object.create(obj).sort(function(a, b){
- a = a[prop], b = b[prop];
- if (a > b) return 1;
- if (a < b) return -1;
- return 0;
- });
-};
-
-/**
- * Size or length of the target `obj`.
- */
-
-exports.size = exports.length = function(obj) {
- return obj.length;
-};
-
-/**
- * Add `a` and `b`.
- */
-
-exports.plus = function(a, b){
- return Number(a) + Number(b);
-};
-
-/**
- * Subtract `b` from `a`.
- */
-
-exports.minus = function(a, b){
- return Number(a) - Number(b);
-};
-
-/**
- * Multiply `a` by `b`.
- */
-
-exports.times = function(a, b){
- return Number(a) * Number(b);
-};
-
-/**
- * Divide `a` by `b`.
- */
-
-exports.divided_by = function(a, b){
- return Number(a) / Number(b);
-};
-
-/**
- * Join `obj` with the given `str`.
- */
-
-exports.join = function(obj, str){
- return obj.join(str || ', ');
-};
-
-/**
- * Truncate `str` to `len`.
- */
-
-exports.truncate = function(str, len){
- str = String(str);
- return str.substr(0, len);
-};
-
-/**
- * Truncate `str` to `n` words.
- */
-
-exports.truncate_words = function(str, n){
- var str = String(str)
- , words = str.split(/ +/);
- return words.slice(0, n).join(' ');
-};
-
-/**
- * Replace `pattern` with `substitution` in `str`.
- */
-
-exports.replace = function(str, pattern, substitution){
- return String(str).replace(pattern, substitution || '');
-};
-
-/**
- * Prepend `val` to `obj`.
- */
-
-exports.prepend = function(obj, val){
- return Array.isArray(obj)
- ? [val].concat(obj)
- : val + obj;
-};
-
-/**
- * Append `val` to `obj`.
- */
-
-exports.append = function(obj, val){
- return Array.isArray(obj)
- ? obj.concat(val)
- : obj + val;
-};
-
-/**
- * Map the given `prop`.
- */
-
-exports.map = function(arr, prop){
- return arr.map(function(obj){
- return obj[prop];
- });
-};
-
-/**
- * Reverse the given `obj`.
- */
-
-exports.reverse = function(obj){
- return Array.isArray(obj)
- ? obj.reverse()
- : String(obj).split('').reverse().join('');
-};
-
-/**
- * Get `prop` of the given `obj`.
- */
-
-exports.get = function(obj, prop){
- return obj[prop];
-};
-
-/**
- * Packs the given `obj` into json string
- */
-exports.json = function(obj){
- return JSON.stringify(obj);
-};
\ No newline at end of file
+++ /dev/null
-
-/*!
- * EJS
- * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
- * MIT Licensed
- */
-
-/**
- * Escape the given string of `html`.
- *
- * @param {String} html
- * @return {String}
- * @api private
- */
-
-exports.escape = function(html){
- return String(html)
- .replace(/&(?!\w+;)/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"');
-};
-
\ No newline at end of file
+++ /dev/null
-{
- "name": "ejs",
- "description": "Embedded JavaScript templates",
- "version": "0.3.1",
- "author": "TJ Holowaychuk <tj@vision-media.ca>",
- "keywords": ["template", "engine", "ejs"],
- "main": "./lib/ejs.js"
-}
\ No newline at end of file
+++ /dev/null
-
-/**
- * Module dependencies.
- */
-
-var ejs = require('ejs');
-
-module.exports = {
- 'test .version': function(assert){
- assert.ok(/^\d+\.\d+\.\d+$/.test(ejs.version), 'Test .version format');
- },
-
- 'test html': function(assert){
- assert.equal('<p>yay</p>', ejs.render('<p>yay</p>'));
- },
-
- 'test buffered code': function(assert){
- var html = '<p>tj</p>',
- str = '<p><%= name %></p>',
- locals = { name: 'tj' };
- assert.equal(html, ejs.render(str, { locals: locals }));
- },
-
- 'test unbuffered code': function(assert){
- var html = '<p>tj</p>',
- str = '<% if (name) { %><p><%= name %></p><% } %>',
- locals = { name: 'tj' };
- assert.equal(html, ejs.render(str, { locals: locals }));
- },
-
- 'test `scope` option': function(assert){
- var html = '<p>tj</p>',
- str = '<p><%= this %></p>';
- assert.equal(html, ejs.render(str, { scope: 'tj' }));
- },
-
- 'test escaping': function(assert){
- assert.equal('<script>', ejs.render('<%= "<script>" %>'));
- assert.equal('<script>', ejs.render('<%- "<script>" %>'));
- },
-
- 'test newlines': function(assert){
- var html = '\n<p>tj</p>\n<p>tj@sencha.com</p>',
- str = '<% if (name) { %>\n<p><%= name %></p>\n<p><%= email %></p><% } %>',
- locals = { name: 'tj', email: 'tj@sencha.com' };
- assert.equal(html, ejs.render(str, { locals: locals }));
- },
-
- 'test single quotes': function(assert){
- var html = '<p>WAHOO</p>',
- str = "<p><%= up('wahoo') %></p>",
- locals = { up: function(str){ return str.toUpperCase(); }};
- assert.equal(html, ejs.render(str, { locals: locals }));
- },
-
- 'test single quotes in the html': function(assert){
- var html = '<p>WAHOO that\'s cool</p>',
- str = '<p><%= up(\'wahoo\') %> that\'s cool</p>',
- locals = { up: function(str){ return str.toUpperCase(); }};
- assert.equal(html, ejs.render(str, { locals: locals }));
- },
-
- 'test multiple single quotes': function(assert) {
- var html = "<p>couldn't shouldn't can't</p>",
- str = "<p>couldn't shouldn't can't</p>";
- assert.equal(html, ejs.render(str));
- },
-
- 'test single quotes inside tags': function(assert) {
- var html = '<p>string</p>',
- str = "<p><%= 'string' %></p>";
- assert.equal(html, ejs.render(str));
- },
-
- 'test back-slashes in the document': function(assert) {
- var html = "<p>backslash: '\\'</p>",
- str = "<p>backslash: '\\'</p>";
- assert.equal(html, ejs.render(str));
- },
-
- 'test double quotes': function(assert){
- var html = '<p>WAHOO</p>',
- str = '<p><%= up("wahoo") %></p>',
- locals = { up: function(str){ return str.toUpperCase(); }};
- assert.equal(html, ejs.render(str, { locals: locals }));
- },
-
- 'test multiple double quotes': function(assert) {
- var html = '<p>just a "test" wahoo</p>',
- str = '<p>just a "test" wahoo</p>';
- assert.equal(html, ejs.render(str));
- },
-
- 'test whitespace': function(assert){
- var html = '<p>foo</p>',
- str = '<p><%="foo"%></p>';
- assert.equal(html, ejs.render(str));
-
- var html = '<p>foo</p>',
- str = '<p><%=bar%></p>';
- assert.equal(html, ejs.render(str, { locals: { bar: 'foo' }}));
- },
-
- 'test custom tags': function(assert){
- var html = '<p>foo</p>',
- str = '<p>{{= "foo" }}</p>';
-
- assert.equal(html, ejs.render(str, {
- open: '{{',
- close: '}}'
- }));
-
- var html = '<p>foo</p>',
- str = '<p><?= "foo" ?></p>';
-
- assert.equal(html, ejs.render(str, {
- open: '<?',
- close: '?>'
- }));
- },
-
- 'test custom tags over 2 chars': function(assert){
- var html = '<p>foo</p>',
- str = '<p>{{{{= "foo" }>>}</p>';
-
- assert.equal(html, ejs.render(str, {
- open: '{{{{',
- close: '}>>}'
- }));
-
- var html = '<p>foo</p>',
- str = '<p><??= "foo" ??></p>';
-
- assert.equal(html, ejs.render(str, {
- open: '<??',
- close: '??>'
- }));
- },
-
- 'test global custom tags': function(assert){
- var html = '<p>foo</p>',
- str = '<p>{{= "foo" }}</p>';
- ejs.open = '{{';
- ejs.close = '}}';
- assert.equal(html, ejs.render(str));
- delete ejs.open;
- delete ejs.close;
- },
-
- 'test iteration': function(assert){
- var html = '<p>foo</p>',
- str = '<% for (var key in items) { %>'
- + '<p><%= items[key] %></p>'
- + '<% } %>';
- assert.equal(html, ejs.render(str, {
- locals: {
- items: ['foo']
- }
- }));
-
- var html = '<p>foo</p>',
- str = '<% items.forEach(function(item){ %>'
- + '<p><%= item %></p>'
- + '<% }) %>';
- assert.equal(html, ejs.render(str, {
- locals: {
- items: ['foo']
- }
- }));
- },
-
- 'test filter support': function(assert){
- var html = 'Zab',
- str = '<%=: items | reverse | first | reverse | capitalize %>';
- assert.equal(html, ejs.render(str, {
- locals: {
- items: ['foo', 'bar', 'baz']
- }
- }));
- },
-
- 'test filter argument support': function(assert){
- var html = 'tj, guillermo',
- str = '<%=: users | map:"name" | join:", " %>';
- assert.equal(html, ejs.render(str, {
- locals: {
- users: [
- { name: 'tj' },
- { name: 'guillermo' }
- ]
- }
- }));
- },
-
- 'test sort_by filter': function(assert){
- var html = 'tj',
- str = '<%=: users | sort_by:"name" | last | get:"name" %>';
- assert.equal(html, ejs.render(str, {
- locals: {
- users: [
- { name: 'guillermo' },
- { name: 'tj' },
- { name: 'mape' }
- ]
- }
- }));
- },
-
- 'test custom filters': function(assert){
- var html = 'Welcome Tj Holowaychuk',
- str = '<%=: users | first | greeting %>';
-
- ejs.filters.greeting = function(user){
- return 'Welcome ' + user.first + ' ' + user.last + '';
- };
-
- assert.equal(html, ejs.render(str, {
- locals: {
- users: [
- { first: 'Tj', last: 'Holowaychuk' }
- ]
- }
- }));
- }
-};
+++ /dev/null
-ul
- li!= self.one
- li!= self.two
- li!= self.three
- - each item in self.items
- li!= item
+++ /dev/null
-<ul>
- <li><%- one %></li>
- <li><%- two %></li>
- <li><%- three %></li>
- <% items.forEach(function(item){ %>
- <li><%- item %></li>
- <% }) %>
-</ul>
\ No newline at end of file
+++ /dev/null
-%ul
- %li= one
- %li= two
- %li= three
- :each item in items
- %li= item
\ No newline at end of file
+++ /dev/null
-ul
- li!= one
- li!= two
- li!= three
- - each item in items
- li!= item
\ No newline at end of file
+++ /dev/null
-%ul
- %li!= one
- %li!= two
- %li!= three
- - each item in items
- %li!= item
\ No newline at end of file
+++ /dev/null
-# HAML-JS Changelog
-
-- **v0.2.5** - *2010-05-06* - NPM support
-
- Fixed to work with Node Package Manager
-
-- **v0.2.4** - *2010-04-16* - Bug fixes, XML support
-
- Allow for commas in calls to helpers in attributes. Also make haml more XML friendly.
-
-- **v0.2.3** - *2010-04-10* - Bug fixes
-
- Fixed an issue where "content" html attributes got munched. (This broke meta tags)
-
-- **v0.2.2** - *2010-04-05* - Bug fixes
-
- Fixed two issues where the parser incorrectly parsed blank lines and extra spaces in attribute blocks.
-
-- **v0.2.1** - *2010-04-01* - Minor speed tweak
-
- `Haml()` now caches the eval step so that there is no eval in executing a compiled template. This should make things a bit faster.
-
-- **v0.2.0** - *2010-03-31* - Function based API, Safe whitespace, Code interpolation.
-
- At the request of some users, I've removed the new insertion into the generated html. This means that most html will be on one long line, but as an added advantage you won't have that extra whitespace next to your anchor labels messing up your visual display.
-
- Also I added string interpolation to every place I could fit it. This means you can do crazy stuff like interpolate within strings in attributes, in the body on plain text sections, and of course in javascript and css plugin blocks.
-
- In order to tame the API, I deprecated the four old interfaces `compile`, `optimize`, `execute` and `render`. The new API is that the Haml/exports object itself is now a function that takes in haml text and outputs a compiled, optimized, ready to execute function.
-
-- **0.1.2** - *2010-02-03* - Bug fixes, plugin aliases, CommonJS, and more...
-
- This is a big release with many improvements. First haml-js is now a CommonJS module and is in the Tusk repository. Thanks to Tom Robinson for helping with that. Some of the plugins got aliases for people who didn't like the original name. For example, you can now do `:javascript` instead of `:script` and `:for` instead of `:each`. There were many bug fixes now that the code is starting to be actually used by myself and others.
-
-- **0.1.1** - *2010-01-09* - Add :css and :script plugins
-
- Added two quick plugins that make working with javascript and css much easier.
-
- - **0.1.0** - *2010-01-09* - Complete Rewrite
-
- Rewrote the compiler to be recursive and compile to JavaScript code instead of JSON data structures. This fixes all the outstanding bugs and simplifies the code. Pending is restoring the `:script` and `:css` plugins.
-
- - **0.0.1** - *2009-12-16* - Initial release
-
- Change how haml is packaged. It is a pure JS function with no node dependencies. There is an exports hook for commonjs usability. It's now the responsibility of the script user to acquire the haml text.
-
-
+++ /dev/null
-Copyright (c) 2009 Tim Caswell
-
-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.
+++ /dev/null
-# haml-js - Server side templating language for JavaScript
-
-Ever wanted to use the excellent HAML syntax on a javascript project? Me too, so I made one!. This has most of the same functionality as the traditional [haml][].
-
-## About the language
-
-Here is the first example(with a little extra added) from the [haml][] site converted to haml-js:
-
-**haml-js**
-
- !!! XML
- !!! strict
- %html{ xmlns: "http://www.w3.org/1999/xhtml" }
- %head
- %title Sample haml template
- %body
- .profile
- .left.column
- #date= print_date()
- #address= current_user.address
- .right.column
- #email= current_user.email
- #bio= current_user.bio
-
-**html**
-
- <?xml version='1.0' encoding='utf-8' ?>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml"><head><title>Sample haml template
- </title></head><body><div class="profile"><div class="left column"><div id="date">January 1, 2009
- </div><div id="address">Richardson, TX
- </div></div><div class="right column"><div id="email">tim@creationix.com
- </div><div id="bio">Experienced software professional...
- </div></div></div></body></html>
-
-Note that this works almost the same as ruby's [haml][], but doesn't pretty print the html. This would greatly slow down and complicate the code. If you really want pretty printed html, then I suggest writing one using the xml parser library and process the resulting html..
-
-## API
-
-### Haml(haml) -> template(locals) -> html
-
-This is the new (as of 0.2.0) way to generate haml templates. A haml template is a live function that takes in "this" context and a "locals" variable. This compile step takes a few milliseconds to complete so it should be done at startup and the resulting function should be cached. Then to use the template function you simply call it with the desired local variables and it will output html at blazing speeds (we're talking millions per second on my 13" MBP)
-
-Compile and store a template:
-
- var main = Haml(main_haml);
-
-Then use it whenever you need a new version:
-
- main({name: "Tim", age: 28});
-
-That's it. Haml templating made easy!
-
-If you want to store the generated javascript to a file to skip the compile step later on you can either decompile the template function or use the `compile` and `optimize` advanced functions directly.
-
-
-### Haml.compile(text) -> JavaScript compiled template
-
-Given a haml template as raw text, this compiles it to a javascript expression
-that can later be eval'ed to get the final HTML.
-
-The following input:
-
- #home
- = title
- %ul.menu
- %li Go Home
- %li Go Back
-
-Produces the following JavaScript expression:
-
- "<div id=\"home\">" +
- title +
- "\n" +
- "<ul class=\"menu\">" +
- "<li>" +
- "Go Home\n" +
- "</li>" +
- "<li>" +
- "Go Back\n" +
- "</li>" +
- "</ul>" +
- "</div>"
-
-### Haml.optimize(js) -> optimized JavaScript expression
-
-Takes the output of compile and optimizes it to run faster with the tradeoff of longer compile time. This is useful for framework developers wanting to use haml in their framework and want to cache the compiled templates for performance.
-
-With the previous input it outputs:
-
- "<div id=\"home\">" +
- title +
- "\n<ul class=\"menu\"><li>Go Home\n</li><li>Go Back\n</li></ul></div>"
-
-Notice how congruent static strings are merged into a single string literal when possible.
-
-### Haml.execute(js, context, locals) -> Executes a compiles template
-
-Context is the value of `this` in the template, and locals is a hash of local variables.
-
-### Haml.render(text, options) -> html text
-
-This is a convenience function that compiles and executes to html in one shot. Most casual users will want to use this function exclusively.
-
-The `text` parameter is the haml source already read from a file.
-
-The three recognized `options` are:
-
- - **context**: This is the `this` context within the haml template.
- - **locals**: This is an object that's used in the `with` scope. Basically it creates local variables and function accessible to the haml template.
- - **optimize**: This is a flag to tell the compiler to use the extra optimizations.
-
-See [test.js][] for an example usage of Haml.render
-
-## Code interpolation
-
-New in version 0.2.0 there is string interpolation throughout. This means that the body of regular text areas can have embedded code. This is true for attributes and the contents of plugins like javascript and markdown also. If you notice an area that doesn't support interpolation and it should then send me a note and I'll add it.
-
-## Plugins
-
-There are plugins in the parser for things like inline script tags, css blocks, and support for if statements and for loops.
-
-### `:if` statements
-
-`if` statements evaluate a condition for truthiness (as opposed to a strict comparison to `true`) and includes the content inside the block if it's truthy.
-
- :if todolist.length > 20
- %p Oh my, you are a busy fellow!
-
-### `:each` loops
-
-`:each` loops allow you to loop over a collection including a block of content once for each item. You need to what variable to pull the data from and where to put the index and value. The index variable is optional and defaults to `__key__`.
-
-Here is an example over a simple array.
-
- %ul.todolist
- :each item in todolist
- %li= item.description
-
-You can loop over the keys and values of objects too (Note the inner `:each` loop)
-
- :each item in data
- :if item.age < 100
- %dl
- :each name, value in item
- %dt&= name
- %dd&= value
-
-### `:css` and `:javascript` helpers.
-
-It's easy to embed javascript and css blocks in an haml document.
-
- %head
- :javascript
- function greet(message) {
- alert("Message from MCP: " + message);
- }
- %title Script and Css test
- :css
- body {
- color: pink;
- }
- %body{ onload: "greet(\"I'm Pink\")" } COLOR ME PINK
-
-This compiles to the following HTML:
-
- <head>
- <script type="text/javascript">
- //<![CDATA[
- function greet(message) {
- alert("Message from MCP: " + message);
- }
- //]]>
- </script>
- <title>Script and Css test
- </title>
- <style type="text/css">
- body {
- color: pink;
- }
- </style>
- </head><body onload="greet("I'm Pink")"> COLOR ME PINK
- </body>
-
-## Get Involved
-
-If you want to use this project and something is missing then send me a message. I'm very busy and have several open source projects I manage. I'll contribute to this project as I have time, but if there is more interest for some particular aspect, I'll work on it a lot faster. Also you're welcome to fork this project and send me patches/pull-requests.
-
-## About Performance
-
-The haml compiler isn't built for speed, it's built for maintainability. The actual generated templates, however are blazing fast. I benchmarked them with over 65 million renders per second on a small (20 line) template with some dynamic data on my laptop. Compare this to the 629 compiles per second I got out of the compiler. The idea is that you pre-compile your templates and reuse them on every request. While 629 per second is nothing compared to 65 million, that still means that your server with over 600 different views can boot up in about a second. I think that's fine for something that only happens every few weeks.
-
-## License
-
-Haml-js is [licensed][] under the [MIT license][].
-
-[MIT license]: http://creativecommons.org/licenses/MIT/
-[licensed]: http://github.com/creationix/haml-js/blob/master/LICENSE
-[jquery-haml]: http://github.com/creationix/jquery-haml
-[haml]: http://haml-lang.com/
-[test.js]: http://github.com/creationix/haml-js/blob/master/test/test.js
-
-
-
-
+++ /dev/null
-var Haml;
-
-(function () {
-
- var matchers, self_close_tags, embedder, forceXML;
-
- function html_escape(text) {
- return (text + "").
- replace(/&/g, "&").
- replace(/</g, "<").
- replace(/>/g, ">").
- replace(/\"/g, """);
- }
-
- function render_attribs(attribs) {
- var key, value, result = [];
- for (key in attribs) {
- if (key !== '_content' && attribs.hasOwnProperty(key)) {
- switch (attribs[key]) {
- case 'undefined':
- case 'false':
- case 'null':
- case '""':
- break;
- default:
- try {
- value = JSON.parse("[" + attribs[key] +"]")[0];
- if (value === true) {
- value = key;
- } else if (typeof value === 'string' && embedder.test(value)) {
- value = '" +\n' + parse_interpol(html_escape(value)) + ' +\n"';
- } else {
- value = html_escape(value);
- }
- result.push(" " + key + '=\\"' + value + '\\"');
- } catch (e) {
- result.push(" " + key + '=\\"" + html_escape(' + attribs[key] + ') + "\\"');
- }
- }
- }
- }
- return result.join("");
- }
-
- // Parse the attribute block using a state machine
- function parse_attribs(line) {
- var attributes = {},
- l = line.length,
- i, c,
- count = 1,
- quote = false,
- skip = false,
- open, close, joiner, seperator,
- pair = {
- start: 1,
- middle: null,
- end: null
- };
-
- if (!(l > 0 && (line.charAt(0) === '{' || line.charAt(0) === '('))) {
- return {
- _content: line[0] === ' ' ? line.substr(1, l) : line
- };
- }
- open = line.charAt(0);
- close = (open === '{') ? '}' : ')';
- joiner = (open === '{') ? ':' : '=';
- seperator = (open === '{') ? ',' : ' ';
-
- function process_pair() {
- if (typeof pair.start === 'number' &&
- typeof pair.middle === 'number' &&
- typeof pair.end === 'number') {
- var key = line.substr(pair.start, pair.middle - pair.start).trim(),
- value = line.substr(pair.middle + 1, pair.end - pair.middle - 1).trim();
- attributes[key] = value;
- }
- pair = {
- start: null,
- middle: null,
- end: null
- };
- }
-
- for (i = 1; count > 0; i += 1) {
-
- // If we reach the end of the line, then there is a problem
- if (i > l) {
- throw "Malformed attribute block";
- }
-
- c = line.charAt(i);
- if (skip) {
- skip = false;
- } else {
- if (quote) {
- if (c === '\\') {
- skip = true;
- }
- if (c === quote) {
- quote = false;
- }
- } else {
- if (c === '"' || c === "'") {
- quote = c;
- }
-
- if (count === 1) {
- if (c === joiner) {
- pair.middle = i;
- }
- if (c === seperator || c === close) {
- pair.end = i;
- process_pair();
- if (c === seperator) {
- pair.start = i + 1;
- }
- }
- }
-
- if (c === open || c === "(") {
- count += 1;
- }
- if (c === close || (count > 1 && c === ")")) {
- count -= 1;
- }
- }
- }
- }
- attributes._content = line.substr(i, line.length);
- return attributes;
- }
-
- // Split interpolated strings into an array of literals and code fragments.
- function parse_interpol(value) {
- var items = [],
- pos = 0,
- next = 0,
- match;
- while (true) {
- // Match up to embedded string
- next = value.substr(pos).search(embedder);
- if (next < 0) {
- if (pos < value.length) {
- items.push(JSON.stringify(value.substr(pos)));
- }
- break;
- }
- items.push(JSON.stringify(value.substr(pos, next)));
- pos += next;
-
- // Match embedded string
- match = value.substr(pos).match(embedder);
- next = match[0].length;
- if (next < 0) { break; }
- items.push(match[1] || match[2]);
- pos += next;
- }
- return items.filter(function (part) { return part && part.length > 0}).join(" +\n");
- }
-
- // Used to find embedded code in interpolated strings.
- embedder = /\#\{([^}]*)\}/;
-
- self_close_tags = ["meta", "img", "link", "br", "hr", "input", "area", "base"];
-
- // All matchers' regexps should capture leading whitespace in first capture
- // and trailing content in last capture
- matchers = [
- // html tags
- {
- regexp: /^(\s*)((?:[.#%][a-z_\-][a-z0-9_:\-]*)+)(.*)$/i,
- process: function () {
- var tag, classes, ids, attribs, content;
- tag = this.matches[2];
- classes = tag.match(/\.([a-z_\-][a-z0-9_\-]*)/gi);
- ids = tag.match(/\#([a-z_\-][a-z0-9_\-]*)/gi);
- tag = tag.match(/\%([a-z_\-][a-z0-9_:\-]*)/gi);
-
- // Default to <div> tag
- tag = tag ? tag[0].substr(1, tag[0].length) : 'div';
-
- attribs = this.matches[3];
- if (attribs) {
- attribs = parse_attribs(attribs);
- if (attribs._content) {
- this.contents.unshift(attribs._content.trim());
- delete(attribs._content);
- }
- } else {
- attribs = {};
- }
-
- if (classes) {
- classes = classes.map(function (klass) {
- return klass.substr(1, klass.length);
- }).join(' ');
- if (attribs['class']) {
- try {
- attribs['class'] = JSON.stringify(classes + " " + JSON.parse(attribs['class']));
- } catch (e) {
- attribs['class'] = JSON.stringify(classes + " ") + " + " + attribs['class'];
- }
- } else {
- attribs['class'] = JSON.stringify(classes);
- }
- }
- if (ids) {
- ids = ids.map(function (id) {
- return id.substr(1, id.length);
- }).join(' ');
- if (attribs.id) {
- attribs.id = JSON.stringify(ids + " ") + attribs.id;
- } else {
- attribs.id = JSON.stringify(ids);
- }
- }
-
- attribs = render_attribs(attribs);
-
- content = this.render_contents();
- if (content === '""') {
- content = '';
- }
-
- if (forceXML ? content.length > 0 : self_close_tags.indexOf(tag) == -1) {
- return '"<' + tag + attribs + '>"' +
- (content.length > 0 ? ' + \n' + content : "") +
- ' + \n"</' + tag + '>"';
- } else {
- return '"<' + tag + attribs + ' />"';
- }
- }
- },
-
- // each loops
- {
- regexp: /^(\s*)(?::for|:each)\s+(?:([a-z_][a-z_\-]*),\s*)?([a-z_][a-z_\-]*)\s+in\s+(.*)(\s*)$/i,
- process: function () {
- var ivar = this.matches[2] || '__key__', // index
- vvar = this.matches[3], // value
- avar = this.matches[4], // array
- rvar = '__result__'; // results
-
- if (this.matches[5]) {
- this.contents.unshift(this.matches[5]);
- }
- return '(function () { ' +
- 'var ' + rvar + ' = [], ' + ivar + ', ' + vvar + '; ' +
- 'for (' + ivar + ' in ' + avar + ') { ' +
- 'if (' + avar + '.hasOwnProperty(' + ivar + ')) { ' +
- vvar + ' = ' + avar + '[' + ivar + ']; ' +
- rvar + '.push(\n' + (this.render_contents() || "''") + '\n); ' +
- '} } return ' + rvar + '.join(""); }).call(this)';
- }
- },
-
- // if statements
- {
- regexp: /^(\s*):if\s+(.*)\s*$/i,
- process: function () {
- var condition = this.matches[2];
- return '(function () { ' +
- 'if (' + condition + ') { ' +
- 'return (\n' + (this.render_contents() || '') + '\n);' +
- '} else { return ""; } }).call(this)';
- }
- },
-
- // declarations
- {
- regexp: /^()!!!(?:\s*(.*))\s*$/,
- process: function () {
- var line = '';
- switch ((this.matches[2] || '').toLowerCase()) {
- case '':
- // XHTML 1.0 Transitional
- line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
- break;
- case 'strict':
- case '1.0':
- // XHTML 1.0 Strict
- line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
- break;
- case 'frameset':
- // XHTML 1.0 Frameset
- line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
- break;
- case '5':
- // XHTML 5
- line = '<!DOCTYPE html>';
- break;
- case '1.1':
- // XHTML 1.1
- line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
- break;
- case 'basic':
- // XHTML Basic 1.1
- line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">';
- break;
- case 'mobile':
- // XHTML Mobile 1.2
- line = '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">';
- break;
- case 'xml':
- // XML
- line = "<?xml version='1.0' encoding='utf-8' ?>";
- break;
- case 'xml iso-8859-1':
- // XML iso-8859-1
- line = "<?xml version='1.0' encoding='iso-8859-1' ?>";
- break;
- }
- return JSON.stringify(line + "\n");
- }
- },
-
- // Embedded markdown. Needs to be added to exports externally.
- {
- regexp: /^(\s*):markdown\s*$/i,
- process: function () {
- return parse_interpol(exports.Markdown.encode(this.contents.join("\n")));
- }
- },
-
- // script blocks
- {
- regexp: /^(\s*):(?:java)?script\s*$/,
- process: function () {
- return parse_interpol('\n<script type="text/javascript">\n' +
- '//<![CDATA[\n' +
- this.contents.join("\n") +
- "\n//]]>\n</script>\n");
- }
- },
-
- // css blocks
- {
- regexp: /^(\s*):css\s*$/,
- process: function () {
- return JSON.stringify('\n<style type="text/css">\n' +
- this.contents.join("\n") +
- "\n</style>\n");
- }
- },
-
- ];
-
- function compile(lines) {
- var block = false,
- output = [];
-
- // If lines is a string, turn it into an array
- if (typeof lines === 'string') {
- lines = lines.trim().split("\n");
- }
-
- lines.forEach(function(line) {
- var match, found = false;
-
- // Collect all text as raw until outdent
- if (block) {
- match = block.check_indent(line);
- if (match) {
- block.contents.push(match[1] || "");
- return;
- } else {
- output.push(block.process());
- block = false;
- }
- }
-
- matchers.forEach(function (matcher) {
- if (!found) {
- match = matcher.regexp(line);
- if (match) {
- block = {
- contents: [],
- matches: match,
- check_indent: new RegExp("^(?:\\s*|" + match[1] + " (.*))$"),
- process: matcher.process,
- render_contents: function () {
- return compile(this. contents);
- }
- };
- found = true;
- }
- }
- });
-
- // Match plain text
- if (!found) {
- output.push(function () {
- // Escaped plain text
- if (line[0] === '\\') {
- return parse_interpol(line.substr(1, line.length));
- }
-
- // Plain variable data
- if (line[0] === '=') {
- line = line.substr(1, line.length).trim();
- try {
- return parse_interpol(JSON.parse(line));
- } catch (e) {
- return line;
- }
- }
-
- // HTML escape variable data
- if (line.substr(0, 2) === "&=") {
- line = line.substr(2, line.length).trim();
- try {
- return JSON.stringify(html_escape(JSON.parse(line)));
- } catch (e2) {
- return 'html_escape(' + line + ')';
- }
- }
-
- // Plain text
- return parse_interpol(line);
- }());
- }
-
- });
- if (block) {
- output.push(block.process());
- }
- return output.filter(function (part) { return part && part.length > 0}).join(" +\n");
- };
-
- function optimize(js) {
- var new_js = [], buffer = [], part, end;
-
- function flush() {
- if (buffer.length > 0) {
- new_js.push(JSON.stringify(buffer.join("")) + end);
- buffer = [];
- }
- }
- js.split("\n").forEach(function (line) {
- part = line.match(/^(\".*\")(\s*\+\s*)?$/);
- if (!part) {
- flush();
- new_js.push(line);
- return;
- }
- end = part[2] || "";
- part = part[1];
- try {
- buffer.push(JSON.parse(part));
- } catch (e) {
- flush();
- new_js.push(line);
- }
- });
- flush();
- return new_js.join("\n");
- };
-
- function render(text, options) {
- options = options || {};
- text = text || "";
- var js = compile(text);
- if (options.optimize) {
- js = Haml.optimize(js);
- }
- return execute(js, options.context || Haml, options.locals);
- };
-
- function execute(js, self, locals) {
- return (function () {
- with(locals || {}) {
- try {
- return eval("(" + js + ")");
- } catch (e) {
- return "\n<pre class='error'>" + html_escape(e.stack) + "</pre>\n";
- }
-
- }
- }).call(self);
- };
-
- Haml = function Haml(haml, xml) {
- forceXML = xml;
- var js = optimize(compile(haml));
- return new Function("locals",
- html_escape + "\n" +
- "with(locals || {}) {\n" +
- " try {\n" +
- " return (" + js + ");\n" +
- " } catch (e) {\n" +
- " return \"\\n<pre class='error'>\" + html_escape(e.stack) + \"</pre>\\n\";\n" +
- " }\n" +
- "}");
- }
- Haml.compile = compile;
- Haml.optimize = optimize;
- Haml.render = render;
- Haml.execute = execute;
-
-}());
-
-// Hook into module system
-if (typeof module !== 'undefined') {
- module.exports = Haml;
-}
+++ /dev/null
-{
- "name": "haml",
- "description": "Haml ported to server-side Javascript. This is a traditional server-side templating language.",
- "keywords": ["haml", "template"],
- "main" : "./lib/haml",
- "author": "Tim Caswell <tim@creationix.com>",
- "version": "0.2.5"
-}
\ No newline at end of file
+++ /dev/null
-%tag(name="value" name2=true ns:tag=100)
-%input#space-end(type="hidden" value="3" )
-%input#space-start( type="hidden" value="3" )
-%input#space-middle(type="hidden" value="3")
+++ /dev/null
-<tag name="value" name2="name2" ns:tag="100"></tag><input type="hidden" value="3" id="space-end" /><input type="hidden" value="3" id="space-start" /><input type="hidden" value="3" id="space-middle" />
\ No newline at end of file
+++ /dev/null
-%div
- Does not close properly
- %div Nested same level as next div
-%div
- Will be nested, but should be top level
\ No newline at end of file
+++ /dev/null
-<div>Does not close properly<div>Nested same level as next div</div></div><div>Will be nested, but should be top level</div>
\ No newline at end of file
+++ /dev/null
-!!!
-!!! strict
-!!! 1.1
-!!! 5
-!!! xml
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<!DOCTYPE html>
-<?xml version='1.0' encoding='utf-8' ?>
+++ /dev/null
-%head
- :javascript
- Page.chapter = #{JSON.stringify(chapter)};
-%body
- %h1 Welcome #{name}
- %div{class: "div_#{id}"}
+++ /dev/null
-<head>
-<script type="text/javascript">
-//<![CDATA[
-Page.chapter = {"name":"Ninja","page":42};
-//]]>
-</script>
-</head><body><h1>Welcome Tim</h1><div class="div_42"></div></body>
\ No newline at end of file
+++ /dev/null
-{
- locals: {
- chapter: {name: "Ninja", page: 42},
- name: "Tim",
- id: 42
- }
-}
\ No newline at end of file
+++ /dev/null
-:each color in colors
- .preview{style: "color: " + color + ";"}&= name
-:each item in data
- :if item.age < 100
- %dl
- :each name, value in item
- %dt&= name
- %dd&= value
-:each number in [1,2,3,4,5,6,7]
- = number
-:for word in "Hello World".split(" ")
- = word
\ No newline at end of file
+++ /dev/null
-<div style="color: #f80;" class="preview">My Rainbow</div><div style="color: #08f;" class="preview">My Rainbow</div><div style="color: #4f4;" class="preview">My Rainbow</div><dl><dt>name</dt><dd>Tim Caswell</dd><dt>age</dt><dd>27</dd></dl>1234567HelloWorld
\ No newline at end of file
+++ /dev/null
-{
- locals: {
- colors: ["#f80", "#08f", "#4f4"],
- name: "My Rainbow",
- data: [
- {name: "Tim Caswell", age: 27},
- {name: "John Smith", age: 107},
- ]
- }
-}
\ No newline at end of file
+++ /dev/null
-%meta(http-equiv="content-type" content="text/html; charset=UTF-8")
+++ /dev/null
-<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
\ No newline at end of file
+++ /dev/null
-!!! 5
-%html
- %head
- %title atomix
-
- %script(src='atomix_xlib.js')
+++ /dev/null
-<!DOCTYPE html>
-<html><head><title>atomix</title><script src="atomix_xlib.js"></script></head></html>
\ No newline at end of file
+++ /dev/null
-%p= this.name
-#main
- :each item in items
- .item= this.name + ": " + item
- :if items
- #cool= this.name
\ No newline at end of file
+++ /dev/null
-<p>Frank</p><div id="main"><div class="item">Frank: 1</div><div class="item">Frank: 2</div><div class="item">Frank: 3</div><div id="cool">Frank</div></div>
\ No newline at end of file
+++ /dev/null
-{
- context: {
- name: "Frank"
- },
- locals: {
- items: [1,2,3]
- }
-}
\ No newline at end of file
+++ /dev/null
-%html
- %body
- %div#a
- %div I do not self close.
- :javascript
- (function(){
- document.getElementById('a').textContent='I self close';
- })();
+++ /dev/null
-<html><body><div id="a"></div><div>I do not self close.</div>
-<script type="text/javascript">
-//<![CDATA[
-(function(){
- document.getElementById('a').textContent='I self close';
-})();
-//]]>
-</script>
-</body></html>
\ No newline at end of file
+++ /dev/null
-#plain= "Plain Text"
-#escaped&= "<escaped>"
-%input{checked: true}
-%input{checked: false}
-%input{checked: null}
-%input{checked: undefined}
-%input{checked: 0}
-%input{checked: ""}
\ No newline at end of file
+++ /dev/null
-<div id="plain">Plain Text</div><div id="escaped"><escaped></div><input checked="checked" /><input /><input /><input /><input checked="0" /><input />
\ No newline at end of file
+++ /dev/null
-%head
- :javascript
- function greet(message) {
- alert("Message from MCP: " + message);
- }
- %title Script and Css test
- :css
- body {
- color: pink;
- }
-%body{onload: "greet(\"I'm Pink\")"} COLOR ME PINK
-
+++ /dev/null
-<head>
-<script type="text/javascript">
-//<![CDATA[
-function greet(message) {
- alert("Message from MCP: " + message);
-}
-//]]>
-</script>
-<title>Script and Css test</title>
-<style type="text/css">
-body {
- color: pink;
-}
-</style>
-</head><body onload="greet("I'm Pink")">COLOR ME PINK</body>
\ No newline at end of file
+++ /dev/null
-%html
- %head
- :if url !='/'
- %script
- %meta{name: "test", value:"Monkey"}
- %body
- %a{ href: url }
- link
\ No newline at end of file
+++ /dev/null
-<html><head><script></script><meta name="test" value="Monkey" /></head><body><a href="http://nodejs.org/">link</a></body></html>
\ No newline at end of file
+++ /dev/null
-{
- locals: {
- url: "http://nodejs.org/"
- }
-}
\ No newline at end of file
+++ /dev/null
-!!! XML
-!!! strict
-%html{ xmlns: "http://www.w3.org/1999/xhtml" }
- %head
- %title
- Sample haml template
- %body
- .profile
- .left.column
- #date= print_date()
- #address= current_user.address
- .right.column
- #email= current_user.email
- #bio= current_user.bio
\ No newline at end of file
+++ /dev/null
-<?xml version='1.0' encoding='utf-8' ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Sample haml template</title></head><body><div class="profile"><div class="left column"><div id="date">January 1, 2009</div><div id="address">Richardson, TX</div></div><div class="right column"><div id="email">tim@creationix.com</div><div id="bio">Experienced software professional...</div></div></div></body></html>
\ No newline at end of file
+++ /dev/null
-{
- locals: {
- print_date: function () {
- return 'January 1, 2009';
- },
- current_user: {
- address: "Richardson, TX",
- email: "tim@creationix.com",
- bio: "Experienced software professional..."
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-var FILE = require("file");
-var ASSERT = require("assert");
-
-var Haml = require("../lib/haml");
-
-FILE.glob("test/*.haml").forEach(function(hamlFile) {
- exports["test " + hamlFile] = function() {
- var scopeFile = hamlFile.replace(/haml$/, "js");
- var htmlFile = hamlFile.replace(/haml$/, "html");
-
- var haml = FILE.read(hamlFile);
- var expected = FILE.read(htmlFile);
- var scope = FILE.exists(scopeFile) ? eval("("+FILE.read(scopeFile)+")") : {};
-
- var js = Haml.compile(haml);
- var js_opt = Haml.optimize(js);
- var actual = Haml.execute(js_opt, scope.context, scope.locals);
- ASSERT.equal(actual.trim(), expected.trim());
- }
-});
-
-if (module == require.main)
- require("os").exit(require("test").run(exports));
+++ /dev/null
-var fs = require('fs');
-var assert = require('assert');
-var sys = require('sys');
-
-var Haml = require("../lib/haml");
-
-fs.readdir('.', function (err, files) {
- files.forEach(function (haml_file) {
- var m = haml_file.match(/^(.*)\.haml/),
- base;
- if (!m) {
- return;
- }
- base = m[1];
-
- function load_haml(scope) {
- fs.readFile(haml_file, "utf8", function (err, haml) {
- fs.readFile(base + ".html", "utf8", function (err, expected) {
- try {
- var js = Haml.compile(haml);
- var js_opt = Haml.optimize(js);
- var actual = Haml(haml).call(scope.context, scope.locals);
- assert.equal(actual, expected);
-
- sys.puts(haml_file + " Passed")
- } catch (e) {
- var message = e.name;
- if (e.message) { message += ": " + e.message; }
- sys.error(haml_file + " FAILED")
- sys.error(message);
- sys.error("\nJS:\n\n" + js);
- sys.error("\nOptimized JS:\n\n" + js_opt);
- sys.error("\nActual:\n\n" + actual);
- sys.error("\nExpected:\n\n" + expected);
- process.exit();
- }
- });
- });
- }
-
- // Load scope
- if (files.indexOf(base + ".js") >= 0) {
- fs.readFile(base + ".js", "utf8", function (err, js) {
- load_haml(eval("(" + js + ")"));
- });
- } else {
- load_haml({});
- }
- });
-});
-
-
+++ /dev/null
-
-/**
- * Module dependencies.
- */
-
-var bm = require('./common'),
- haml = require('./haml-js/lib/haml'),
- fs = require('fs');
-
-var str = fs.readFileSync(__dirname + '/example.haml', 'ascii');
-
-var n = bm.times;
-bm.start('haml-js compilation');
-while (n--) {
- haml(str);
-}
-bm.stop();
-
-var n = bm.times;
-var fn = haml(str);
-bm.start('haml-js execution');
-while (n--) {
- fn.call('whatever scope', bm.locals);
-}
-bm.stop();
\ No newline at end of file
+++ /dev/null
-[submodule "benchmarks/haml-js"]
- path = benchmarks/haml-js
- url = git://github.com/creationix/haml-js.git
+++ /dev/null
-benchmarks
\ No newline at end of file
+++ /dev/null
-
-0.4.5 / 2010-06-04
-==================
-
- * Ignoring stray indent
-
-0.4.4 / 2010-05-27
-==================
-
- * Fixed arbitrary whitespace support
-
-0.4.3 / 2010-05-25
-==================
-
- * Fixed support for CRLF / CR line-endings, converted on input
- * Exporting HamlError
-
-0.4.2 / 2010-05-25
-==================
-
- * Added HamlError
- * Buffer newline indentation. Closes #23
- * Benchmarks with node-bench
-
-0.4.1 / 2010-05-17
-==================
-
- * Fixed "- each" with non-enumerables, no longer throws exception
- * Fixed array iteration
-
-0.4.0 / 2010-05-06
-==================
-
- * Using Function constructor instead of eval()
- * Performance enhanced by faster iteration implementation for "- each"
-
-0.3.1 / 2010-04-26
-==================
-
- * Fixed support for tags with hypens (both namespace / tag name, ex: "fb:login-button")
-
-0.3.0 / 2010-04-16
-==================
-
- * Added xml namespace support
- * Added xml support
-
-0.2.0 / 2010-04-06
-==================
-
- * Added conditional comment support [ciaranj]
- * Fixed; Trimming input string before tokenization
- * Fixed issue requiring quoting of "for" when used in attrs [aheckmann]
- * Fixed; Exposing Haml compilation cache. Closes #12
- * Fixed :javascript "type" attr, now "text/javascript" [aheckmann]
-
-0.1.0 / 2010-03-31
-==================
-
- * Added "cache" option, making haml.js over 90 times faster than haml-js
- * Improved textBlock whitespace replication
- * Fixed empty tags followed by class / ids on new lines. Closes #6
-
-0.0.4 / 2010-03-29
-==================
-
- * Added better error reporting
-
-0.0.3 / 2010-03-29
-==================
-
- * Added "filename" option support to aid in error reporting
- * Added exports.compile() to create intermediate javascript
- * Added `make benchmark`
- * Changed; caching function templates to increase performance
- * Fixed; ids and classes allowing underscores. Closes #5
- * Fixed outdent issue when \n is not followed by whitespace. Closes #8
-
-0.0.2 / 2010-03-26
-==================
-
- * Added haml.js vs haml-js benchmarks
- * Fixed; commenting :javascript CDATA
-
-0.0.1 / 2010-03-26
-==================
-
- * Initial release
+++ /dev/null
-
-test:
- @node spec/node.js
-
-benchmark:
- @node-bench benchmarks/run.js
-
-.PHONY: test benchmark
\ No newline at end of file
+++ /dev/null
-
-# Haml.js
-
- High performance JavaScript [Haml](http://haml-lang.com) implementation for [nodejs](http://nodejs.org)
-
- For a higher quality implementation you may want to look at my [Jade](http://jade-lang.com) template engine,
- however the syntax is slightly different. Jade's engine may be back-ported to haml.js in the future.
-
-## Installation
-
- Install the [Kiwi package manager for nodejs](http://github.com/visionmedia/kiwi)
- and run:
-
- $ kiwi install haml
-
- node> require('haml')
-
-Or npm:
-
- $ npm install hamljs
-
- node> require('hamljs')
-
-## About
-
- Benchmarks rendering the same 21 line haml file located at _benchmarks/page.haml_,
- shows that this library is nearly **65%** or **3 times** faster than haml-js.
-
- Winner: haml.js
- Compared with next highest (haml-js), it's:
- 65.39% faster
- 2.89 times as fast
- 0 order(s) of magnitude faster
-
- Haml.js attempts to comply with the original [Haml](http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html)
- implementation as well as possible. There are no magic "plugins" like
- found in other JavaScript haml implementations, for example the following
- will work just fine:
-
- - if (items)
- %ul
- - for (var i = 0; i < items.length; ++i)
- %li= items[i]
-
- Iteration is the one exception to these magical plugins,
- since this is **ugly** in JavaScript, you may also:
-
- - if (items)
- %ul
- - each item in items
- %li= item
-
-## Usage
-
- var haml = require('haml')
- haml.render('a string of haml', { a: 'hash', of: 'options' })
-
-## Options
-
- * context
- - when passed the value of "this" becomes the given "context" object
- * locals
- - when passed all members of this object become available to this template
- * filename
- - required when _cache_ is enabled
- * cache
- - compiled intermediate javascript is cached in memory keyed by _filename_
-
-## Tags
-
- %div text
-
-html:
-
- <div>text</div>
-
-## Classes
-
- %div.article.first
- article text here
- and here
-
-html:
-
- <div class="article first">
- article text here and here
- </div>
-
-## Div Class Shortcut
-
- .comment hey
-
-html:
-
- <div class="comment">hey</div>
-
-## Div Id Shortcut
-
- #article-1 foo
-
-html:
-
- <div id="article-1">foo</div>
-
-## Combining Ids and Classes
-
-You may chain id and classes in any order:
-
- .article#first.summary content
-
-html:
-
- <div id="first" class="article summary">context</div>
-
-## Attributes
-
- %a{ href: 'http://google.com', title: 'Google It' } Google
-
-html:
-
- <a href="http://google.com" title="Google It">Google</a>
-
-Attribute keys such as "for" are automatically quoted
-by haml.js, so instead of:
-
- %label{ 'for': 'something' }
-
-you should:
-
- %label{ for: 'something' }
-
-which will render:
-
- <label for="something"></label>
-
-## Boolean Attributes
-
- %input{ type: 'checkbox', checked: true }
-
-html:
-
- <input type="checkbox" checked="checked"/>
-
-## Combining Attributes, Ids, and Classes
-
-Wemay also contain id and classes before or after:
-
- %a.button{ href: 'http://google.com', title: 'Google It' }.first Google
-
-html:
-
- <a href="http://google.com" title="Google It" class="button first">Google</a>
-
-## Code
-
-Code starting with a hyphen will be executed but
-not buffered, where as code using the equals sign
-will be buffered:
-
- - a = 1
- - b = 2
- = a + b
-
-html:
-
- 3
-
-HTML buffered with equals sign will **always** be escaped:
-
- = "<br/>"
-
-html:
-
- <br/>
-
-To prevent escaping of HTML entities we can use _!=_:
-
- != "<br/>"
-
-html:
-
- <br/>
-
-## Iteration
-
- %ul
- - each item in items
- %li= item
-
-html:
-
- <ul>
- <li>one</li>
- <li>two</li>
- <li>three</li>
- </ul>
-
-If you require the key or index of the object
-or array during iteration simple append a comma
-following another id:
-
- %ul
- - each item, index in items
- %li= item + '(' + index + ')'
-
-html:
-
- <ul>
- <li>one(0)</li>
- <li>two(1)</li>
- <li>three(2)</li>
- </ul>
-
-## Doctypes
-
-Defaults to transitional:
-
- !!!
-
-html:
-
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-Optionally pass a supported doctype name:
-
- !!! strict
-
-html:
-
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
-currently supported doctypes, which can be
-extended simply by adding values to to _haml.doctypes_.
-
- '5': '<!DOCTYPE html>',
- 'xml': '<?xml version="1.0" encoding="utf-8" ?>',
- 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
- 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
- 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
- '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
- 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
- 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
-
-
-## :cdata
-
- %script
- :cdata
- foo
-
-html:
-
- <script><![CDATA[
- foo
- ]]></script>
-
-## :javascript
-
- %head
- :javascript
- if (foo)
- if (bar)
- alert('baz')
-
-html:
-
- <head>
- <script type="javascript">
- //<![CDATA[
- if (foo)
- if (bar)
- alert('baz')
- //]]>
- </script>
- </head>
-
-## Extending Haml
-
-### Adding Filters
-
- var haml = require('haml')
- haml.filters.my_filter = function(str) {
- return doSomethingWith(str)
- }
-
-by registering the filter function _my_filter_ we can now
-utilize it within our Haml templates as shown below:
- %p
- :my_filter
- some text
- here yay
- whoop awesome
-
-### Adding Doctypes
-
- var haml = require('haml')
- haml.doctypes.foo = '<!DOCTYPE ... >'
-
-Will now allow you to:
- !!! foo
-
-## Running Benchmarks
-
-To run benchmarks against [haml-js](http://github.com/creationix/haml-js)
-simply execute:
-
- $ git submodule update --init
- $ node benchmarks/run.js
-
-## More Information
-
- * View _spec/fixtures_ for more examples
- * Official [Haml](http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html) reference
- * JavaScript [Sass](http://github.com/visionmedia/sass.js) implementation
-
-## License
-
-(The MIT License)
-
-Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca>
-
-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.
\ No newline at end of file
+++ /dev/null
-!!!
-%html
- %head
- %title Welcome
- %script{ src: 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js', type: 'javascript' }
- -# javascript filter (and others)
- -# support arbitrary whitespace
- :javascript
- $(function(){
- alert('yay!')
- })
- %meta{ name: 'keywords', content: 'foo bar baz' }
- %body
- %h1 Title
- %ul
- %li one
- %li two
- %li
- %ol
- %li one
- %li two
- %li three
- %li four
- %ul
- %li five
- %li six
- %li
- %ol
- %li seven
- %li eight
\ No newline at end of file
+++ /dev/null
-
-var fs = require('fs'),
- haml = require('../lib/haml'),
- hamlJS = require('./haml-js/lib/haml'),
- page = fs.readFileSync('benchmarks/page.haml')
-
-var js = hamlJS.compile(page)
-
-exports.compare = {
- 'haml.js': function(){
- haml.render(page)
- },
- 'haml.js cached': function(){
- haml.render(page, { cache: true, filename: 'page.haml' })
- },
- 'haml-js': function(){
- hamlJS.render(page)
- },
- 'haml-js cached': function(){
- hamlJS.execute(js)
- },
- 'haml-js cached / optimized': function(){
- hamlJS.execute(js)
- }
-}
+++ /dev/null
-
-var sys = require('sys'),
- fs = require('fs'),
- haml = require('../lib/haml')
-
-var options = {
- filename: 'page.haml',
- locals: {
- title: 'Welcome',
- body: 'wahoo',
- usersOnline: 15
- }
-}
-
-sys.puts(haml.render(fs.readFileSync('examples/page.haml'), options))
\ No newline at end of file
+++ /dev/null
-%html
- %head
- %title= title
-
- %script{ src: 'jquery.js' }
-
- %script{ src: 'jquery.ui.js' }
-
- %body
- %h1 Welcome
-
- %ul#menu
- %li.first one
- %li two
- %li.last three
- %li
- %ul
- %li nested
-
- #content
- = body
- #usersOnline= usersOnline
+++ /dev/null
-lib/haml.js
\ No newline at end of file
+++ /dev/null
-
-// Haml - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
-
-/**
- * Module dependencies.
- */
-
-var sys = require('sys')
-
-/**
- * Version.
- */
-
-exports.version = '0.4.5'
-
-/**
- * Haml template cache.
- */
-
-exports.cache = {}
-
-/**
- * Default error context length.
- */
-
-exports.errorContextLength = 15
-
-/**
- * Self closing tags.
- */
-
-exports.selfClosing = [
- 'meta',
- 'img',
- 'link',
- 'br',
- 'hr',
- 'input',
- 'area',
- 'base'
- ]
-
-/**
- * Default supported doctypes.
- */
-
-exports.doctypes = {
- '5': '<!DOCTYPE html>',
- 'xml': '<?xml version="1.0" encoding="utf-8" ?>',
- 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
- 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
- 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
- '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
- 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
- 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
-}
-
-/**
- * Default filters.
- */
-
-exports.filters = {
-
- /**
- * Return plain string.
- */
-
- plain: function(str) {
- return str
- },
-
- /**
- * Wrap with CDATA tags.
- */
-
- cdata: function(str) {
- return '<![CDATA[\n' + str + '\n]]>'
- },
-
- /**
- * Wrap with <script> and CDATA tags.
- */
-
- javascript: function(str) {
- return '<script type="text/javascript">\n//<![CDATA[\n' + str + '\n//]]></script>'
- }
-}
-
-/**
- * Function templates.
- */
-
-exports.templates = {
-
- /**
- * Execute __code__.
- */
-
- code: (function(){
- __code__;
- return ""
- }).toString(),
-
- /**
- * Execute __code__ followed by buffering __block__.
- */
-
- codeBlock: (function(){
- var buf = [];
- __code__
- buf.push(__block__);
- return buf.join("")
- }).toString(),
-
- /**
- * Iterate __vals__ as __val__ while buffering __block__.
- */
-
- iterate: (function(){
- var buf = [];
- if (__vals__ instanceof Array) {
- for (var i = 0, len = __vals__.length; i < len; ++i) {
- var __key__ = i;
- var __val__ = __vals__[i];
- buf.push(__block__);
- }
- } else if (__vals__) {
- var keys = Object.keys(__vals__);
- for (var i = 0, len = keys.length; i < len; ++i) {
- var __key__ = keys[i];
- var __val__ = __vals__[__key__];
- buf.push(__block__);
- }
- }
- return buf.join("")
- }).toString()
-}
-
-/**
- * HamlError.
- */
-
-var HamlError = exports.HamlError = function(msg) {
- this.name = 'HamlError'
- this.message = msg
- Error.captureStackTrace(this, exports.render)
-}
-sys.inherits(HamlError, Error)
-
-/**
- * Lexing rules.
- */
-
-var rules = {
- indent: /^\n( *)(?! *-#)/,
- conditionalComment: /^\/(\[[^\n]+\])/,
- comment: /^\n? *\/ */,
- silentComment: /^\n? *-#([^\n]*)/,
- doctype: /^!!! *([^\n]*)/,
- escape: /^\\(.)/,
- filter: /^:(\w+) */,
- each: /^\- *each *(\w+)(?: *, *(\w+))? * in ([^\n]+)/,
- code: /^\-([^\n]+)/,
- outputCode: /^!=([^\n]+)/,
- escapeCode: /^=([^\n]+)/,
- attrs: /^\{(.*?)\}/,
- tag: /^%([-a-zA-Z][-a-zA-Z0-9:]*)/,
- class: /^\.([\w\-]+)/,
- id: /^\#([\w\-]+)/,
- text: /^([^\n]+)/
-}
-
-/**
- * Return error context _str_.
- *
- * @param {string} str
- * @return {string}
- * @api private
- */
-
-function context(str) {
- return String(str)
- .substr(0, exports.errorContextLength)
- .replace(/\n/g, '\\n')
-}
-
-/**
- * Tokenize _str_.
- *
- * @param {string} str
- * @return {array}
- * @api private
- */
-
-function tokenize(str) {
- var captures,
- token,
- tokens = [],
- line = 1,
- lastIndents = 0,
- str = String(str).trim().replace(/\r\n|\r/g, '\n')
- function error(msg){ throw new HamlError('(Haml):' + line + ' ' + msg) }
- while (str.length) {
- for (var type in rules)
- if (captures = rules[type].exec(str)) {
- token = {
- type: type,
- line: line,
- match: captures[0],
- val: captures.length > 2
- ? captures.slice(1)
- : captures[1]
- }
- str = str.substr(captures[0].length)
- if (type === 'newline' ||
- type === 'indent') ++line
- if (type !== 'indent') break
- var indents = token.val.length / 2
- if (indents % 1)
- error('invalid indentation; got ' + token.val.length + ' spaces, should be multiple of 2')
- else if (indents - 1 > lastIndents)
- error('invalid indentation; got ' + indents + ', when previous was ' + lastIndents)
- else if (lastIndents > indents)
- while (lastIndents-- > indents)
- tokens.push({ type: 'outdent', line: line })
- else if (lastIndents !== indents)
- tokens.push({ type: 'indent', line: line })
- else
- tokens.push({ type: 'newline', line: line })
- lastIndents = indents
- }
- if (token) {
- if (token.type !== 'silentComment')
- tokens.push(token)
- token = null
- } else
- error('near "' + context(str) + '"')
- }
- return tokens.concat({ type: 'eof' })
-}
-
-/**
- * Render template _name_ with the given placeholder _vals_.
- *
- * @param {string} name
- * @param {object} vals
- * @return {string}
- * @api private
- */
-
-function template(name, vals) {
- var buf = '(' + exports.templates[name] + ').call(this)'
- for (var key in vals)
- buf = buf.replace(new RegExp(key, 'g'), vals[key])
- return buf
-}
-
-// --- Parser
-
-/**
- * Initialize parser with _str_ and _options_.
- */
-
-function Parser(str, options) {
- options = options || {}
- this.tokens = tokenize(str)
- this.xml = options.xml
-}
-
-Parser.prototype = {
-
- /**
- * Lookahead a single token.
- *
- * @return {object}
- * @api private
- */
-
- get peek() {
- return this.tokens[0]
- },
-
- /**
- * Advance a single token.
- *
- * @return {object}
- * @api private
- */
-
- get advance() {
- return this.current = this.tokens.shift()
- },
-
- /**
- * outdent
- * | eof
- */
-
- get outdent() {
- switch (this.peek.type) {
- case 'eof':
- return
- case 'outdent':
- return this.advance
- default:
- throw new HamlError('expected outdent, got ' + this.peek.type)
- }
- },
-
- /**
- * text
- */
-
- get text() {
- return '"' + this.advance.val.trim() + '"'
- },
-
- /**
- * indent expr outdent
- */
-
- get block() {
- var buf = []
- this.advance
- while (this.peek.type !== 'outdent' &&
- this.peek.type !== 'eof')
- buf.push(this.expr)
- this.outdent
- return buf.join(' + ')
- },
-
- /**
- * indent expr
- */
-
- get textBlock() {
- var token,
- indents = 1,
- buf = []
- this.advance
- while (this.peek.type !== 'eof' && indents)
- switch((token = this.advance).type) {
- case 'newline':
- buf.push('"\\n' + Array(indents).join(' ') + '"')
- break
- case 'indent':
- ++indents
- buf.push('"\\n' + Array(indents).join(' ') + '"')
- break
- case 'outdent':
- --indents
- if (indents === 1) buf.push('"\\n"')
- break
- default:
- buf.push('"' + token.match.replace(/"/g, '\\\"') + '"')
- }
- return buf.join(' + ')
- },
-
- /**
- * ( attrs | class | id )*
- */
-
- get attrs() {
- var attrs = ['attrs', 'class', 'id'],
- classes = [],
- buf = []
- while (attrs.indexOf(this.peek.type) !== -1)
- switch (this.peek.type) {
- case 'id':
- buf.push('id: "' + this.advance.val + '"')
- break
- case 'class':
- classes.push(this.advance.val)
- break
- case 'attrs':
- buf.push(this.advance.val.replace(/(for) *:/gi, '"$1":'))
- }
- if (classes.length)
- buf.push('"class": "' + classes.join(' ') + '"')
- return buf.length
- ? ' " + attrs({' + buf.join() + '}) + "'
- : ''
- },
-
- /**
- * tag
- * | tag text
- * | tag conditionalComment
- * | tag comment
- * | tag outputCode
- * | tag escapeCode
- * | tag block
- */
-
- get tag() {
- var tag = this.advance.val,
- selfClosing = !this.xml && exports.selfClosing.indexOf(tag) !== -1,
- buf = ['"\\n<' + tag + this.attrs + (selfClosing ? '/>"' : '>"')]
- switch (this.peek.type) {
- case 'text':
- buf.push(this.text)
- break
- case 'conditionalComment':
- buf.push(this.conditionalComment)
- break;
- case 'comment':
- buf.push(this.comment)
- break
- case 'outputCode':
- buf.push(this.outputCode)
- break
- case 'escapeCode':
- buf.push(this.escapeCode)
- break
- case 'indent':
- buf.push(this.block)
- }
- if (!selfClosing) buf.push('"</' + tag + '>"')
- return buf.join(' + ')
- },
-
- /**
- * outputCode
- */
-
- get outputCode() {
- return this.advance.val
- },
-
- /**
- * escapeCode
- */
-
- get escapeCode() {
- return 'escape(' + this.advance.val + ')'
- },
-
- /**
- * doctype
- */
-
- get doctype() {
- var doctype = this.advance.val.trim().toLowerCase() || 'default'
- if (doctype in exports.doctypes)
- return '"' + exports.doctypes[doctype].replace(/"/g, '\\"') + '"'
- else
- throw new HamlError("doctype `" + doctype + "' does not exist")
- },
-
- /**
- * conditional comment expr
- */
-
- get conditionalComment() {
- var condition= this.advance.val
- var buf = this.peek.type === 'indent'
- ? this.block
- : this.expr
- return '"<!--' + condition + '>" + (' + buf + ') + "<![endif]-->"'
- },
-
- /**
- * comment expr
- */
-
- get comment() {
- this.advance
- var buf = this.peek.type === 'indent'
- ? this.block
- : this.expr
- return '"<!-- " + (' + buf + ') + " -->"'
- },
-
- /**
- * code
- * | code block
- */
-
- get code() {
- var code = this.advance.val
- if (this.peek.type === 'indent')
- return template('codeBlock', { __code__: code, __block__: this.block })
- return template('code', { __code__: code })
- },
-
- /**
- * filter textBlock
- */
-
- get filter() {
- var filter = this.advance.val
- if (!(filter in exports.filters))
- throw new HamlError("filter `" + filter + "' does not exist")
- if (this.peek.type !== 'indent')
- throw new HamlError("filter `" + filter + "' expects a text block")
- return 'exports.filters.' + filter + '(' + this.textBlock + ')'
- },
-
- /**
- * each block
- */
-
- get iterate() {
- var each = this.advance
- if (this.peek.type !== 'indent')
- throw new HamlError("'- each' expects a block, but got " + this.peek.type)
- return template('iterate', {
- __key__: each.val[1],
- __vals__: each.val[2],
- __val__: each.val[0],
- __block__: this.block
- })
- },
-
- /**
- * eof
- * | tag
- * | text*
- * | each
- * | code
- * | escape
- * | doctype
- * | filter
- * | comment
- * | conditionalComment
- * | escapeCode
- * | outputCode
- */
-
- get expr() {
- switch (this.peek.type) {
- case 'id':
- case 'class':
- this.tokens.unshift({ type: 'tag', val: 'div' })
- return this.tag
- case 'tag':
- return this.tag
- case 'text':
- var buf = []
- while (this.peek.type === 'text') {
- buf.push(this.advance.val.trim())
- if (this.peek.type === 'newline')
- this.advance
- }
- return '"' + buf.join(' ') + '"'
- case 'each':
- return this.iterate
- case 'code':
- return this.code
- case 'escape':
- return '"' + this.advance.val + '"'
- case 'doctype':
- return this.doctype
- case 'filter':
- return this.filter
- case 'conditionalComment':
- return this.conditionalComment
- case 'comment':
- return this.comment
- case 'escapeCode':
- return this.escapeCode
- case 'outputCode':
- return this.outputCode
- case 'newline':
- case 'indent':
- case 'outdent':
- this.advance
- return this.expr
- default:
- throw new HamlError('unexpected ' + this.peek.type)
- }
- },
-
- /**
- * expr*
- */
-
- get js() {
- var buf = []
- while (this.peek.type !== 'eof')
- buf.push(this.expr)
- return buf.join(' + ')
- }
-}
-
-/**
- * Escape html entities in _str_.
- *
- * @param {string} str
- * @return {string}
- * @api private
- */
-
-function escape(str) {
- return String(str)
- .replace(/&/g, '&')
- .replace(/>/g, '>')
- .replace(/</g, '<')
- .replace(/"/g, '"')
-}
-
-/**
- * Render _attrs_ to html escaped attributes.
- *
- * @param {object} attrs
- * @return {string}
- * @api public
- */
-
-function attrs(attrs) {
- var buf = []
- for (var key in attrs)
- if (typeof attrs[key] === 'boolean') {
- if (attrs[key] === true)
- buf.push(key + '="' + key + '"')
- } else if (attrs[key])
- buf.push(key + '="' + escape(attrs[key]) + '"')
- return buf.join(' ')
-}
-
-/**
- * Render a _str_ of haml.
- *
- * Options:
- *
- * - locals Local variables available to the template
- * - context Context in which the template is evaluated (becoming "this")
- * - filename Filename used to aid in error reporting
- * - cache Cache compiled javascript, requires "filename"
- * - xml Force xml support (no self-closing tags)
- *
- * @param {string} str
- * @param {object} options
- * @return {string}
- * @api public
- */
-
-exports.render = function(str, options) {
- var parser,
- options = options || {}
- if (options.cache && !options.filename)
- throw new Error('filename option must be passed when cache is enabled')
- return (function(){
- try {
- var fn
- if (options.cache && exports.cache[options.filename])
- fn = exports.cache[options.filename]
- else {
- parser = new Parser(str, options)
- fn = Function('locals, attrs, escape, exports', 'with (locals || {}){ return ' + parser.js + '}')
- }
- return (options.cache
- ? exports.cache[options.filename] = fn
- : fn).call(options.context, options.locals, attrs, escape, exports)
- } catch (err) {
- if (parser && err instanceof HamlError)
- err.message = '(Haml):' + parser.peek.line + ' ' + err.message
- else if (!(err instanceof HamlError))
- err.message = '(Haml): ' + err.message
- if (options.filename)
- err.message = err.message.replace('Haml', options.filename)
- throw err
- }
- }).call(options.context)
-}
+++ /dev/null
-{
- "name": "hamljs",
- "description": "Faster, harder, better HAML template engine",
- "keywords": ["haml", "template", "engine", "view", "nodejs"],
- "author": "TJ Holowaychuk <tj@vision-media.ca>",
- "version": "0.4.5",
- "directories": {
- "lib": "."
- }
-}
\ No newline at end of file
+++ /dev/null
----
- name: Haml
- description: Faster, harder, better HAML template engine
- tags: haml html template engine
- version: 0.4.5
+++ /dev/null
-.users
\ No newline at end of file
+++ /dev/null
-<div class="users"></div>
\ No newline at end of file
+++ /dev/null
-.foo.bar.baz_is-awesome
\ No newline at end of file
+++ /dev/null
-<div class="foo bar baz_is-awesome"></div>
\ No newline at end of file
+++ /dev/null
-%ul
- - each item in items
- %li= item
\ No newline at end of file
+++ /dev/null
-<ul>
-<li>one</li>
-<li>two</li>
-<li>three</li></ul>
\ No newline at end of file
+++ /dev/null
-%ul
- - each item, index in items
- %li= item + '(' + index + ')'
\ No newline at end of file
+++ /dev/null
-<ul>
-<li>one(0)</li>
-<li>two(1)</li>
-<li>three(2)</li></ul>
\ No newline at end of file
+++ /dev/null
-%ul
- - each item, index in items
- %li= item + '(' + index + ')'
\ No newline at end of file
+++ /dev/null
-<ul></ul>
\ No newline at end of file
+++ /dev/null
-= "<br" + "/>"
\ No newline at end of file
+++ /dev/null
-<br/>
\ No newline at end of file
+++ /dev/null
-!= "foo" + "bar"
\ No newline at end of file
+++ /dev/null
-foobar
\ No newline at end of file
+++ /dev/null
-- name = "tj"
-- if (name === 'tj')
- %p You are logged in
\ No newline at end of file
+++ /dev/null
-<p>You are logged in</p>
\ No newline at end of file
+++ /dev/null
-- if (true)
- - if (false)
- %p nope
- - if (true)
- %p yay
\ No newline at end of file
+++ /dev/null
-<p>yay</p>
\ No newline at end of file
+++ /dev/null
-/[if IE]
- %a{ 'href' : 'http://www.mozilla.com/en-US/firefox/' }
- %h1 Get Firefox
-/[if IE]
- %a{ 'href' : 'http://www.mozilla.com/en-US/firefox/' }
- /[if IE 6]
- %h1 Get Firefox (IE6 user)
- /[if IE 7]
- %h1 Get Firefox (IE7 user)
\ No newline at end of file
+++ /dev/null
-<!--[if IE]>
-<a href="http://www.mozilla.com/en-US/firefox/">
-<h1>Get Firefox</h1></a><![endif]--><!--[if IE]>
-<a href="http://www.mozilla.com/en-US/firefox/"><!--[if IE 6]>
-<h1>Get Firefox (IE6 user)</h1><![endif]--><!--[if IE 7]>
-<h1>Get Firefox (IE7 user)</h1><![endif]--></a><![endif]-->
\ No newline at end of file
+++ /dev/null
-/
- %ul
- %li nope
- %li nope
- %li nope
\ No newline at end of file
+++ /dev/null
-<!--
-<ul>
-<li>nope</li>
-<li>nope</li>
-<li>nope</li></ul> -->
\ No newline at end of file
+++ /dev/null
--# nothing
-%p yay
- -# whatever you want
\ No newline at end of file
+++ /dev/null
-<p>yay</p>
\ No newline at end of file
+++ /dev/null
- / %p foo
\ No newline at end of file
+++ /dev/null
-<!--
-<p>foo</p> -->
\ No newline at end of file
+++ /dev/null
-%ul
- %li one
- / first item
- %li two
- / second item
- %ul
- %li three
\ No newline at end of file
+++ /dev/null
-<ul>
-<li>one</li><!-- first item -->
-<li>two</li><!-- second item -->
-<ul>
-<li>three</li></ul></ul>
\ No newline at end of file
+++ /dev/null
-%p
- / foo bar baz
\ No newline at end of file
+++ /dev/null
-<p><!-- foo bar baz --></p>
\ No newline at end of file
+++ /dev/null
-%p= this
\ No newline at end of file
+++ /dev/null
-<p>yay</p>
\ No newline at end of file
+++ /dev/null
-#foo\r .bar
\ No newline at end of file
+++ /dev/null
-<div id="foo">
-<div class="bar"></div>
-</div>
\ No newline at end of file
+++ /dev/null
-#foo\r
- .bar
\ No newline at end of file
+++ /dev/null
-<div id="foo">
-<div class="bar"></div>
-</div>
\ No newline at end of file
+++ /dev/null
-!!!
\ No newline at end of file
+++ /dev/null
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
\ No newline at end of file
+++ /dev/null
-!!! xml
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8" ?>
\ No newline at end of file
+++ /dev/null
-!!! XML
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8" ?>
\ No newline at end of file
+++ /dev/null
-%html
- %body
- %p
\ No newline at end of file
+++ /dev/null
-%p
- \.foo
\ No newline at end of file
+++ /dev/null
-<p>.foo</p>
\ No newline at end of file
+++ /dev/null
-!!! xml
-%rss{ version: '2.0' }
- %channel
- %title ExpressJS
- %link http://expressjs.com
- %description Is super cool
- %language en-us
- %item
- %title Creating Routes
- %description Some stuff
- %link http://expressjs.com/routes
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8" ?>
-<rss version="2.0">
-<channel>
-<title>ExpressJS</title>
-<link>http://expressjs.com</link>
-<description>Is super cool</description>
-<language>en-us</language>
-<item>
-<title>Creating Routes</title>
-<description>Some stuff</description>
-<link>http://expressjs.com/routes</link></item></channel></rss>
\ No newline at end of file
+++ /dev/null
-%script
- :cdata
- foo
\ No newline at end of file
+++ /dev/null
-<script><![CDATA[
-foo
-]]></script>
\ No newline at end of file
+++ /dev/null
-%script
- :cdata
- $(function(){
- if (foo)
- if (bar)
- alert('yay')
- })
\ No newline at end of file
+++ /dev/null
-<script><![CDATA[
-$(function(){
- if (foo)
- if (bar)
- alert('yay')
-})
-]]></script>
\ No newline at end of file
+++ /dev/null
-%head
- :javascript
- if (foo)
- if (bar)
- alert("baz")
- foo()
- bar()
- %title Yay
\ No newline at end of file
+++ /dev/null
-<head><script type="text/javascript">
-//<![CDATA[
-if (foo)
- if (bar)
- alert("baz")
-foo()
-bar()
-//]]></script>
-<title>Yay</title></head>
\ No newline at end of file
+++ /dev/null
-%body
- :plain
- .foo
- bar
- = baz
\ No newline at end of file
+++ /dev/null
-<body>.foo
-bar
-= baz</body>
\ No newline at end of file
+++ /dev/null
-%div
- <p>literal</p>
\ No newline at end of file
+++ /dev/null
-<div><p>literal</p></div>
\ No newline at end of file
+++ /dev/null
-#users
\ No newline at end of file
+++ /dev/null
-<div id="users"></div>
\ No newline at end of file
+++ /dev/null
-%label{ for: "forsomething"}
\ No newline at end of file
+++ /dev/null
-<label for="forsomething"></label>
\ No newline at end of file
+++ /dev/null
-%ul
- - each item in items
- %li= item
-%p= "Total: " + items.length
\ No newline at end of file
+++ /dev/null
-<ul>
-<li>foo</li>
-<li>bar</li>
-<li>baz</li></ul>
-<p>Total: 3</p>
\ No newline at end of file
+++ /dev/null
-%p= user
\ No newline at end of file
+++ /dev/null
-<p>tj</p>
\ No newline at end of file
+++ /dev/null
-%h:table{ 'xmlns:h': 'http://www.w3.org/1999/xhtml' }
\ No newline at end of file
+++ /dev/null
-%body
- %fb:test.a
- %fb:bar
- %fb:foo{ title: 'home' }
- %fb:login-button
+++ /dev/null
-<body>
-<fb:test class="a"></fb:test>
-<fb:bar></fb:bar>
-<fb:foo title="home"></fb:foo>
-<fb:login-button></fb:login-button></body>
+++ /dev/null
-<h:table xmlns:h="http://www.w3.org/1999/xhtml"></h:table>
\ No newline at end of file
+++ /dev/null
-%html
- %head
- %title Page Title
- %body
- %h1 Title
- %p some stuff
\ No newline at end of file
+++ /dev/null
-<html>
-<head>
-<title>Page Title</title></head>
-<body>
-<h1>Title</h1>
-<p>some stuff</p></body></html>
\ No newline at end of file
+++ /dev/null
-%ul
- %li one
- %li two
- %li
- %ul
- %li three
\ No newline at end of file
+++ /dev/null
-<ul>
-<li>one</li>
-<li>two</li>
-<li>
-<ul>
-<li>three</li></ul></li></ul>
\ No newline at end of file
+++ /dev/null
-%ul
- %li one
- %li two
- %li three
- %li
- %ul
- %li four
-
-%p foo
-%p
- bar
- baz
\ No newline at end of file
+++ /dev/null
-<ul>
-<li>one</li>
-<li>two</li>
-<li>three</li>
-<li>
-<ul>
-<li>four</li></ul></li></ul>
-<p>foo</p>
-<p>bar baz</p>
\ No newline at end of file
+++ /dev/null
-%input{ type: 'checkbox', checked: true }
\ No newline at end of file
+++ /dev/null
-<input type="checkbox" checked="checked"/>
\ No newline at end of file
+++ /dev/null
-%option{ value: '<script></script>' }
\ No newline at end of file
+++ /dev/null
-<option value="<script></script>"></option>
\ No newline at end of file
+++ /dev/null
-%a{ href: '/', title: 'home' }
\ No newline at end of file
+++ /dev/null
-<a href="/" title="home"></a>
\ No newline at end of file
+++ /dev/null
-%h1.title
\ No newline at end of file
+++ /dev/null
-<h1 class="title"></h1>
\ No newline at end of file
+++ /dev/null
-%body.front-page.editing-user
\ No newline at end of file
+++ /dev/null
-<body class="front-page editing-user"></body>
\ No newline at end of file
+++ /dev/null
-%div= "yay"
\ No newline at end of file
+++ /dev/null
-<div>yay</div>
\ No newline at end of file
+++ /dev/null
-%div!= "<br/>"
\ No newline at end of file
+++ /dev/null
-<div><br/></div>
\ No newline at end of file
+++ /dev/null
-%a#delete-user.first.button{ href: '/', title: 'Click to delete' }= "Delete " + "tj"
\ No newline at end of file
+++ /dev/null
-<a id="delete-user" href="/" title="Click to delete" class="first button">Delete tj</a>
\ No newline at end of file
+++ /dev/null
-%body
- %div.a
- %div.b
- .c
- .d
- #e
- #f
\ No newline at end of file
+++ /dev/null
-<body>
-<div class="a"></div>
-<div class="b"></div>
-<div class="c"></div>
-<div class="d"></div>
-<div id="e"></div>
-<div id="f"></div></body>
\ No newline at end of file
+++ /dev/null
-%div= "<br/>"
\ No newline at end of file
+++ /dev/null
-<div><br/></div>
\ No newline at end of file
+++ /dev/null
-%br
\ No newline at end of file
+++ /dev/null
-<br/>
\ No newline at end of file
+++ /dev/null
-%div
\ No newline at end of file
+++ /dev/null
-<div></div>
\ No newline at end of file
+++ /dev/null
-%p
- Visit
- %a{ href: 'http://vision-media.ca' } Vision Media
- because im amazing,
- yes...
\ No newline at end of file
+++ /dev/null
-<p>Visit
-<a href="http://vision-media.ca">Vision Media</a>because im amazing, yes...</p>
\ No newline at end of file
+++ /dev/null
-%p
- some text
- and more text
- and even more text!
- OMG
\ No newline at end of file
+++ /dev/null
-<p>some text and more text and even more text! OMG</p>
\ No newline at end of file
+++ /dev/null
-%div some text
\ No newline at end of file
+++ /dev/null
-<div>some text</div>
\ No newline at end of file
+++ /dev/null
-%a
- %b
-
-
\ No newline at end of file
+++ /dev/null
-<a>
-<b></b></a>
\ No newline at end of file
+++ /dev/null
-body.jspec {
- margin: 45px 0;
- font: 12px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
- background: #efefef url(images/bg.png) top left repeat-x;
- text-align: center;
-}
-#jspec {
- margin: 0 auto;
- padding-top: 30px;
- width: 1008px;
- background: url(images/vr.png) top left repeat-y;
- text-align: left;
-}
-#jspec-top {
- position: relative;
- margin: 0 auto;
- width: 1008px;
- height: 40px;
- background: url(images/sprites.bg.png) top left no-repeat;
-}
-#jspec-bottom {
- margin: 0 auto;
- width: 1008px;
- height: 15px;
- background: url(images/sprites.bg.png) bottom left no-repeat;
-}
-#jspec .loading {
- margin-top: -45px;
- width: 1008px;
- height: 80px;
- background: url(images/loading.gif) 50% 50% no-repeat;
-}
-#jspec-title {
- position: absolute;
- top: 15px;
- left: 20px;
- width: 160px;
- font-size: 22px;
- font-weight: normal;
- background: url(images/sprites.png) 0 -126px no-repeat;
- text-align: center;
-}
-#jspec-title em {
- font-size: 10px;
- font-style: normal;
- color: #BCC8D1;
-}
-#jspec-report * {
- margin: 0;
- padding: 0;
- background: none;
- border: none;
-}
-#jspec-report {
- padding: 15px 40px;
- font: 11px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
- color: #7B8D9B;
-}
-#jspec-report.has-failures {
- padding-bottom: 30px;
-}
-#jspec-report .hidden {
- display: none;
-}
-#jspec-report .heading {
- margin-bottom: 15px;
-}
-#jspec-report .heading span {
- padding-right: 10px;
-}
-#jspec-report .heading .passes em {
- color: #0ea0eb;
-}
-#jspec-report .heading .failures em {
- color: #FA1616;
-}
-#jspec-report table {
- font-size: 11px;
- border-collapse: collapse;
-}
-#jspec-report td {
- padding: 8px;
- text-indent: 30px;
- color: #7B8D9B;
-}
-#jspec-report tr.body {
- display: none;
-}
-#jspec-report tr.body pre {
- margin: 0;
- padding: 0 0 5px 25px;
-}
-#jspec-report tr.even:hover + tr.body,
-#jspec-report tr.odd:hover + tr.body {
- display: block;
-}
-#jspec-report tr td:first-child em {
- display: block;
- clear: both;
- font-style: normal;
- font-weight: normal;
- color: #7B8D9B;
-}
-#jspec-report tr.even:hover,
-#jspec-report tr.odd:hover {
- text-shadow: 1px 1px 1px #fff;
- background: #F2F5F7;
-}
-#jspec-report td + td {
- padding-right: 0;
- width: 15px;
-}
-#jspec-report td.pass {
- background: url(images/sprites.png) 3px -7px no-repeat;
-}
-#jspec-report td.fail {
- background: url(images/sprites.png) 3px -158px no-repeat;
- font-weight: bold;
- color: #FC0D0D;
-}
-#jspec-report td.requires-implementation {
- background: url(images/sprites.png) 3px -333px no-repeat;
-}
-#jspec-report tr.description td {
- margin-top: 25px;
- padding-top: 25px;
- font-size: 12px;
- font-weight: bold;
- text-indent: 0;
- color: #1a1a1a;
-}
-#jspec-report tr.description:first-child td {
- border-top: none;
-}
-#jspec-report .assertion {
- display: block;
- float: left;
- margin: 0 0 0 1px;
- padding: 0;
- width: 1px;
- height: 5px;
- background: #7B8D9B;
-}
-#jspec-report .assertion.failed {
- background: red;
-}
-.jspec-sandbox {
- display: none;
-}
\ No newline at end of file
+++ /dev/null
-
-// JSpec - Growl - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
-
-;(function(){
-
- Growl = {
-
- // --- Version
-
- version: '1.0.0',
-
- /**
- * Execute the given _cmd_, returning an array of lines from stdout.
- *
- * Examples:
- *
- * Growl.exec('growlnotify', '-m', msg)
- *
- * @param {string ...} cmd
- * @return {array}
- * @api public
- */
-
- exec: function(cmd) {
- var lines = [], line
- with (JavaImporter(java.lang, java.io)) {
- var proccess = Runtime.getRuntime().exec(Array.prototype.slice.call(arguments))
- var stream = new DataInputStream(proccess.getInputStream())
- while (line = stream.readLine())
- lines.push(line + '')
- stream.close()
- }
- return lines
- },
-
- /**
- * Return the extension of the given _path_ or null.
- *
- * @param {string} path
- * @return {string}
- * @api private
- */
-
- extname: function(path) {
- return path.lastIndexOf('.') != -1 ?
- path.slice(path.lastIndexOf('.') + 1, path.length) :
- null
- },
-
- /**
- * Version of the 'growlnotify' binary.
- *
- * @return {string}
- * @api private
- */
-
- binVersion: function() {
- try { return this.exec('growlnotify', '-v')[0].split(' ')[1] } catch (e) {}
- },
-
- /**
- * Send growl notification _msg_ with _options_.
- *
- * Options:
- *
- * - title Notification title
- * - sticky Make the notification stick (defaults to false)
- * - name Application name (defaults to growlnotify)
- * - image
- * - path to an icon sets --iconpath
- * - path to an image sets --image
- * - capitalized word sets --appIcon
- * - filename uses extname as --icon
- * - otherwise treated as --icon
- *
- * Examples:
- *
- * Growl.notify('New email')
- * Growl.notify('5 new emails', { title: 'Thunderbird' })
- *
- * @param {string} msg
- * @param {options} hash
- * @api public
- */
-
- notify: function(msg, options) {
- options = options || {}
- var args = ['growlnotify', '-m', msg]
- if (!this.binVersion()) throw new Error('growlnotify executable is required')
- if (image = options.image) {
- var flag, ext = this.extname(image)
- flag = flag || ext == 'icns' && 'iconpath'
- flag = flag || /^[A-Z]/.test(image) && 'appIcon'
- flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image'
- flag = flag || ext && (image = ext) && 'icon'
- flag = flag || 'icon'
- args.push('--' + flag, image)
- }
- if (options.sticky) args.push('--sticky')
- if (options.name) args.push('--name', options.name)
- if (options.title) args.push(options.title)
- this.exec.apply(this, args)
- }
- }
-
- JSpec.include({
- name: 'Growl',
- reporting: function(options){
- var stats = JSpec.stats
- if (stats.failures) Growl.notify('failed ' + stats.failures + ' assertions', { title: 'JSpec'})
- else Growl.notify('passed ' + stats.passes + ' assertions', { title: 'JSpec' })
- }
- })
-
-})()
\ No newline at end of file
+++ /dev/null
-
-// JSpec - jQuery - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
-
-JSpec
-.requires('jQuery', 'when using jspec.jquery.js')
-.include({
- name: 'jQuery',
-
- // --- Initialize
-
- init : function() {
- jQuery.ajaxSetup({ async: false })
- },
-
- // --- Utilities
-
- utilities : {
- element: jQuery,
- elements: jQuery,
- sandbox : function() {
- return jQuery('<div class="sandbox"></div>')
- }
- },
-
- // --- Matchers
-
- matchers : {
- have_tag : "jQuery(expected, actual).length === 1",
- have_one : "alias have_tag",
- have_tags : "jQuery(expected, actual).length > 1",
- have_many : "alias have_tags",
- have_any : "alias have_tags",
- have_child : "jQuery(actual).children(expected).length === 1",
- have_children : "jQuery(actual).children(expected).length > 1",
- have_text : "jQuery(actual).text() === expected",
- have_value : "jQuery(actual).val() === expected",
- be_enabled : "!jQuery(actual).attr('disabled')",
- have_class : "jQuery(actual).hasClass(expected)",
- be_animated : "jQuery(actual).queue().length > 0",
-
- be_visible : function(actual) {
- return jQuery(actual).css('display') != 'none' &&
- jQuery(actual).css('visibility') != 'hidden' &&
- jQuery(actual).attr('type') != 'hidden'
- },
-
- be_hidden : function(actual) {
- return !JSpec.does(actual, 'be_visible')
- },
-
- have_classes : function(actual) {
- return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){
- return !JSpec.does(actual, 'have_class', arg)
- })
- },
-
- have_attr : function(actual, attr, value) {
- return value ? jQuery(actual).attr(attr) == value:
- jQuery(actual).attr(attr)
- },
-
- have_event_handlers : function(actual, expected) {
- return jQuery(actual).data('events') ?
- jQuery(actual).data('events').hasOwnProperty(expected) :
- false
- },
-
- 'be disabled selected checked' : function(attr) {
- return 'jQuery(actual).attr("' + attr + '")'
- },
-
- 'have type id title alt href src sel rev name target' : function(attr) {
- return function(actual, value) {
- return JSpec.does(actual, 'have_attr', attr, value)
- }
- }
- }
-})
-
+++ /dev/null
-
-// JSpec - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
-
-;(function(){
-
- JSpec = {
- version : '4.3.2',
- assert : true,
- cache : {},
- suites : [],
- modules : [],
- allSuites : [],
- sharedBehaviors: [],
- matchers : {},
- stubbed : [],
- options : {},
- request : 'XMLHttpRequest' in this ? XMLHttpRequest : null,
- stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 },
-
- /**
- * Default context in which bodies are evaluated.
- *
- * Replace context simply by setting JSpec.context
- * to your own like below:
- *
- * JSpec.context = { foo : 'bar' }
- *
- * Contexts can be changed within any body, this can be useful
- * in order to provide specific helper methods to specific suites.
- *
- * To reset (usually in after hook) simply set to null like below:
- *
- * JSpec.context = null
- *
- */
-
- defaultContext : {
-
- /**
- * Return an object used for proxy assertions.
- * This object is used to indicate that an object
- * should be an instance of _object_, not the constructor
- * itself.
- *
- * @param {function} constructor
- * @return {hash}
- * @api public
- */
-
- an_instance_of : function(constructor) {
- return { an_instance_of : constructor }
- },
-
- /**
- * Load fixture at _path_.
- *
- * Fixtures are resolved as:
- *
- * - <path>
- * - <path>.html
- *
- * @param {string} path
- * @return {string}
- * @api public
- */
-
- fixture : function(path) {
- if (JSpec.cache[path]) return JSpec.cache[path]
- return JSpec.cache[path] =
- JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) ||
- JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.html')
- },
-
- /**
- * Load json fixture at _path_.
- *
- * JSON fixtures are resolved as:
- *
- * - <path>
- * - <path>.json
- *
- * @param {string} path
- * @return {object}
- * @api public
- */
-
- json_fixture: function(path) {
- if (!JSpec.cache['json:' + path])
- JSpec.cache['json:' + path] =
- JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) ||
- JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.json')
- try {
- return eval('(' + JSpec.cache['json:' + path] + ')')
- } catch (e) {
- throw 'json_fixture("' + path + '"): ' + e
- }
- }
- },
-
- // --- Objects
-
- reporters : {
-
- /**
- * Report to server.
- *
- * Options:
- * - uri specific uri to report to.
- * - verbose weither or not to output messages
- * - failuresOnly output failure messages only
- *
- * @api public
- */
-
- Server : function(results, options) {
- var uri = options.uri || 'http://' + window.location.host + '/results'
- JSpec.post(uri, {
- stats: JSpec.stats,
- options: options,
- results: map(results.allSuites, function(suite) {
- if (suite.isExecutable())
- return {
- description: suite.description,
- specs: map(suite.specs, function(spec) {
- return {
- description: spec.description,
- message: !spec.passed() ? spec.failure().message : null,
- status: spec.requiresImplementation() ? 'pending' :
- spec.passed() ? 'pass' :
- 'fail',
- assertions: map(spec.assertions, function(assertion){
- return {
- passed: assertion.passed
- }
- })
- }
- })
- }
- })
- })
- if ('close' in main) main.close()
- },
-
- /**
- * Default reporter, outputting to the DOM.
- *
- * Options:
- * - reportToId id of element to output reports to, defaults to 'jspec'
- * - failuresOnly displays only suites with failing specs
- *
- * @api public
- */
-
- DOM : function(results, options) {
- var id = option('reportToId') || 'jspec',
- report = document.getElementById(id),
- failuresOnly = option('failuresOnly'),
- classes = results.stats.failures ? 'has-failures' : ''
- if (!report) throw 'JSpec requires the element #' + id + ' to output its reports'
-
- function bodyContents(body) {
- return JSpec.
- escape(JSpec.contentsOf(body)).
- replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }).
- replace(/\r\n|\r|\n/gm, '<br/>')
- }
-
- report.innerHTML = '<div id="jspec-report" class="' + classes + '"><div class="heading"> \
- <span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
- <span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
- <span class="passes">Duration: <em>' + results.duration + '</em> ms</span> \
- </div><table class="suites">' + map(results.allSuites, function(suite) {
- var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
- if (displaySuite && suite.isExecutable())
- return '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>' +
- map(suite.specs, function(i, spec) {
- return '<tr class="' + (i % 2 ? 'odd' : 'even') + '">' +
- (spec.requiresImplementation() ?
- '<td class="requires-implementation" colspan="2">' + escape(spec.description) + '</td>' :
- (spec.passed() && !failuresOnly) ?
- '<td class="pass">' + escape(spec.description)+ '</td><td>' + spec.assertionsGraph() + '</td>' :
- !spec.passed() ?
- '<td class="fail">' + escape(spec.description) +
- map(spec.failures(), function(a){ return '<em>' + escape(a.message) + '</em>' }).join('') +
- '</td><td>' + spec.assertionsGraph() + '</td>' :
- '') +
- '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
- }).join('') + '</tr>'
- }).join('') + '</table></div>'
- },
-
- /**
- * Terminal reporter.
- *
- * @api public
- */
-
- Terminal : function(results, options) {
- var failuresOnly = option('failuresOnly')
- print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
- color(" Failures: ", 'bold') + color(results.stats.failures, 'red') +
- color(" Duration: ", 'bold') + color(results.duration, 'green') + " ms \n")
-
- function indent(string) {
- return string.replace(/^(.)/gm, ' $1')
- }
-
- each(results.allSuites, function(suite) {
- var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
- if (displaySuite && suite.isExecutable()) {
- print(color(' ' + suite.description, 'bold'))
- each(suite.specs, function(spec){
- var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
- return graph + color('.', assertion.passed ? 'green' : 'red')
- })
- if (spec.requiresImplementation())
- print(color(' ' + spec.description, 'blue') + assertionsGraph)
- else if (spec.passed() && !failuresOnly)
- print(color(' ' + spec.description, 'green') + assertionsGraph)
- else if (!spec.passed())
- print(color(' ' + spec.description, 'red') + assertionsGraph +
- "\n" + indent(map(spec.failures(), function(a){ return a.message }).join("\n")) + "\n")
- })
- print("")
- }
- })
-
- quit(results.stats.failures)
- }
- },
-
- Assertion : function(matcher, actual, expected, negate) {
- extend(this, {
- message: '',
- passed: false,
- actual: actual,
- negate: negate,
- matcher: matcher,
- expected: expected,
-
- // Report assertion results
-
- report : function() {
- if (JSpec.assert)
- this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
- return this
- },
-
- // Run the assertion
-
- run : function() {
- // TODO: remove unshifting
- expected.unshift(actual)
- this.result = matcher.match.apply(this, expected)
- this.passed = negate ? !this.result : this.result
- if (!this.passed) this.message = matcher.message.call(this, actual, expected, negate, matcher.name)
- return this
- }
- })
- },
-
- ProxyAssertion : function(object, method, times, negate) {
- var self = this,
- old = object[method]
-
- // Proxy
-
- object[method] = function(){
- var args = toArray(arguments),
- result = old.apply(object, args)
- self.calls.push({ args : args, result : result })
- return result
- }
-
- // Times
-
- this.times = {
- once : 1,
- twice : 2
- }[times] || times || 1
-
- extend(this, {
- calls: [],
- message: '',
- defer: true,
- passed: false,
- negate: negate,
- object: object,
- method: method,
-
- // Proxy return value
-
- and_return : function(result) {
- this.expectedResult = result
- return this
- },
-
- // Proxy arguments passed
-
- with_args : function() {
- this.expectedArgs = toArray(arguments)
- return this
- },
-
- // Check if any calls have failing results
-
- anyResultsFail : function() {
- return any(this.calls, function(call){
- return self.expectedResult.an_instance_of ?
- call.result.constructor != self.expectedResult.an_instance_of:
- !equal(self.expectedResult, call.result)
- })
- },
-
- // Check if any calls have passing results
-
- anyResultsPass : function() {
- return any(this.calls, function(call){
- return self.expectedResult.an_instance_of ?
- call.result.constructor == self.expectedResult.an_instance_of:
- equal(self.expectedResult, call.result)
- })
- },
-
- // Return the passing result
-
- passingResult : function() {
- return this.anyResultsPass().result
- },
-
- // Return the failing result
-
- failingResult : function() {
- return this.anyResultsFail().result
- },
-
- // Check if any arguments fail
-
- anyArgsFail : function() {
- return any(this.calls, function(call){
- return any(self.expectedArgs, function(i, arg){
- if (arg == null) return call.args[i] == null
- return arg.an_instance_of ?
- call.args[i].constructor != arg.an_instance_of:
- !equal(arg, call.args[i])
-
- })
- })
- },
-
- // Check if any arguments pass
-
- anyArgsPass : function() {
- return any(this.calls, function(call){
- return any(self.expectedArgs, function(i, arg){
- return arg.an_instance_of ?
- call.args[i].constructor == arg.an_instance_of:
- equal(arg, call.args[i])
-
- })
- })
- },
-
- // Return the passing args
-
- passingArgs : function() {
- return this.anyArgsPass().args
- },
-
- // Return the failing args
-
- failingArgs : function() {
- return this.anyArgsFail().args
- },
-
- // Report assertion results
-
- report : function() {
- if (JSpec.assert)
- this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures
- return this
- },
-
- // Run the assertion
-
- run : function() {
- var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' )
-
- function times(n) {
- return n > 2 ? n + ' times' : { 1: 'once', 2: 'twice' }[n]
- }
-
- if (this.expectedResult != null && (negate ? this.anyResultsPass() : this.anyResultsFail()))
- this.message = methodString + ' to return ' + puts(this.expectedResult) +
- ' but ' + (negate ? 'it did' : 'got ' + puts(this.failingResult()))
-
- if (this.expectedArgs && (negate ? !this.expectedResult && this.anyArgsPass() : this.anyArgsFail()))
- this.message = methodString + ' to be called with ' + puts.apply(this, this.expectedArgs) +
- ' but was' + (negate ? '' : ' called with ' + puts.apply(this, this.failingArgs()))
-
- if (negate ? !this.expectedResult && !this.expectedArgs && this.calls.length >= this.times : this.calls.length != this.times)
- this.message = methodString + ' to be called ' + times(this.times) +
- ', but ' + (this.calls.length == 0 ? ' was not called' : ' was called ' + times(this.calls.length))
-
- if (!this.message.length)
- this.passed = true
-
- return this
- }
- })
- },
-
- /**
- * Specification Suite block object.
- *
- * @param {string} description
- * @param {function} body
- * @api private
- */
-
- Suite : function(description, body, isShared) {
- var self = this
- extend(this, {
- body: body,
- description: description,
- suites: [],
- sharedBehaviors: [],
- specs: [],
- ran: false,
- shared: isShared,
- hooks: { 'before' : [], 'after' : [],
- 'before_each' : [], 'after_each' : [],
- 'before_nested' : [], 'after_nested' : []},
-
- // Add a spec to the suite
-
- addSpec : function(description, body) {
- var spec = new JSpec.Spec(description, body)
- this.specs.push(spec)
- JSpec.stats.specs++ // TODO: abstract
- spec.suite = this
- },
-
- // Add a before hook to the suite
-
- addBefore : function(options, body) {
- body.options = options || {}
- this.befores.push(body)
- },
-
- // Add an after hook to the suite
-
- addAfter : function(options, body) {
- body.options = options || {}
- this.afters.unshift(body)
- },
-
- // Add a hook to the suite
-
- addHook : function(hook, body) {
- this.hooks[hook].push(body)
- },
-
- // Add a nested suite
-
- addSuite : function(description, body, isShared) {
- var suite = new JSpec.Suite(description, body, isShared)
- JSpec.allSuites.push(suite)
- suite.name = suite.description
- suite.description = this.description + ' ' + suite.description
- this.suites.push(suite)
- suite.suite = this
- },
-
- // Invoke a hook in context to this suite
-
- hook : function(hook) {
- if (hook != 'before' && hook != 'after')
- if (this.suite) this.suite.hook(hook)
-
- each(this.hooks[hook], function(body) {
- JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.description + "': ")
- })
- },
-
- // Check if nested suites are present
-
- hasSuites : function() {
- return this.suites.length
- },
-
- // Check if this suite has specs
-
- hasSpecs : function() {
- return this.specs.length
- },
-
- // Check if the entire suite passed
-
- passed : function() {
- return !any(this.specs, function(spec){
- return !spec.passed()
- })
- },
-
- isShared : function(){
- return this.shared
- },
-
- isExecutable : function() {
- return !this.isShared() && this.hasSpecs()
- }
- })
- },
-
- /**
- * Specification block object.
- *
- * @param {string} description
- * @param {function} body
- * @api private
- */
-
- Spec : function(description, body) {
- extend(this, {
- body: body,
- description: description,
- assertions: [],
-
- // Add passing assertion
-
- pass : function(message) {
- this.assertions.push({ passed: true, message: message })
- if (JSpec.assert) ++JSpec.stats.passes
- },
-
- // Add failing assertion
-
- fail : function(message) {
- this.assertions.push({ passed: false, message: message })
- if (JSpec.assert) ++JSpec.stats.failures
- },
-
- // Run deferred assertions
-
- runDeferredAssertions : function() {
- each(this.assertions, function(assertion){
- if (assertion.defer) assertion.run().report(), hook('afterAssertion', assertion)
- })
- },
-
- // Find first failing assertion
-
- failure : function() {
- return find(this.assertions, function(assertion){
- return !assertion.passed
- })
- },
-
- // Find all failing assertions
-
- failures : function() {
- return select(this.assertions, function(assertion){
- return !assertion.passed
- })
- },
-
- // Weither or not the spec passed
-
- passed : function() {
- return !this.failure()
- },
-
- // Weither or not the spec requires implementation (no assertions)
-
- requiresImplementation : function() {
- return this.assertions.length == 0
- },
-
- // Sprite based assertions graph
-
- assertionsGraph : function() {
- return map(this.assertions, function(assertion){
- return '<span class="assertion ' + (assertion.passed ? 'passed' : 'failed') + '"></span>'
- }).join('')
- }
- })
- },
-
- Module : function(methods) {
- extend(this, methods)
- },
-
- JSON : {
-
- /**
- * Generic sequences.
- */
-
- meta : {
- '\b' : '\\b',
- '\t' : '\\t',
- '\n' : '\\n',
- '\f' : '\\f',
- '\r' : '\\r',
- '"' : '\\"',
- '\\' : '\\\\'
- },
-
- /**
- * Escapable sequences.
- */
-
- escapable : /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
-
- /**
- * JSON encode _object_.
- *
- * @param {mixed} object
- * @return {string}
- * @api private
- */
-
- encode : function(object) {
- var self = this
- if (object == undefined || object == null) return 'null'
- if (object === true) return 'true'
- if (object === false) return 'false'
- switch (typeof object) {
- case 'number': return object
- case 'string': return this.escapable.test(object) ?
- '"' + object.replace(this.escapable, function (a) {
- return typeof self.meta[a] === 'string' ? self.meta[a] :
- '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4)
- }) + '"' :
- '"' + object + '"'
- case 'object':
- if (object.constructor == Array)
- return '[' + map(object, function(val){
- return self.encode(val)
- }).join(', ') + ']'
- else if (object)
- return '{' + map(object, function(key, val){
- return self.encode(key) + ':' + self.encode(val)
- }).join(', ') + '}'
- }
- return 'null'
- }
- },
-
- // --- DSLs
-
- DSLs : {
- snake : {
- expect : function(actual){
- return JSpec.expect(actual)
- },
-
- describe : function(description, body) {
- return JSpec.currentSuite.addSuite(description, body, false)
- },
-
- it : function(description, body) {
- return JSpec.currentSuite.addSpec(description, body)
- },
-
- before : function(body) {
- return JSpec.currentSuite.addHook('before', body)
- },
-
- after : function(body) {
- return JSpec.currentSuite.addHook('after', body)
- },
-
- before_each : function(body) {
- return JSpec.currentSuite.addHook('before_each', body)
- },
-
- after_each : function(body) {
- return JSpec.currentSuite.addHook('after_each', body)
- },
-
- before_nested : function(body) {
- return JSpec.currentSuite.addHook('before_nested', body)
- },
-
- after_nested : function(body){
- return JSpec.currentSuite.addhook('after_nested', body)
- },
-
- shared_behaviors_for : function(description, body){
- return JSpec.currentSuite.addSuite(description, body, true)
- },
-
- should_behave_like : function(description) {
- return JSpec.shareBehaviorsOf(description)
- }
- }
- },
-
- // --- Methods
-
- /**
- * Check if _value_ is 'stop'. For use as a
- * utility callback function.
- *
- * @param {mixed} value
- * @return {bool}
- * @api public
- */
-
- haveStopped : function(value) {
- return value === 'stop'
- },
-
- /**
- * Include _object_ which may be a hash or Module instance.
- *
- * @param {hash, Module} object
- * @return {JSpec}
- * @api public
- */
-
- include : function(object) {
- var module = object.constructor == JSpec.Module ? object : new JSpec.Module(object)
- this.modules.push(module)
- if ('init' in module) module.init()
- if ('utilities' in module) extend(this.defaultContext, module.utilities)
- if ('matchers' in module) this.addMatchers(module.matchers)
- if ('reporters' in module) extend(this.reporters, module.reporters)
- if ('DSLs' in module)
- each(module.DSLs, function(name, methods){
- JSpec.DSLs[name] = JSpec.DSLs[name] || {}
- extend(JSpec.DSLs[name], methods)
- })
- return this
- },
-
- /**
- * Add a module hook _name_, which is immediately
- * called per module with the _args_ given. An array of
- * hook return values is returned.
- *
- * @param {name} string
- * @param {...} args
- * @return {array}
- * @api private
- */
-
- hook : function(name, args) {
- args = toArray(arguments, 1)
- return inject(JSpec.modules, [], function(results, module){
- if (typeof module[name] == 'function')
- results.push(JSpec.evalHook(module, name, args))
- })
- },
-
- /**
- * Eval _module_ hook _name_ with _args_. Evaluates in context
- * to the module itself, JSpec, and JSpec.context.
- *
- * @param {Module} module
- * @param {string} name
- * @param {array} args
- * @return {mixed}
- * @api private
- */
-
- evalHook : function(module, name, args) {
- hook('evaluatingHookBody', module, name)
- return module[name].apply(module, args)
- },
-
- /**
- * Same as hook() however accepts only one _arg_ which is
- * considered immutable. This function passes the arg
- * to the first module, then passes the return value of the last
- * module called, to the following module.
- *
- * @param {string} name
- * @param {mixed} arg
- * @return {mixed}
- * @api private
- */
-
- hookImmutable : function(name, arg) {
- return inject(JSpec.modules, arg, function(result, module){
- if (typeof module[name] == 'function')
- return JSpec.evalHook(module, name, [result])
- })
- },
-
- /**
- * Find a shared example suite by its description or name.
- * First searches parent tree of suites for shared behavior
- * before falling back to global scoped nested behaviors.
- *
- * @param {string} description
- * @return {Suite}
- * @api private
- */
-
- findSharedBehavior : function(description) {
- var behavior
- return (behavior = JSpec.findLocalSharedBehavior(description))
- ? behavior
- : JSpec.findGlobalSharedBehavior(description)
- },
-
- /**
- * Find a shared example suite within the current suite's
- * parent tree by its description or name.
- *
- * @param {string} description
- * @return {Suite}
- * @api private
- */
-
- findLocalSharedBehavior : function(description) {
- var behavior,
- currentSuite = JSpec.currentSuite.suite
- while (currentSuite)
- if (behavior = find(currentSuite.suites, JSpec.suiteDescriptionPredicate(description)))
- return behavior
- else
- currentSuite = currentSuite.suite
- },
-
- /**
- * Find a shared example suite within the global
- * scope by its description or name.
- *
- * @param {string} description
- * @return {Suite}
- * @api private
- */
-
- findGlobalSharedBehavior : function(description) {
- return find(JSpec.suites, JSpec.suiteDescriptionPredicate(description))
- },
-
- /**
- * Build a predicate that will match a suite based on name or description
- *
- * @param {string} description
- * @return {function}
- * @api private
- */
-
- suiteDescriptionPredicate : function(description) {
- return function(suite){
- return suite.name === description ||
- suite.description === description
- }
- },
-
- /**
- * Share behaviors (specs) of the given suite with
- * the current suite.
- *
- * @param {string} description
- * @api public
- */
-
- shareBehaviorsOf : function(description) {
- var suite = JSpec.findSharedBehavior(description)
- if (suite)
- JSpec.evalBody(suite.body)
- else
- throw new Error("failed to find shared behaviors named `" + description + "'")
- },
-
-
- /**
- * Convert arguments to an array.
- *
- * @param {object} arguments
- * @param {int} offset
- * @return {array}
- * @api public
- */
-
- toArray : function(arguments, offset) {
- return Array.prototype.slice.call(arguments, offset || 0)
- },
-
- /**
- * Return ANSI-escaped colored string.
- *
- * @param {string} string
- * @param {string} color
- * @return {string}
- * @api public
- */
-
- color : function(string, color) {
- if (option('disableColors')) {
- return string
- } else {
- return "\u001B[" + {
- bold : 1,
- black : 30,
- red : 31,
- green : 32,
- yellow : 33,
- blue : 34,
- magenta : 35,
- cyan : 36,
- white : 37
- }[color] + 'm' + string + "\u001B[0m"
- }
- },
-
- /**
- * Default matcher message callback.
- *
- * @api private
- */
-
- defaultMatcherMessage : function(actual, expected, negate, name) {
- return 'expected ' + puts(actual) + ' to ' +
- (negate ? 'not ' : '') +
- name.replace(/_/g, ' ') +
- ' ' + (expected.length > 1 ?
- puts.apply(this, expected.slice(1)) :
- '')
- },
-
- /**
- * Normalize a matcher message.
- *
- * When no messge callback is present the defaultMatcherMessage
- * will be assigned, will suffice for most matchers.
- *
- * @param {hash} matcher
- * @return {hash}
- * @api public
- */
-
- normalizeMatcherMessage : function(matcher) {
- if (typeof matcher.message != 'function')
- matcher.message = this.defaultMatcherMessage
- return matcher
- },
-
- /**
- * Normalize a matcher body
- *
- * This process allows the following conversions until
- * the matcher is in its final normalized hash state.
- *
- * - '==' becomes 'actual == expected'
- * - 'actual == expected' becomes 'return actual == expected'
- * - function(actual, expected) { return actual == expected } becomes
- * { match : function(actual, expected) { return actual == expected }}
- *
- * @param {mixed} body
- * @return {hash}
- * @api public
- */
-
- normalizeMatcherBody : function(body) {
- var captures
- switch (body.constructor) {
- case String:
- if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
- if (body.length < 4) body = 'actual ' + body + ' expected'
- return { match: function(actual, expected) { return eval(body) }}
-
- case Function:
- return { match: body }
-
- default:
- return body
- }
- },
-
- /**
- * Get option value. This method first checks if
- * the option key has been set via the query string,
- * otherwise returning the options hash value.
- *
- * @param {string} key
- * @return {mixed}
- * @api public
- */
-
- option : function(key) {
- return (value = query(key)) !== null ? value :
- JSpec.options[key] || null
- },
-
- /**
- * Check if object _a_, is equal to object _b_.
- *
- * @param {object} a
- * @param {object} b
- * @return {bool}
- * @api private
- */
-
- equal: function(a, b) {
- if (typeof a != typeof b) return
- if (a === b) return true
- if (a instanceof RegExp)
- return a.toString() === b.toString()
- if (a instanceof Date)
- return Number(a) === Number(b)
- if (typeof a != 'object') return
- if (a.length !== undefined)
- if (a.length !== b.length) return
- else
- for (var i = 0, len = a.length; i < len; ++i)
- if (!equal(a[i], b[i]))
- return
- for (var key in a)
- if (!equal(a[key], b[key]))
- return
- return true
- },
-
- /**
- * Return last element of an array.
- *
- * @param {array} array
- * @return {object}
- * @api public
- */
-
- last : function(array) {
- return array[array.length - 1]
- },
-
- /**
- * Convert object(s) to a print-friend string.
- *
- * @param {...} object
- * @return {string}
- * @api public
- */
-
- puts : function(object) {
- if (arguments.length > 1)
- return map(toArray(arguments), function(arg){
- return puts(arg)
- }).join(', ')
- if (object === undefined) return 'undefined'
- if (object === null) return 'null'
- if (object === true) return 'true'
- if (object === false) return 'false'
- if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
- if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector)
- if (object.jquery) return object.get(0).outerHTML
- if (object.nodeName) return object.outerHTML
- switch (object.constructor) {
- case Function: return object.name || object
- case String:
- return '"' + object
- .replace(/"/g, '\\"')
- .replace(/\n/g, '\\n')
- .replace(/\t/g, '\\t')
- + '"'
- case Array:
- return inject(object, '[', function(b, v){
- return b + ', ' + puts(v)
- }).replace('[,', '[') + ' ]'
- case Object:
- object.__hit__ = true
- return inject(object, '{', function(b, k, v) {
- if (k == '__hit__') return b
- return b + ', ' + k + ': ' + (v && v.__hit__ ? '<circular reference>' : puts(v))
- }).replace('{,', '{') + ' }'
- default:
- return object.toString()
- }
- },
-
- /**
- * Parse an XML String and return a 'document'.
- *
- * @param {string} text
- * @return {document}
- * @api public
- */
-
- parseXML : function(text) {
- var xmlDoc
- if (window.DOMParser)
- xmlDoc = (new DOMParser()).parseFromString(text, "text/xml")
- else {
- xmlDoc = new ActiveXObject("Microsoft.XMLDOM")
- xmlDoc.async = "false"
- xmlDoc.loadXML(text)
- }
- return xmlDoc
- },
-
- /**
- * Escape HTML.
- *
- * @param {string} html
- * @return {string}
- * @api public
- */
-
- escape : function(html) {
- return html.toString()
- .replace(/&/gmi, '&')
- .replace(/"/gmi, '"')
- .replace(/>/gmi, '>')
- .replace(/</gmi, '<')
- },
-
- /**
- * Perform an assertion without reporting.
- *
- * This method is primarily used for internal
- * matchers in order retain DRYness. May be invoked
- * like below:
- *
- * does('foo', 'eql', 'foo')
- * does([1,2], 'include', 1, 2)
- *
- * External hooks are not run for internal assertions
- * performed by does().
- *
- * @param {mixed} actual
- * @param {string} matcher
- * @param {...} expected
- * @return {mixed}
- * @api private
- */
-
- does : function(actual, matcher, expected) {
- var assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, toArray(arguments, 2))
- return assertion.run().result
- },
-
- /**
- * Perform an assertion.
- *
- * expect(true).to('be', true)
- * expect('foo').not_to('include', 'bar')
- * expect([1, [2]]).to('include', 1, [2])
- *
- * @param {mixed} actual
- * @return {hash}
- * @api public
- */
-
- expect : function(actual) {
- function assert(matcher, args, negate) {
- var expected = toArray(args, 1)
- matcher.negate = negate
- var assertion = new JSpec.Assertion(matcher, actual, expected, negate)
- hook('beforeAssertion', assertion)
- if (matcher.defer) assertion.run()
- else JSpec.currentSpec.assertions.push(assertion.run().report()), hook('afterAssertion', assertion)
- return assertion.result
- }
-
- function to(matcher) {
- return assert(matcher, arguments, false)
- }
-
- function not_to(matcher) {
- return assert(matcher, arguments, true)
- }
-
- return {
- to : to,
- should : to,
- not_to: not_to,
- should_not : not_to
- }
- },
-
- /**
- * Strim whitespace or chars.
- *
- * @param {string} string
- * @param {string} chars
- * @return {string}
- * @api public
- */
-
- strip : function(string, chars) {
- return string.
- replace(new RegExp('[' + (chars || '\\s') + ']*$'), '').
- replace(new RegExp('^[' + (chars || '\\s') + ']*'), '')
- },
-
- /**
- * Call an iterator callback with arguments a, or b
- * depending on the arity of the callback.
- *
- * @param {function} callback
- * @param {mixed} a
- * @param {mixed} b
- * @return {mixed}
- * @api private
- */
-
- callIterator : function(callback, a, b) {
- return callback.length == 1 ? callback(b) : callback(a, b)
- },
-
- /**
- * Extend an object with another.
- *
- * @param {object} object
- * @param {object} other
- * @api public
- */
-
- extend : function(object, other) {
- each(other, function(property, value){
- object[property] = value
- })
- },
-
- /**
- * Iterate an object, invoking the given callback.
- *
- * @param {hash, array} object
- * @param {function} callback
- * @return {JSpec}
- * @api public
- */
-
- each : function(object, callback) {
- if (object.constructor == Array)
- for (var i = 0, len = object.length; i < len; ++i)
- callIterator(callback, i, object[i])
- else
- for (var key in object)
- if (object.hasOwnProperty(key))
- callIterator(callback, key, object[key])
- },
-
- /**
- * Iterate with memo.
- *
- * @param {hash, array} object
- * @param {object} memo
- * @param {function} callback
- * @return {object}
- * @api public
- */
-
- inject : function(object, memo, callback) {
- each(object, function(key, value){
- memo = (callback.length == 2 ?
- callback(memo, value):
- callback(memo, key, value)) ||
- memo
- })
- return memo
- },
-
- /**
- * Destub _object_'s _method_. When no _method_ is passed
- * all stubbed methods are destubbed. When no arguments
- * are passed every object found in JSpec.stubbed will be
- * destubbed.
- *
- * @param {mixed} object
- * @param {string} method
- * @api public
- */
-
- destub : function(object, method) {
- var captures
- if (method) {
- if (object['__prototype__' + method])
- delete object[method]
- else
- object[method] = object['__original__' + method]
- delete object['__prototype__' + method]
- delete object['__original____' + method]
- }
- else if (object) {
- for (var key in object)
- if (captures = key.match(/^(?:__prototype__|__original__)(.*)/))
- destub(object, captures[1])
- }
- else
- while (JSpec.stubbed.length)
- destub(JSpec.stubbed.shift())
- },
-
- /**
- * Stub _object_'s _method_.
- *
- * stub(foo, 'toString').and_return('bar')
- *
- * @param {mixed} object
- * @param {string} method
- * @return {hash}
- * @api public
- */
-
- stub : function(object, method) {
- hook('stubbing', object, method)
- JSpec.stubbed.push(object)
- var type = object.hasOwnProperty(method) ? '__original__' : '__prototype__'
- object[type + method] = object[method]
- object[method] = function(){}
- return {
- and_return : function(value) {
- if (typeof value == 'function') object[method] = value
- else object[method] = function(){ return value }
- }
- }
- },
-
- /**
- * Map callback return values.
- *
- * @param {hash, array} object
- * @param {function} callback
- * @return {array}
- * @api public
- */
-
- map : function(object, callback) {
- return inject(object, [], function(memo, key, value){
- memo.push(callIterator(callback, key, value))
- })
- },
-
- /**
- * Returns the first matching expression or null.
- *
- * @param {hash, array} object
- * @param {function} callback
- * @return {mixed}
- * @api public
- */
-
- any : function(object, callback) {
- return inject(object, null, function(state, key, value){
- if (state == undefined)
- return callIterator(callback, key, value) ? value : state
- })
- },
-
- /**
- * Returns an array of values collected when the callback
- * given evaluates to true.
- *
- * @param {hash, array} object
- * @return {function} callback
- * @return {array}
- * @api public
- */
-
- select : function(object, callback) {
- return inject(object, [], function(selected, key, value){
- if (callIterator(callback, key, value))
- selected.push(value)
- })
- },
-
- /**
- * Define matchers.
- *
- * @param {hash} matchers
- * @api public
- */
-
- addMatchers : function(matchers) {
- each(matchers, function(name, body){
- JSpec.addMatcher(name, body)
- })
- },
-
- /**
- * Define a matcher.
- *
- * @param {string} name
- * @param {hash, function, string} body
- * @api public
- */
-
- addMatcher : function(name, body) {
- hook('addingMatcher', name, body)
- if (name.indexOf(' ') != -1) {
- var matchers = name.split(/\s+/)
- var prefix = matchers.shift()
- each(matchers, function(name) {
- JSpec.addMatcher(prefix + '_' + name, body(name))
- })
- }
- this.matchers[name] = this.normalizeMatcherMessage(this.normalizeMatcherBody(body))
- this.matchers[name].name = name
- },
-
- /**
- * Add a root suite to JSpec.
- *
- * @param {string} description
- * @param {body} function
- * @api public
- */
-
- describe : function(description, body) {
- var suite = new JSpec.Suite(description, body, false)
- hook('addingSuite', suite)
- this.allSuites.push(suite)
- this.suites.push(suite)
- },
-
- /**
- * Add a shared example suite to JSpec.
- *
- * @param {string} description
- * @param {body} function
- * @api public
- */
-
- shared_behaviors_for : function(description, body) {
- var suite = new JSpec.Suite(description, body, true)
- hook('addingSuite', suite)
- this.allSuites.push(suite)
- this.suites.push(suite)
- },
-
- /**
- * Return the contents of a function body.
- *
- * @param {function} body
- * @return {string}
- * @api public
- */
-
- contentsOf : function(body) {
- return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
- },
-
- /**
- * Evaluate a JSpec capture body.
- *
- * @param {function} body
- * @param {string} errorMessage (optional)
- * @return {Type}
- * @api private
- */
-
- evalBody : function(body, errorMessage) {
- var dsl = this.DSL || this.DSLs.snake
- var matchers = this.matchers
- var context = this.context || this.defaultContext
- var contents = this.contentsOf(body)
- hook('evaluatingBody', dsl, matchers, context, contents)
- with (dsl){ with (context) { with (matchers) { eval(contents) }}}
- },
-
- /**
- * Pre-process a string of JSpec.
- *
- * @param {string} input
- * @return {string}
- * @api private
- */
-
- preprocess : function(input) {
- if (typeof input != 'string') return
- input = hookImmutable('preprocessing', input)
- return input.
- replace(/\t/g, ' ').
- replace(/\r\n|\n|\r/g, '\n').
- split('__END__')[0].
- replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
- replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
- replace(/shared_behaviors_for\s+(.*?)$/gm, 'shared_behaviors_for($1, function(){').
- replace(/^\s+it\s+(.*?)$/gm, ' it($1, function(){').
- replace(/^ *(before_nested|after_nested|before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
- replace(/^\s*end(?=\s|$)/gm, '});').
- replace(/-\{/g, 'function(){').
- replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
- replace(/\.should([_\.]not)?[_\.](\w+)(?: |;|$)(.*)$/gm, '.should$1_$2($3)').
- replace(/([\/\s]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)\s*;?$/gm, '$1 expect($2).$3($4, $5)').
- replace(/, \)/g, ')').
- replace(/should\.not/g, 'should_not')
- },
-
- /**
- * Create a range string which can be evaluated to a native array.
- *
- * @param {int} start
- * @param {int} end
- * @return {string}
- * @api public
- */
-
- range : function(start, end) {
- var current = parseInt(start), end = parseInt(end), values = [current]
- if (end > current) while (++current <= end) values.push(current)
- else while (--current >= end) values.push(current)
- return '[' + values + ']'
- },
-
- /**
- * Report on the results.
- *
- * @api public
- */
-
- report : function() {
- this.duration = Number(new Date) - this.start
- hook('reporting', JSpec.options)
- new (JSpec.options.reporter || JSpec.reporters.DOM)(JSpec, JSpec.options)
- },
-
- /**
- * Run the spec suites. Options are merged
- * with JSpec options when present.
- *
- * @param {hash} options
- * @return {JSpec}
- * @api public
- */
-
- run : function(options) {
- if (any(hook('running'), haveStopped)) return this
- if (options) extend(this.options, options)
- this.start = Number(new Date)
- each(this.suites, function(suite) { JSpec.runSuite(suite) })
- return this
- },
-
- /**
- * Run a suite.
- *
- * @param {Suite} suite
- * @api public
- */
-
- runSuite : function(suite) {
- if (!suite.isShared())
- {
- this.currentSuite = suite
- this.evalBody(suite.body)
- suite.ran = true
- hook('beforeSuite', suite), suite.hook('before'), suite.hook('before_nested')
- each(suite.specs, function(spec) {
- hook('beforeSpec', spec)
- suite.hook('before_each')
- JSpec.runSpec(spec)
- hook('afterSpec', spec)
- suite.hook('after_each')
- })
- if (suite.hasSuites()) {
- each(suite.suites, function(suite) {
- JSpec.runSuite(suite)
- })
- }
- hook('afterSuite', suite), suite.hook('after_nested'), suite.hook('after')
- this.stats.suitesFinished++
- }
- },
-
- /**
- * Report a failure for the current spec.
- *
- * @param {string} message
- * @api public
- */
-
- fail : function(message) {
- JSpec.currentSpec.fail(message)
- },
-
- /**
- * Report a passing assertion for the current spec.
- *
- * @param {string} message
- * @api public
- */
-
- pass : function(message) {
- JSpec.currentSpec.pass(message)
- },
-
- /**
- * Run a spec.
- *
- * @param {Spec} spec
- * @api public
- */
-
- runSpec : function(spec) {
- this.currentSpec = spec
- try { this.evalBody(spec.body) }
- catch (e) { fail(e) }
- spec.runDeferredAssertions()
- destub()
- this.stats.specsFinished++
- this.stats.assertions += spec.assertions.length
- },
-
- /**
- * Require a dependency, with optional message.
- *
- * @param {string} dependency
- * @param {string} message (optional)
- * @return {JSpec}
- * @api public
- */
-
- requires : function(dependency, message) {
- hook('requiring', dependency, message)
- try { eval(dependency) }
- catch (e) { throw 'JSpec depends on ' + dependency + ' ' + message }
- return this
- },
-
- /**
- * Query against the current query strings keys
- * or the queryString specified.
- *
- * @param {string} key
- * @param {string} queryString
- * @return {string, null}
- * @api private
- */
-
- query : function(key, queryString) {
- var queryString = (queryString || (main.location ? main.location.search : null) || '').substring(1)
- return inject(queryString.split('&'), null, function(value, pair){
- parts = pair.split('=')
- return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value
- })
- },
-
- /**
- * Ad-hoc POST request for JSpec server usage.
- *
- * @param {string} uri
- * @param {string} data
- * @api private
- */
-
- post : function(uri, data) {
- if (any(hook('posting', uri, data), haveStopped)) return
- var request = this.xhr()
- request.open('POST', uri, false)
- request.setRequestHeader('Content-Type', 'application/json')
- request.send(JSpec.JSON.encode(data))
- },
-
- /**
- * Instantiate an XMLHttpRequest.
- *
- * Here we utilize IE's lame ActiveXObjects first which
- * allow IE access serve files via the file: protocol, otherwise
- * we then default to XMLHttpRequest.
- *
- * @return {XMLHttpRequest, ActiveXObject}
- * @api private
- */
-
- xhr : function() {
- return this.ieXhr() || new JSpec.request
- },
-
- /**
- * Return Microsoft piece of crap ActiveXObject.
- *
- * @return {ActiveXObject}
- * @api public
- */
-
- ieXhr : function() {
- function object(str) {
- try { return new ActiveXObject(str) } catch(e) {}
- }
- return object('Msxml2.XMLHTTP.6.0') ||
- object('Msxml2.XMLHTTP.3.0') ||
- object('Msxml2.XMLHTTP') ||
- object('Microsoft.XMLHTTP')
- },
-
- /**
- * Check for HTTP request support.
- *
- * @return {bool}
- * @api private
- */
-
- hasXhr : function() {
- return JSpec.request || 'ActiveXObject' in main
- },
-
- /**
- * Try loading _file_ returning the contents
- * string or null. Chain to locate / read a file.
- *
- * @param {string} file
- * @return {string}
- * @api public
- */
-
- tryLoading : function(file) {
- try { return JSpec.load(file) } catch (e) {}
- },
-
- /**
- * Load a _file_'s contents.
- *
- * @param {string} file
- * @param {function} callback
- * @return {string}
- * @api public
- */
-
- load : function(file, callback) {
- if (any(hook('loading', file), haveStopped)) return
- if ('readFile' in main)
- return readFile(file)
- else if (this.hasXhr()) {
- var request = this.xhr()
- request.open('GET', file, false)
- request.send(null)
- if (request.readyState == 4 &&
- (request.status == 0 ||
- request.status.toString().charAt(0) == 2))
- return request.responseText
- }
- else
- throw new Error("failed to load `" + file + "'")
- },
-
- /**
- * Load, pre-process, and evaluate a file.
- *
- * @param {string} file
- * @param {JSpec}
- * @api public
- */
-
- exec : function(file) {
- if (any(hook('executing', file), haveStopped)) return this
- eval('with (JSpec){' + this.preprocess(this.load(file)) + '}')
- return this
- }
- }
-
- // --- Node.js support
-
- if (typeof GLOBAL === 'object' && typeof exports === 'object') {
- var fs = require('fs')
- quit = process.exit
- print = require('sys').puts
- readFile = function(file){
- return fs.readFileSync(file).toString('utf8')
- }
- }
-
- // --- Utility functions
-
- var main = this,
- find = JSpec.any,
- utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \
- error escape extend puts query strip color does addMatchers callIterator toArray equal'.split(/\s+/)
- while (utils.length) eval('var ' + utils[0] + ' = JSpec.' + utils.shift())
- if (!main.setTimeout) main.setTimeout = function(callback){ callback() }
-
- // --- Matchers
-
- addMatchers({
- equal : "===",
- eql : "equal(actual, expected)",
- be : "alias equal",
- be_greater_than : ">",
- be_less_than : "<",
- be_at_least : ">=",
- be_at_most : "<=",
- be_a : "actual.constructor == expected",
- be_an : "alias be_a",
- be_an_instance_of : "actual instanceof expected",
- be_null : "actual == null",
- be_true : "actual == true",
- be_false : "actual == false",
- be_undefined : "typeof actual == 'undefined'",
- be_type : "typeof actual == expected",
- match : "typeof actual == 'string' ? actual.match(expected) : false",
- respond_to : "typeof actual[expected] == 'function'",
- have_length : "actual.length == expected",
- be_within : "actual >= expected[0] && actual <= last(expected)",
- have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
-
- receive : { defer : true, match : function(actual, method, times) {
- var proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
- JSpec.currentSpec.assertions.push(proxy)
- return proxy
- }},
-
- be_empty : function(actual) {
- if (actual.constructor == Object && actual.length == undefined)
- for (var key in actual)
- return false;
- return !actual.length
- },
-
- include : function(actual) {
- for (var state = true, i = 1; i < arguments.length; i++) {
- var arg = arguments[i]
- switch (actual.constructor) {
- case String:
- case Number:
- case RegExp:
- case Function:
- state = actual.toString().indexOf(arg) !== -1
- break
-
- case Object:
- state = arg in actual
- break
-
- case Array:
- state = any(actual, function(value){ return equal(value, arg) })
- break
- }
- if (!state) return false
- }
- return true
- },
-
- throw_error : { match : function(actual, expected, message) {
- try { actual() }
- catch (e) {
- this.e = e
- var assert = function(arg) {
- switch (arg.constructor) {
- case RegExp : return arg.test(e.message || e.toString())
- case String : return arg == (e.message || e.toString())
- case Function : return e instanceof arg || e.name == arg.name
- }
- }
- return message ? assert(expected) && assert(message) :
- expected ? assert(expected) :
- true
- }
- }, message : function(actual, expected, negate) {
- // TODO: refactor when actual is not in expected [0]
- var message_for = function(i) {
- if (expected[i] == undefined) return 'exception'
- switch (expected[i].constructor) {
- case RegExp : return 'exception matching ' + puts(expected[i])
- case String : return 'exception of ' + puts(expected[i])
- case Function : return expected[i].name || 'Error'
- }
- }
- var exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
- return 'expected ' + exception + (negate ? ' not ' : '' ) +
- ' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was')
- }},
-
- have : function(actual, length, property) {
- return actual[property] == null ? false : actual[property].length == length
- },
-
- have_at_least : function(actual, length, property) {
- return actual[property] == null ? (length === 0) : actual[property].length >= length
- },
-
- have_at_most :function(actual, length, property) {
- return actual[property] == null || actual[property].length <= length
- },
-
- have_within : function(actual, range, property) {
- var length = actual[property] == undefined ? 0 : actual[property].length
- return length >= range.shift() && length <= range.pop()
- },
-
- have_prop : function(actual, property, value) {
- var actualVal = actual[property], actualType = typeof actualVal
- return (actualType == 'function' || actualType == 'undefined') ? false :
- typeof value === 'undefined' ||
- does(actual[property],'eql',value)
- },
-
- have_property : function(actual, property, value) {
- var actualVal = actual[property], actualType = typeof actualVal
- return (actualType == 'function' || actualType == 'undefined') ? false :
- typeof value === 'undefined' ||
- value === actualVal
- }
- })
-
-})()
+++ /dev/null
-
-// JSpec - node - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
-
-JSpec
-.include({
- name: 'node',
-
- // --- Matchers
-
- matchers : {
- have_enumerable_property: 'actual.propertyIsEnumerable(expected)',
- have_writable_property: 'Object.getOwnPropertyDescriptor(actual, expected).writable === true',
- have_configurable_property: 'Object.getOwnPropertyDescriptor(actual, expected).configurable === true',
- have_keys: 'does(Object.keys(actual), "eql", expected)',
- have_prototype: 'Object.getPrototypeOf(actual) === expected'
- }
-})
-
+++ /dev/null
-
-// JSpec - Shell - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
-
-;(function(){
-
- var _quit = quit
-
- Shell = {
-
- // --- Global
-
- main: this,
-
- // --- Commands
-
- commands: {
- quit: ['Terminate the shell', function(){ _quit() }],
- exit: ['Terminate the shell', function(){ _quit() }],
- p: ['Inspect an object', function(o){ return o.toSource() }]
- },
-
- /**
- * Start the interactive shell.
- *
- * @api public
- */
-
- start : function() {
- for (var name in this.commands)
- if (this.commands.hasOwnProperty(name))
- this.commands[name][1].length ?
- this.main[name] = this.commands[name][1] :
- this.main.__defineGetter__(name, this.commands[name][1])
- }
- }
-
- Shell.start()
-
-})()
\ No newline at end of file
+++ /dev/null
-
-// JSpec - Mock Timers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
-
-;(function(){
-
- /**
- * Version.
- */
-
- mockTimersVersion = '1.0.2'
-
- /**
- * Localized timer stack.
- */
-
- var timers = []
-
- /**
- * Set mock timeout with _callback_ and timeout of _ms_.
- *
- * @param {function} callback
- * @param {int} ms
- * @return {int}
- * @api public
- */
-
- setTimeout = function(callback, ms) {
- var id
- return id = setInterval(function(){
- callback()
- clearInterval(id)
- }, ms)
- }
-
- /**
- * Set mock interval with _callback_ and interval of _ms_.
- *
- * @param {function} callback
- * @param {int} ms
- * @return {int}
- * @api public
- */
-
- setInterval = function(callback, ms) {
- callback.step = ms, callback.current = callback.last = 0
- return timers[timers.length] = callback, timers.length
- }
-
- /**
- * Destroy timer with _id_.
- *
- * @param {int} id
- * @return {bool}
- * @api public
- */
-
- clearInterval = clearTimeout = function(id) {
- return delete timers[--id]
- }
-
- /**
- * Reset timers.
- *
- * @return {array}
- * @api public
- */
-
- resetTimers = function() {
- return timers = []
- }
-
- /**
- * Increment each timers internal clock by _ms_.
- *
- * @param {int} ms
- * @api public
- */
-
- tick = function(ms) {
- for (var i = 0, len = timers.length; i < len; ++i)
- if (timers[i] && (timers[i].current += ms))
- if (timers[i].current - timers[i].last >= timers[i].step) {
- var times = Math.floor((timers[i].current - timers[i].last) / timers[i].step)
- var remainder = (timers[i].current - timers[i].last) % timers[i].step
- timers[i].last = timers[i].current - remainder
- while (times-- && timers[i]) timers[i]()
- }
- }
-
-})()
\ No newline at end of file
+++ /dev/null
-
-// JSpec - XHR - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
-
-(function(){
-
- var lastRequest
-
- // --- Original XMLHttpRequest
-
- var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
- XMLHttpRequest :
- function(){}
- var OriginalActiveXObject = 'ActiveXObject' in this ?
- ActiveXObject :
- undefined
-
- // --- MockXMLHttpRequest
-
- var MockXMLHttpRequest = function() {
- this.requestHeaders = {}
- }
-
- MockXMLHttpRequest.prototype = {
- status: 0,
- async: true,
- readyState: 0,
- responseXML: null,
- responseText: '',
- abort: function(){},
- onreadystatechange: function(){},
-
- /**
- * Return response headers hash.
- */
-
- getAllResponseHeaders : function(){
- return JSpec.inject(this.responseHeaders, '', function(buf, key, val){
- return buf + key + ': ' + val + '\r\n'
- })
- },
-
- /**
- * Return case-insensitive value for header _name_.
- */
-
- getResponseHeader : function(name) {
- return this.responseHeaders[name.toLowerCase()]
- },
-
- /**
- * Set case-insensitive _value_ for header _name_.
- */
-
- setRequestHeader : function(name, value) {
- this.requestHeaders[name.toLowerCase()] = value
- },
-
- /**
- * Open mock request.
- */
-
- open : function(method, url, async, user, password) {
- this.user = user
- this.password = password
- this.url = url
- this.readyState = 1
- this.method = method.toUpperCase()
- if (async != undefined) this.async = async
- if (this.async) this.onreadystatechange()
- },
-
- /**
- * Send request _data_.
- */
-
- send : function(data) {
- var self = this
- this.data = data
- this.readyState = 4
- if (this.method == 'HEAD') this.responseText = null
- this.responseHeaders['content-length'] = (this.responseText || '').length
- if(this.async) this.onreadystatechange()
- this.populateResponseXML()
- lastRequest = function(){
- return self
- }
- },
-
- /**
- * Parse request body and populate responseXML if response-type is xml
- * Based on the standard specification : http://www.w3.org/TR/XMLHttpRequest/
- */
- populateResponseXML: function() {
- var type = this.getResponseHeader("content-type")
- if (!type || !this.responseText || !type.match(/(text\/xml|application\/xml|\+xml$)/g))
- return
- this.responseXML = JSpec.parseXML(this.responseText)
- }
- }
-
- // --- Response status codes
-
- JSpec.statusCodes = {
- 100: 'Continue',
- 101: 'Switching Protocols',
- 200: 'OK',
- 201: 'Created',
- 202: 'Accepted',
- 203: 'Non-Authoritative Information',
- 204: 'No Content',
- 205: 'Reset Content',
- 206: 'Partial Content',
- 300: 'Multiple Choice',
- 301: 'Moved Permanently',
- 302: 'Found',
- 303: 'See Other',
- 304: 'Not Modified',
- 305: 'Use Proxy',
- 307: 'Temporary Redirect',
- 400: 'Bad Request',
- 401: 'Unauthorized',
- 402: 'Payment Required',
- 403: 'Forbidden',
- 404: 'Not Found',
- 405: 'Method Not Allowed',
- 406: 'Not Acceptable',
- 407: 'Proxy Authentication Required',
- 408: 'Request Timeout',
- 409: 'Conflict',
- 410: 'Gone',
- 411: 'Length Required',
- 412: 'Precondition Failed',
- 413: 'Request Entity Too Large',
- 414: 'Request-URI Too Long',
- 415: 'Unsupported Media Type',
- 416: 'Requested Range Not Satisfiable',
- 417: 'Expectation Failed',
- 422: 'Unprocessable Entity',
- 500: 'Internal Server Error',
- 501: 'Not Implemented',
- 502: 'Bad Gateway',
- 503: 'Service Unavailable',
- 504: 'Gateway Timeout',
- 505: 'HTTP Version Not Supported'
- }
-
- /**
- * Mock XMLHttpRequest requests.
- *
- * mockRequest().and_return('some data', 'text/plain', 200, { 'X-SomeHeader' : 'somevalue' })
- *
- * @return {hash}
- * @api public
- */
-
- function mockRequest() {
- return { and_return : function(body, type, status, headers) {
- XMLHttpRequest = MockXMLHttpRequest
- ActiveXObject = false
- status = status || 200
- headers = headers || {}
- headers['content-type'] = type
- JSpec.extend(XMLHttpRequest.prototype, {
- responseText: body,
- responseHeaders: headers,
- status: status,
- statusText: JSpec.statusCodes[status]
- })
- }}
- }
-
- /**
- * Unmock XMLHttpRequest requests.
- *
- * @api public
- */
-
- function unmockRequest() {
- XMLHttpRequest = OriginalXMLHttpRequest
- ActiveXObject = OriginalActiveXObject
- }
-
- JSpec.include({
- name: 'Mock XHR',
-
- // --- Utilities
-
- utilities : {
- mockRequest: mockRequest,
- unmockRequest: unmockRequest
- },
-
- // --- Hooks
-
- afterSpec : function() {
- unmockRequest()
- },
-
- // --- DSLs
-
- DSLs : {
- snake : {
- mock_request: mockRequest,
- unmock_request: unmockRequest,
- last_request: function(){ return lastRequest() }
- }
- }
-
- })
-})()
+++ /dev/null
-
-require.paths.unshift('spec', './spec/lib', 'lib')
-require('jspec')
-require('unit/spec.helper')
-haml = require('haml')
-
-JSpec
- .exec('spec/unit/spec.js')
- .run({ reporter: JSpec.reporters.Terminal, fixturePath: 'spec/fixtures', failuresOnly: true })
- .report()
+++ /dev/null
-
-describe 'haml'
- describe '.version'
- it 'should be a triplet'
- haml.version.should.match(/^\d+\.\d+\.\d+$/)
- end
- end
-
- describe '.render()'
- before
- assertAs = function(name, type, options) {
- var str = fixture(name + '.haml')
- try {
- var html = haml.render(str, options).trim(),
- expected = fixture(name + '.' + type).trim()
- if (html === expected)
- pass()
- else
- fail('got:\n' + html + '\n\nexpected:\n' + expected)
- } catch (err) {
- if (err instanceof haml.HamlError) {
- throw err
- } else {
- fail('\n:' + err.stack + '\n')
- }
- }
- }
- assert = function(name, options) {
- assertAs(name, 'html', options, 'CRLF', '\r\n')
- }
- assertXML = function(name, options) {
- assertAs(name, 'xml', options, 'CRLF', '\r\n')
- }
- end
-
- it 'should allow passing of a context object'
- assert('context', { context: 'yay' })
- end
-
- it 'should allow passing of literals'
- assert('literals', { locals: { user: 'tj' }})
- end
-
- it 'should not fail on trailing indents'
- assert('trailing-indent')
- end
-
- it 'should add xml support via the "xml" option'
- assertXML('feed', { xml: true })
- end
-
- it 'should support xml namespaces'
- assertXML('namespace')
- end
-
- it 'should utilize "filename" option when an error is thrown'
- try { assert('error', { filename: 'error.haml' }) }
- catch (err) {
- err.message.should.eql '(error.haml):3 invalid indentation; got 3, when previous was 1'
- }
- end
-
- it 'should default filename to "Haml" when an error is thrown'
- try { assert('error') }
- catch (err) {
- err.message.should.eql '(Haml):3 invalid indentation; got 3, when previous was 1'
- }
- end
-
- it 'should bitch when "cache" is true without a filename given'
- // -{ assert('tag.simple', { cache: true }) }.should.throw_error
- end
-
- it 'should pre-compiled and cache when "cache" is true'
- assert('tag.simple', { cache: true, filename: 'tag.simple.haml' })
- assert('tag.simple', { cache: true, filename: 'tag.simple.haml' })
- end
-
- it 'should support blank lines'
- assert('newlines')
- end
-
- describe '.class'
- it 'should output a div with the given class'
- assert('class')
- end
-
- it 'should work with several classes'
- assert('classes')
- end
- end
-
- describe '#id'
- it 'should output a div with the given id'
- assert('id')
- end
- end
-
- describe '%tag'
- it 'should work with no text or block'
- assert('tag.simple')
- end
-
- it 'should work with text'
- assert('tag.text')
- end
-
- it 'should work with block text'
- assert('tag.text.block')
- end
-
- it 'should work with blocks of text and tags'
- assert('tag.text.block.complex')
- end
-
- it 'should work with many classes / ids / attrs'
- assert('tag.complex')
- end
-
- it 'should allow empty tags'
- assert('tag.empty')
- end
- end
-
- describe '%tag.class'
- it 'should output tag with a class'
- assert('tag.class')
- end
-
- it 'should work with several classes'
- assert('tag.classes')
- end
-
- it 'should support self-closing tags'
- assert('tag.self-close')
- end
- end
-
- describe '%tag!='
- it 'should output the evaluated code'
- assert('tag.code')
- end
-
- it 'should not escape output'
- assert('tag.code.no-escape')
- end
- end
-
- describe '%tag='
- it 'should escape the evaluated code'
- assert('tag.escape')
- end
- end
-
- describe '%namespace:tag'
- it 'should output a tag with a namespace prefix'
- assert('namespace.tag')
- end
- end
-
- describe '{...}'
- it 'should be mapped as html attributes'
- assert('tag.attrs')
- end
-
- it 'should escape values'
- assert('tag.attrs.escape')
- end
-
- it 'should allow booleans'
- assert('tag.attrs.bools')
- end
- end
-
- describe '!!!'
- it 'should default the doctype to 1.0 transitional'
- assert('doctype')
- end
- end
-
- describe '!!! NAME'
- it 'should output a specific doctype'
- assert('doctype.xml')
- end
-
- it 'should be case-insensitive'
- assert('doctype.xml.case')
- end
- end
-
- describe 'nesting'
- it 'should work when nested downwards'
- assert('nesting.simple')
- end
-
- it 'should work when blocks outdent'
- assert('nesting.complex')
- end
- end
-
- describe '- code'
- it 'should work with if statements'
- assert('code.if')
- end
-
- it 'should work when nested'
- assert('code.nested')
- end
- end
-
- describe '- each'
- it 'should iterate'
- assert('code.each', { locals: { items: ['one', 'two', 'three'] }})
- assert('code.each.non-enumerable', { locals: { items: null }})
- end
-
- it 'should iterate objects'
- assert('code.each', { locals: { items: { 0: 'one', 1: 'two', 2: 'three' }}})
- assert('code.each.index', { locals: { items: { 0: 'one', 1: 'two', 2: 'three' }}})
- end
-
- it 'should iterate with index'
- assert('code.each.index', { locals: { items: ['one', 'two', 'three'] }})
- end
- end
-
- describe '= code'
- it 'should output evaluation'
- assert('code')
- end
- end
-
- describe '&= code'
- it 'should output evaluation while escaping html entities'
- assert('code.escape')
- end
- end
-
- describe '<literal></html>'
- it 'should remain intact'
- assert('html')
- end
- end
-
- describe '\\char'
- it 'should escape the character'
- assert('escape')
- end
- end
-
- describe '-#'
- it 'should become a silent comment'
- assert('comment')
- end
- end
-
- describe '/'
- it 'should comment out tags'
- assert('comment.tag')
- end
-
- it 'should comment out blocks'
- assert('comment.block')
- end
-
- it 'should comment out text'
- assert('comment.text')
- end
-
- it 'should work in blocks'
- assert('comment.text.complex')
- end
- end
-
- describe '/[]'
- it 'should insert conditional comment blocks'
- assert('comment.block.conditional')
- end
- end
-
- describe ':filter'
- describe 'plain'
- it 'should ignore haml specific characters'
- assert('filter.plain')
- end
- end
-
- describe 'cdata'
- it 'should wrap with CDATA tags'
- assert('filter.cdata')
- end
-
- it 'should retain whitespace'
- assert('filter.cdata.whitespace')
- end
- end
-
- describe 'javascript'
- it 'should wrap with <script> and CDATA tags'
- assert('filter.javascript')
- end
- end
- end
-
- describe 'bug fixes'
- it '#8 code block'
- assert('issue.#8', { locals: { items: ['foo', 'bar', 'baz'] }})
- end
-
- it '#10 Attributes should not need quotes'
- assert('issue.#10')
- end
- end
-
- end
-end
\ No newline at end of file
+++ /dev/null
-
-/**
- * Module dependencies.
- */
-
-var bm = require('./common'),
- haml = require('./haml/lib/haml'),
- fs = require('fs');
-
-var str = fs.readFileSync(__dirname + '/example2.haml', 'ascii');
-
-var n = bm.times;
-bm.start('haml compilation');
-while (n--) {
- haml.render(str, {
- locals: bm.locals
- });
-}
-bm.stop();
-
-var n = bm.times;
-bm.start('haml execution');
-while (n--) {
- haml.render(str, {
- locals: bm.locals,
- cache: true,
- filename: 'example2.haml'
- });
-}
-bm.stop();
\ No newline at end of file
+++ /dev/null
-
-/**
- * Module dependencies.
- */
-
-var bm = require('./common'),
- jade = require('../lib/jade'),
- fs = require('fs');
-
-var str = fs.readFileSync(__dirname + '/example-self.jade', 'ascii');
-var fn = jade.compile(str, { self: true });
-var n = bm.times;
-
-bm.start('jade self compilation');
-while (n--) {
- jade.render(str, {
- filename: 'example-self.jade'
- , self: true
- , locals: bm.locals
- });
-}
-bm.stop();
-
-var n = bm.times;
-
-bm.start('jade self execution');
-while (n--) {
- jade.render(str, {
- filename: 'example-self.jade'
- , self: true
- , cache: true
- , locals: bm.locals
- });
-}
-bm.stop();
-
-var n = bm.times;
-
-bm.start('jade compile()');
-while (n--) {
- fn(bm.locals);
-}
-bm.stop();
+++ /dev/null
-
-/**
- * Module dependencies.
- */
-
-var bm = require('./common'),
- jade = require('../lib/jade'),
- fs = require('fs');
-
-var str = fs.readFileSync(__dirname + '/example.jade', 'ascii');
-var fn = jade.compile(str);
-var n = bm.times;
-
-bm.start('jade compilation');
-while (n--) {
- jade.render(str, {
- filename: 'example.jade',
- locals: bm.locals
- });
-}
-bm.stop();
-
-var n = bm.times;
-
-bm.start('jade execution');
-while (n--) {
- jade.render(str, {
- filename: 'example.jade',
- cache: true,
- locals: bm.locals
- });
-}
-bm.stop();
-
-var n = bm.times;
-
-bm.start('jade compile()');
-while (n--) {
- fn(bm.locals);
-}
-bm.stop();
\ No newline at end of file
*/
var fs = require('fs')
+ , program = require('commander')
, path = require('path')
- , resolve = path.resolve
, basename = path.basename
, dirname = path.dirname
- , jade;
-
-try {
- jade = require('../lib/jade');
-} catch (err) {
- jade = require('jade');
-}
-
-/**
- * Arguments.
- */
-
-var args = process.argv.slice(2);
+ , resolve = path.resolve
+ , join = path.join
+ , mkdirp = require('mkdirp')
+ , jade = require('../');
-/**
- * Options javascript.
- */
+// jade options
var options = {};
-/**
- * Destination dir.
- */
-
-var dest;
-
-/**
- * Watcher hash.
- */
-
-var watchers;
+// options
+
+program
+ .version(jade.version)
+ .usage('[options] [dir|file ...]')
+ .option('-o, --obj <str>', 'javascript options object')
+ .option('-O, --out <dir>', 'output the compiled html to <dir>')
+
+program.on('--help', function(){
+ console.log(' Examples:');
+ console.log('');
+ console.log(' # translate jade the templates dir');
+ console.log(' $ jade templates');
+ console.log('');
+ console.log(' # create {foo,bar}.html');
+ console.log(' $ jade {foo,bar}.jade');
+ console.log('');
+ console.log(' # jade over stdio');
+ console.log(' $ jade < my.jade > my.html');
+ console.log('');
+ console.log(' # jade over stdio');
+ console.log(' $ echo "h1 Jade!" | jade');
+ console.log('');
+ console.log(' # foo, bar dirs rendering to /tmp');
+ console.log(' $ jade foo bar --out /tmp ');
+ console.log('');
+});
+
+program.parse(process.argv);
+
+// options given, parse them
+
+if (program.obj) options = eval('(' + program.obj + ')');
+
+// left-over args are file paths
+
+var files = program.args;
+
+// compile files
+
+if (files.length) {
+ console.log();
+ files.forEach(renderFile);
+ process.on('exit', console.log);
+// stdio
+} else {
+ stdin();
+}
/**
- * Usage information.
+ * Compile from stdin.
*/
-var usage = ''
- + '\n'
- + ' Usage: jade [options]\n'
- + ' [path ...]\n'
- + ' < in.jade > out.jade'
- + ' \n'
- + ' Options:\n'
- + ' -o, --options <str> JavaScript options object passed\n'
- + ' -h, --help Output help information\n'
- + ' -w, --watch Watch file(s) or folder(s) for changes and re-compile\n'
- + ' -v, --version Output jade version\n'
- + ' --out <dir> Output the compiled html to <dir>\n';
- + '\n';
-
-// Parse arguments
-
-var arg
- , files = [];
-while (args.length) {
- arg = args.shift();
- switch (arg) {
- case '-h':
- case '--help':
- console.log(usage);
- process.exit(1);
- case '-v':
- case '--version':
- console.log(jade.version);
- process.exit(1);
- case '-o':
- case '--options':
- var str = args.shift();
- if (str) {
- options = eval('(' + str + ')');
- } else {
- console.error('-o, --options requires a string.');
- process.exit(1);
- }
- break;
- case '-w':
- case '--watch':
- watchers = {};
- break;
- case '--out':
- dest = args.shift();
- break;
- default:
- files.push(arg);
- }
-}
-
-// Watching and no files passed - watch cwd
-if (watchers && !files.length) {
- fs.readdirSync(process.cwd()).forEach(processFile);
-// Process passed files
-} else if (files.length) {
- files.forEach(processFile);
-// Stdio
-} else {
+function stdin() {
var buf = '';
process.stdin.setEncoding('utf8');
- process.stdin.on('data', function(chunk){
- buf += chunk;
- }).on('end', function(){
- console.log(jade.render(buf, options));
+ process.stdin.on('data', function(chunk){ buf += chunk; });
+ process.stdin.on('end', function(){
+ var fn = jade.compile(buf, options);
+ process.stdout.write(fn(options));
}).resume();
}
/**
* Process the given path, compiling the jade files found.
- * Always walk the subdirectories.;
+ * Always walk the subdirectories.
*/
-function processFile(path) {
+function renderFile(path) {
+ var re = /\.jade$/;
fs.lstat(path, function(err, stat) {
if (err) throw err;
// Found jade file
- if (stat.isFile() && path.match(/\.jade$/)) {
- renderJade(path);
+ if (stat.isFile() && re.test(path)) {
+ fs.readFile(path, 'utf8', function(err, str){
+ if (err) throw err;
+ var fn = jade.compile(str, options);
+ path = path.replace(re, '.html');
+ if (program.out) path = join(program.out, basename(path));
+ var dir = resolve(dirname(path));
+ mkdirp(dir, 0755, function(err){
+ if (err) throw err;
+ fs.writeFile(path, fn(options), function(err){
+ if (err) throw err;
+ console.log(' \033[90mrendered \033[36m%s\033[0m', path);
+ });
+ });
+ });
// Found directory
} else if (stat.isDirectory()) {
fs.readdir(path, function(err, files) {
if (err) throw err;
files.map(function(filename) {
return path + '/' + filename;
- }).forEach(processFile);
+ }).forEach(renderFile);
});
}
});
}
-
-/**
- * Render jade
- */
-
-function renderJade(jadefile) {
- jade.renderFile(jadefile, options, function(err, html) {
- if (err) throw err;
- writeFile(jadefile, html);
- });
-}
-
-/**
- * mkdir -p implementation.
- */
-
-function mkdirs(path, fn) {
- var segs = dirname(path).split('/')
- , dir = '';
-
- (function next() {
- var seg = segs.shift();
- if (seg) {
- dir += seg + '/';
- fs.mkdir(dir, 0755, function(err){
- if (!err) return next();
- if ('EEXIST' == err.code) return next();
- });
- } else {
- fn();
- }
- })();
-}
-
-/**
- * Write the html output to a file.
- */
-
-function writeFile(src, html) {
- var path = src.replace('.jade', '.html');
- if (dest) path = dest + '/' + path;
- mkdirs(path, function(err){
- if (err) throw err;
- fs.writeFile(path, html, function(err) {
- if (err) throw err;
- console.log(' \033[90mcompiled\033[0m %s', path);
- watch(src, renderJade);
- });
- });
-}
-
-/**
- * Watch the given `file` and invoke `fn` when modified.
- */
-
-function watch(file, fn) {
- // not watching
- if (!watchers) return;
-
- // already watched
- if (watchers[file]) return;
-
- // watch the file itself
- watchers[file] = true;
- console.log(' \033[90mwatching\033[0m %s', file);
- fs.watchFile(file, { interval: 50 }, function(curr, prev){
- if (curr.mtime > prev.mtime) fn(file);
- });
-}
head
title My Site
- script(src='/javascripts/jquery.js')
- script(src='/javascripts/app.js')
\ No newline at end of file
+ include scripts
+ include style.css
\ No newline at end of file
--- /dev/null
+script(src='/javascripts/jquery.js')
+script(src='/javascripts/app.js')
--- /dev/null
+<style>
+ body {
+ padding: 50px;
+ }
+</style>
\ No newline at end of file
var Compiler = module.exports = function Compiler(node, options) {
this.options = options = options || {};
this.node = node;
-
this.hasCompiledDoctype = false;
this.hasCompiledTag = false;
+ this.pp = options.pretty || false;
+ this.debug = false !== options.compileDebug;
+ this.indents = 0;
if (options.doctype) this.setDoctype(options.doctype);
-
- this.pp = options.prettyprint || false;
- this.indentDepth = 0;
};
/**
compile: function(){
this.buf = ['var interp;'];
+ this.lastBufferedIdx = -1
this.visit(this.node);
return this.buf.join('\n');
},
buffer: function(str, esc){
if (esc) str = utils.escape(str);
- this.buf.push("buf.push('" + str + "');");
+
+ if (this.lastBufferedIdx == this.buf.length) {
+ this.lastBuffered += str;
+ this.buf[this.lastBufferedIdx - 1] = "buf.push('" + this.lastBuffered + "');"
+ } else {
+ this.buf.push("buf.push('" + str + "');");
+ this.lastBuffered = str;
+ this.lastBufferedIdx = this.buf.length;
+ }
},
/**
*/
line: function(node){
- if (node.instrumentLineNumber === false) return;
+ if (false === node.instrumentLineNumber) return;
this.buf.push('__.lineno = ' + node.line + ';');
},
*/
visit: function(node){
- this.line(node);
+ if (this.debug) this.line(node);
return this.visitNode(node);
},
|| node.constructor.toString().match(/function ([^(\s]+)()/)[1];
return this['visit' + name](node);
},
-
+
+ /**
+ * Visit literal `node`.
+ *
+ * @param {Literal} node
+ * @api public
+ */
+
+ visitLiteral: function(node){
+ var str = node.str.replace(/\n/g, '\\\\n');
+ this.buffer(str);
+ },
+
/**
* Visit all nodes in `block`.
*
*/
visitBlock: function(block){
- var len = len = block.nodes.length;
+ var len = block.nodes.length;
for (var i = 0; i < len; ++i) {
this.visit(block.nodes[i]);
}
if (this.doctype) this.buffer(this.doctype);
this.hasCompiledDoctype = true;
},
-
+
+ /**
+ * Visit `mixin`, generating a function that
+ * may be called within the template.
+ *
+ * @param {Mixin} mixin
+ * @api public
+ */
+
+ visitMixin: function(mixin){
+ var name = mixin.name.replace(/-/g, '_') + '_mixin'
+ , args = mixin.args || '';
+
+ if (mixin.block) {
+ this.buf.push('var ' + name + ' = function(' + args + '){');
+ this.visit(mixin.block);
+ this.buf.push('}');
+ } else {
+ this.buf.push(name + '(' + args + ');');
+ }
+ },
+
/**
* Visit `tag` buffering tag markup, generating
* attributes, visiting the `tag`'s code and block.
*/
visitTag: function(tag){
- this.indentDepth++;
+ this.indents++;
var name = tag.name;
if (!this.hasCompiledTag) {
this.hasCompiledTag = true;
}
- if(this.pp && inlineTags.indexOf(name) == -1)
- this.buffer('\\n' + new Array(this.indentDepth).join(' '));
+ // pretty print
+ if (this.pp && inlineTags.indexOf(name) == -1) {
+ this.buffer('\\n' + Array(this.indents).join(' '));
+ }
if (~selfClosing.indexOf(name) && !this.xml) {
this.buffer('<' + name);
if (tag.text) this.buffer(utils.text(tag.text.nodes[0].trimLeft()));
this.escape = 'pre' == tag.name;
this.visit(tag.block);
- if (this.pp && inlineTags.indexOf(name) == -1 && tag.textOnly == 0) this.buffer('\\n' + new Array(this.indentDepth).join(' '));
+
+ // pretty print
+ if (this.pp && !~inlineTags.indexOf(name) && !tag.textOnly) {
+ this.buffer('\\n' + Array(this.indents).join(' '));
+ }
+
this.buffer('</' + name + '>');
}
- this.indentDepth--;
+ this.indents--;
},
/**
visitComment: function(comment){
if (!comment.buffer) return;
- if (this.pp) this.buffer('\\n' + new Array(this.indentDepth + 1).join(' '));
+ if (this.pp) this.buffer('\\n' + Array(this.indents + 1).join(' '));
this.buffer('<!--' + utils.escape(comment.val) + '-->');
},
*/
visitBlockComment: function(comment){
+ if (!comment.buffer) return;
if (0 == comment.val.indexOf('if')) {
this.buffer('<!--[' + comment.val + ']>');
this.visit(comment.block);
*/
module.exports = [
- 'a',
- 'abbr',
- 'acronym',
- 'b',
- 'br',
- 'code',
- 'em',
- 'font',
- 'i',
- 'img',
- 'ins',
- 'kbd',
- 'map',
- 'samp',
- 'small',
- 'span',
- 'strong',
- 'sub',
- 'sup'
+ 'a'
+ , 'abbr'
+ , 'acronym'
+ , 'b'
+ , 'br'
+ , 'code'
+ , 'em'
+ , 'font'
+ , 'i'
+ , 'img'
+ , 'ins'
+ , 'kbd'
+ , 'map'
+ , 'samp'
+ , 'small'
+ , 'span'
+ , 'strong'
+ , 'sub'
+ , 'sup'
];
}); // module: inline-tags.js
var Parser = require('./parser')
, Compiler = require('./compiler')
+ , runtime = require('./runtime')
/**
* Library version.
*/
-exports.version = '0.12.1';
+exports.version = '0.15.4';
/**
* Intermediate JavaScript cache.
exports.nodes = require('./nodes');
/**
- * Render the given attributes object.
- *
- * @param {Object} obj
- * @return {String}
- * @api private
+ * Jade runtime helpers.
*/
-function attrs(obj){
- var buf = []
- , terse = obj.terse;
- delete obj.terse;
- var keys = Object.keys(obj)
- , len = keys.length;
- if (len) {
- buf.push('');
- for (var i = 0; i < len; ++i) {
- var key = keys[i]
- , val = obj[key];
- if (typeof val === 'boolean' || val === '' || val == null) {
- if (val) {
- terse
- ? buf.push(key)
- : buf.push(key + '="' + key + '"');
- }
- } else {
- buf.push(key + '="' + escape(val) + '"');
- }
- }
- }
- return buf.join(' ');
-}
-
-/**
- * Escape the given string of `html`.
- *
- * @param {String} html
- * @return {String}
- * @api private
- */
-
-function escape(html){
- return String(html)
- .replace(/&(?!\w+;)/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"');
-}
-
-/**
- * Re-throw the given `err` in context to the
- * `str` of jade, `filename`, and `lineno`.
- *
- * @param {Error} err
- * @param {String} str
- * @param {String} filename
- * @param {String} lineno
- * @api private
- */
-
-function rethrow(err, str, filename, lineno){
- var context = 3
- , lines = str.split('\n')
- , start = Math.max(lineno - context, 0)
- , end = Math.min(lines.length, lineno + context);
-
- // Error context
- var context = lines.slice(start, end).map(function(line, i){
- var curr = i + start + 1;
- return (curr == lineno ? ' > ' : ' ')
- + curr
- + '| '
- + line;
- }).join('\n');
-
- // Alter exception message
- err.path = filename;
- err.message = (filename || 'Jade') + ':' + lineno
- + '\n' + context + '\n\n' + err.message;
- throw err;
-}
+exports.runtime = runtime;
/**
* Parse the given `str` of jade and return a function body.
function parse(str, options){
var filename = options.filename;
+
try {
// Parse
- var parser = new Parser(str, filename);
- if (options.debug) parser.debug();
+ var parser = new Parser(str, filename, options);
// Compile
var compiler = new (options.compiler || Compiler)(parser.parse(), options)
try {
return ''
- + attrs.toString() + '\n\n'
- + escape.toString() + '\n\n'
+ 'var buf = [];\n'
+ (options.self
- ? 'var self = locals || {}, __ = __ || locals.__;\n' + js
- : 'with (locals || {}) {' + js + '}')
+ ? 'var self = locals || {};\n' + js
+ : 'with (locals || {}) {\n' + js + '\n}\n')
+ 'return buf.join("");';
+
} catch (err) {
process.compile(js, filename || 'Jade');
return;
}
} catch (err) {
- rethrow(err, str, filename, parser.lexer.lineno);
+ runtime.rethrow(err, str, filename, parser.lexer.lineno);
}
}
/**
* Compile a `Function` representation of the given jade `str`.
*
+ * Options:
+ *
+ * - `compileDebug` when `false` debugging code is stripped from the compiled template
+ * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()`
+ * for use with the Jade client-side runtime.js
+ *
* @param {String} str
* @param {Options} options
* @return {Function}
exports.compile = function(str, options){
var options = options || {}
, input = JSON.stringify(str)
+ , client = options.client
, filename = options.filename
? JSON.stringify(options.filename)
- : 'undefined';
-
- // Reduce closure madness by injecting some locals
- var fn = [
- 'var __ = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };'
- , rethrow.toString()
- , 'try {'
- , parse(String(str), options || {})
- , '} catch (err) {'
- , ' rethrow(err, __.input, __.filename, __.lineno);'
- , '}'
- ].join('\n');
-
- return new Function('locals', fn);
-};
-
-/**
- * Render the given `str` of jade.
- *
- * Options:
- *
- * - `scope` Evaluation scope (`this`)
- * - `locals` Local variable object
- * - `filename` Used in exceptions, and required by `cache`
- * - `cache` Cache intermediate JavaScript in memory keyed by `filename`
- * - `compiler` Compiler to replade jade's default
- * - `doctype` Specify the default doctype
- *
- * @param {String|Buffer} str
- * @param {Object} options
- * @return {String}
- * @api public
- */
-
-exports.render = function(str, options){
- var fn
- , options = options || {}
- , filename = options.filename;
-
- // Accept Buffers
- str = String(str);
-
- // Cache support
- if (options.cache) {
- if (filename) {
- if (cache[filename]) {
- fn = cache[filename];
- } else {
- fn = cache[filename] = new Function('locals', parse(str, options));
- }
- } else {
- throw new Error('filename is required when using the cache option');
- }
+ : 'undefined'
+ , fn;
+
+ if (options.compileDebug !== false) {
+ fn = [
+ 'var __ = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };'
+ , 'try {'
+ , parse(String(str), options || {})
+ , '} catch (err) {'
+ , ' rethrow(err, __.input, __.filename, __.lineno);'
+ , '}'
+ ].join('\n');
} else {
- fn = new Function('locals', parse(str, options));
+ fn = parse(String(str), options || {});
}
- // Render the template
- try {
- var locals = options.locals || {}
- , meta = { lineno: 1 };
- locals.__ = meta;
- return fn.call(options.scope, locals);
- } catch (err) {
- rethrow(err, str, filename, meta.lineno);
+ if (client) {
+ fn = 'var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\n' + fn;
}
-};
-
-/**
- * Render jade template at the given `path`.
- *
- * @param {String} path
- * @param {Object} options
- * @param {Function} fn
- * @api public
- */
-exports.renderFile = function(path, options, fn){
- var ret;
+ fn = new Function('locals, attrs, escape, rethrow', fn);
- if (typeof options === 'function') {
- fn = options;
- options = {};
- }
- options.filename = path;
+ if (client) return fn;
- // Primed cache
- if (options.cache && cache[path]) {
- try {
- ret = exports.render('', options);
- } catch (err) {
- return fn(err);
- }
- fn(null, ret);
- } else {
- fs.readFile(path, 'utf8', function(err, str){
- if (err) return fn(err);
- try {
- ret = exports.render(str, options);
- } catch (err) {
- return fn(err);
- }
- fn(null, ret);
- });
- }
+ return function(locals){
+ return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow);
+ };
};
}); // module: jade.js
/**
* Initialize `Lexer` with the given `str`.
*
+ * Options:
+ *
+ * - `colons` allow colons for attr delimiters
+ *
* @param {String} str
+ * @param {Object} options
* @api private
*/
-var Lexer = module.exports = function Lexer(str) {
+var Lexer = module.exports = function Lexer(str, options) {
+ options = options || {};
this.input = str.replace(/\r\n|\r/g, '\n');
+ this.colons = options.colons;
this.deferredTokens = [];
this.lastIndents = 0;
this.lineno = 1;
}
},
- /**
- * Block comment
- */
-
- blockComment: function() {
- var captures;
- if (captures = /^\/([^\n]+)/.exec(this.input)) {
- this.consume(captures[0].length);
- var tok = this.tok('block-comment', captures[1]);
- return tok;
- }
- },
-
/**
* Comment.
*/
comment: function() {
var captures;
- if (captures = /^ *\/\/(-)?([^\n]+)/.exec(this.input)) {
+ if (captures = /^ *\/\/(-)?([^\n]*)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('comment', captures[2]);
tok.buffer = '-' != captures[1];
doctype: function() {
return this.scan(/^(?:!!!|doctype) *(\w+)?/, 'doctype');
},
-
+
/**
* Id.
*/
return this.scan(/^(?:\| ?)?([^\n]+)/, 'text');
},
+ /**
+ * Include.
+ */
+
+ include: function() {
+ return this.scan(/^include +([^\n]+)/, 'include');
+ },
+
+ /**
+ * Mixin.
+ */
+
+ mixin: function(){
+ var captures;
+ if (captures = /^mixin +([-\w]+)(?:\(([^\)]+)\))?/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var tok = this.tok('mixin', captures[1]);
+ tok.args = captures[2];
+ return tok;
+ }
+ },
+
+ /**
+ * Conditional.
+ */
+
+ conditional: function() {
+ var captures;
+ if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var type = captures[1]
+ , js = captures[2];
+
+ switch (type) {
+ case 'if': js = 'if (' + js + ')'; break;
+ case 'unless': js = 'if (!(' + js + '))'; break;
+ case 'else if': js = 'else if (' + js + ')'; break;
+ case 'else': js = 'else'; break;
+ }
+
+ return this.tok('code', js);
+ }
+ },
+
/**
* Each.
*/
each: function() {
var captures;
- if (captures = /^- *each *(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
+ if (captures = /^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('each', captures[1]);
tok.key = captures[2] || 'index';
, str = this.input.substr(1, index-1)
, tok = this.tok('attrs')
, len = str.length
+ , colons = this.colons
, states = ['key']
, key = ''
, val = ''
tok.attrs = {};
function parse(c) {
+ var real = c;
+ // TODO: remove when people fix ":"
+ if (colons && ':' == c) c = '=';
switch (c) {
case ',':
case '\n':
case '=':
switch (state()) {
case 'key char':
- key += c;
+ key += real;
break;
case 'val':
case 'expr':
case 'array':
case 'string':
case 'object':
- val += c;
+ val += real;
break;
default:
states.push('val');
|| this.eos()
|| this.pipelessText()
|| this.doctype()
+ || this.include()
+ || this.mixin()
+ || this.conditional()
+ || this.each()
|| this.tag()
|| this.filter()
- || this.each()
|| this.code()
|| this.id()
|| this.className()
|| this.attrs()
|| this.indent()
|| this.comment()
- || this.blockComment()
|| this.colon()
|| this.text();
}
*
* @param {String} val
* @param {Block} block
+ * @param {Boolean} buffer
* @api public
*/
-var BlockComment = module.exports = function BlockComment(val, block) {
+var BlockComment = module.exports = function BlockComment(val, block, buffer) {
this.block = block;
this.val = val;
+ this.buffer = buffer;
};
/**
exports.Each = require('./each');
exports.Text = require('./text');
exports.Block = require('./block');
+exports.Mixin = require('./mixin');
exports.Filter = require('./filter');
exports.Comment = require('./comment');
+exports.Literal = require('./literal');
exports.BlockComment = require('./block-comment');
exports.Doctype = require('./doctype');
}); // module: nodes/index.js
+require.register("nodes/literal.js", function(module, exports, require){
+
+/*!
+ * Jade - nodes - Literal
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Literal` node with the given `str.
+ *
+ * @param {String} str
+ * @api public
+ */
+
+var Literal = module.exports = function Literal(str) {
+ this.str = str;
+};
+
+/**
+ * Inherit from `Node`.
+ */
+
+Literal.prototype = new Node;
+Literal.prototype.constructor = Literal;
+
+
+}); // module: nodes/literal.js
+
+require.register("nodes/mixin.js", function(module, exports, require){
+
+/*!
+ * Jade - nodes - Mixin
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Node = require('./node');
+
+/**
+ * Initialize a new `Mixin` with `name` and `block`.
+ *
+ * @param {String} name
+ * @param {String} args
+ * @param {Block} block
+ * @api public
+ */
+
+var Mixin = module.exports = function Mixin(name, args, block){
+ this.name = name;
+ this.args = args;
+ this.block = block;
+};
+
+/**
+ * Inherit from `Node`.
+ */
+
+Mixin.prototype = new Node;
+Mixin.prototype.constructor = Mixin;
+
+
+
+}); // module: nodes/mixin.js
+
require.register("nodes/node.js", function(module, exports, require){
/*!
*
* @param {String} str
* @param {String} filename
+ * @param {Object} options
* @api public
*/
-var Parser = exports = module.exports = function Parser(str, filename){
+var Parser = exports = module.exports = function Parser(str, filename, options){
this.input = str;
- this.lexer = new Lexer(str);
+ this.lexer = new Lexer(str, options);
this.filename = filename;
};
Parser.prototype = {
- /**
- * Output parse tree to stdout.
- *
- * @api public
- */
-
- debug: function(){
- var lexer = new Lexer(this.input)
- , tree = require('sys').inspect(this.parse(), false, 12, true);
- console.log('\n\x1b[1mParse Tree\x1b[0m:\n');
- console.log(tree);
- this.lexer = lexer;
- },
-
/**
* Return the next token object.
*
advance: function(){
return this.lexer.advance();
},
+
+ /**
+ * Skip `n` tokens.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+ skip: function(n){
+ while (n--) this.advance();
+ },
/**
* Single token lookahead.
/**
* tag
* | doctype
+ * | mixin
+ * | include
* | filter
* | comment
* | text
switch (this.peek().type) {
case 'tag':
return this.parseTag();
+ case 'mixin':
+ return this.parseMixin();
+ case 'include':
+ return this.parseInclude();
case 'doctype':
return this.parseDoctype();
case 'filter':
return this.parseFilter();
case 'comment':
return this.parseComment();
- case 'block-comment':
- return this.parseBlockComment();
case 'text':
return this.parseText();
case 'each':
parseCode: function(){
var tok = this.expect('code')
- , node = new nodes.Code(tok.val, tok.buffer, tok.escape);
+ , node = new nodes.Code(tok.val, tok.buffer, tok.escape)
+ , block
+ , i = 1;
node.line = this.line();
- if ('indent' == this.peek().type) {
+ while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i;
+ block = 'indent' == this.lookahead(i).type;
+ if (block) {
+ this.skip(i-1);
node.block = this.parseBlock();
}
return node;
},
-
- /**
- * block comment
- */
-
- parseBlockComment: function(){
- var tok = this.expect('block-comment')
- , node = new nodes.BlockComment(tok.val, this.parseBlock());
- node.line = this.line();
- return node;
- },
-
/**
* comment
parseComment: function(){
var tok = this.expect('comment')
- , node = new nodes.Comment(tok.val, tok.buffer);
+ , node;
+
+ if ('indent' == this.peek().type) {
+ node = new nodes.BlockComment(tok.val, this.parseBlock(), tok.buffer);
+ } else {
+ node = new nodes.Comment(tok.val, tok.buffer);
+ }
+
node.line = this.line();
return node;
},
node.line = this.line();
return node;
},
-
+
+ /**
+ * include
+ */
+
+ parseInclude: function(){
+ var path = require('path')
+ , fs = require('fs')
+ , dirname = path.dirname
+ , basename = path.basename
+ , join = path.join;
+
+ if (!this.filename)
+ throw new Error('the "filename" option is required to use includes');
+
+ var path = name = this.expect('include').val.trim()
+ , dir = dirname(this.filename);
+
+ // non-jade
+ if (~basename(path).indexOf('.')) {
+ var path = join(dir, path)
+ , str = fs.readFileSync(path, 'utf8');
+ return new nodes.Literal(str);
+ }
+
+ var path = join(dir, path + '.jade')
+ , str = fs.readFileSync(path, 'utf8')
+ , parser = new Parser(str, path)
+ , ast = parser.parse();
+
+ return ast;
+ },
+
+ /**
+ * mixin block
+ */
+
+ parseMixin: function(){
+ var tok = this.expect('mixin')
+ , name = tok.val
+ , args = tok.args;
+ var block = 'indent' == this.peek().type
+ ? this.parseBlock()
+ : null;
+ return new nodes.Mixin(name, args, block);
+ },
+
/**
* indent (text | newline)* outdent
*/
text.push(indent + this.advance().val);
}
}
- this._spaces = null;
+
+ if (spaces == this._spaces) this._spaces = null;
this.expect('outdent');
return text;
},
}
var name = this.advance().val
- , tag = new nodes.Tag(name);
+ , tag = new nodes.Tag(name)
+ , dot;
tag.line = this.line();
// check immediate '.'
if ('.' == this.peek().val) {
- tag.textOnly = true;
+ dot = tag.textOnly = true;
this.advance();
}
// script special-case
if ('script' == tag.name) {
var type = tag.getAttribute('type');
- if (type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
+ if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
tag.textOnly = false;
}
}
return tag;
}
};
+
}); // module: parser.js
+require.register("runtime.js", function(module, exports, require){
+
+/*!
+ * Jade - runtime
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Lame Array.isArray() polyfill for now.
+ */
+
+if (!Array.isArray) {
+ Array.isArray = function(arr){
+ return '[object Array]' == toString.call(arr);
+ };
+}
+
+/**
+ * Lame Object.keys() polyfill for now.
+ */
+
+if (!Object.keys) {
+ Object.keys = function(obj){
+ var arr = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ arr.push(obj);
+ }
+ }
+ return arr;
+ }
+}
+
+/**
+ * Render the given attributes object.
+ *
+ * @param {Object} obj
+ * @return {String}
+ * @api private
+ */
+
+exports.attrs = function attrs(obj){
+ var buf = []
+ , terse = obj.terse;
+ delete obj.terse;
+ var keys = Object.keys(obj)
+ , len = keys.length;
+ if (len) {
+ buf.push('');
+ for (var i = 0; i < len; ++i) {
+ var key = keys[i]
+ , val = obj[key];
+ if ('boolean' == typeof val || null == val) {
+ if (val) {
+ terse
+ ? buf.push(key)
+ : buf.push(key + '="' + key + '"');
+ }
+ } else if ('class' == key && Array.isArray(val)) {
+ buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
+ } else {
+ buf.push(key + '="' + exports.escape(val) + '"');
+ }
+ }
+ }
+ return buf.join(' ');
+};
+
+/**
+ * Escape the given string of `html`.
+ *
+ * @param {String} html
+ * @return {String}
+ * @api private
+ */
+
+exports.escape = function escape(html){
+ return String(html)
+ .replace(/&(?!\w+;)/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"');
+};
+
+/**
+ * Re-throw the given `err` in context to the
+ * `str` of jade, `filename`, and `lineno`.
+ *
+ * @param {Error} err
+ * @param {String} str
+ * @param {String} filename
+ * @param {String} lineno
+ * @api private
+ */
+
+exports.rethrow = function rethrow(err, str, filename, lineno){
+ var context = 3
+ , lines = str.split('\n')
+ , start = Math.max(lineno - context, 0)
+ , end = Math.min(lines.length, lineno + context);
+
+ // Error context
+ var context = lines.slice(start, end).map(function(line, i){
+ var curr = i + start + 1;
+ return (curr == lineno ? ' > ' : ' ')
+ + curr
+ + '| '
+ + line;
+ }).join('\n');
+
+ // Alter exception message
+ err.path = filename;
+ err.message = (filename || 'Jade') + ':' + lineno
+ + '\n' + context + '\n\n' + err.message;
+ throw err;
+};
+
+}); // module: runtime.js
+
require.register("self-closing.js", function(module, exports, require){
/*!
// CommonJS require()
-function require(p){var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');mod.exports||(mod.exports={},mod.call(mod.exports,mod,mod.exports,require.relative(path)));return mod.exports}require.modules={},require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&®||require.modules[index]&&index||orig},require.register=function(path,fn){require.modules[path]=fn},require.relative=function(parent){return function(p){if("."!=p[0])return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i<segs.length;i++){var seg=segs[i];".."==seg?path.pop():"."!=seg&&path.push(seg)}return require(path.join("/"))}},require.register("compiler.js",function(module,exports,require){var nodes=require("./nodes"),filters=require("./filters"),doctypes=require("./doctypes"),selfClosing=require("./self-closing"),utils=require("./utils");Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(obj);return arr}),String.prototype.trimLeft||(String.prototype.trimLeft=function(){return this.replace(/^\s+/,"")});var Compiler=module.exports=function Compiler(node,options){this.options=options=options||{},this.node=node,this.hasCompiledDoctype=!1,this.hasCompiledTag=!1,options.doctype&&this.setDoctype(options.doctype)};Compiler.prototype={compile:function(){this.buf=["var interp;"],this.visit(this.node);return this.buf.join("\n")},setDoctype:function(name){var doctype=doctypes[(name||"default").toLowerCase()];if(!doctype)throw new Error('unknown doctype "'+name+'"');this.doctype=doctype,this.terse="5"==name||"html"==name,this.xml=0==this.doctype.indexOf("<?xml")},buffer:function(str,esc){esc&&(str=utils.escape(str)),this.buf.push("buf.push('"+str+"');")},line:function(node){node.instrumentLineNumber!==!1&&this.buf.push("__.lineno = "+node.line+";")},visit:function(node){this.line(node);return this.visitNode(node)},visitNode:function(node){var name=node.constructor.name||node.constructor.toString().match(/function ([^(\s]+)()/)[1];return this["visit"+name](node)},visitBlock:function(block){var len=len=block.nodes.length;for(var i=0;i<len;++i)this.visit(block.nodes[i])},visitDoctype:function(doctype){doctype&&(doctype.val||!this.doctype)&&this.setDoctype(doctype.val||"default"),this.doctype&&this.buffer(this.doctype),this.hasCompiledDoctype=!0},visitTag:function(tag){var name=tag.name;this.hasCompiledTag||(!this.hasCompiledDoctype&&"html"==name&&this.visitDoctype(),this.hasCompiledTag=!0),~selfClosing.indexOf(name)&&!this.xml?(this.buffer("<"+name),this.visitAttributes(tag.attrs),this.terse?this.buffer(">"):this.buffer("/>")):(tag.attrs.length?(this.buffer("<"+name),tag.attrs.length&&this.visitAttributes(tag.attrs),this.buffer(">")):this.buffer("<"+name+">"),tag.code&&this.visitCode(tag.code),tag.text&&this.buffer(utils.text(tag.text.nodes[0].trimLeft())),this.escape="pre"==tag.name,this.visit(tag.block),this.buffer("</"+name+">"))},visitFilter:function(filter){var fn=filters[filter.name];if(!fn)throw filter.isASTFilter?new Error('unknown ast filter "'+filter.name+':"'):new Error('unknown filter ":'+filter.name+'"');if(filter.isASTFilter)this.buf.push(fn(filter.block,this,filter.attrs));else{var text=filter.block.nodes.join("");this.buffer(utils.text(fn(text,filter.attrs)))}},visitText:function(text){text=utils.text(text.nodes.join("")),this.escape&&(text=escape(text)),this.buffer(text),this.buffer("\\n")},visitComment:function(comment){!comment.buffer||this.buffer("<!--"+utils.escape(comment.val)+"-->")},visitBlockComment:function(comment){0==comment.val.indexOf("if")?(this.buffer("<!--["+comment.val+"]>"),this.visit(comment.block),this.buffer("<![endif]-->")):(this.buffer("<!--"+comment.val),this.visit(comment.block),this.buffer("-->"))},visitCode:function(code){if(code.buffer){var val=code.val.trimLeft();this.buf.push("var __val__ = "+val),val='null == __val__ ? "" : __val__',code.escape&&(val="escape("+val+")"),this.buf.push("buf.push("+val+");")}else this.buf.push(code.val);code.block&&(code.buffer||this.buf.push("{"),this.visit(code.block),code.buffer||this.buf.push("}"))},visitEach:function(each){this.buf.push("// iterate "+each.obj+"\n"+"(function(){\n"+" if ('number' == typeof "+each.obj+".length) {\n"+" for (var "+each.key+" = 0, $$l = "+each.obj+".length; "+each.key+" < $$l; "+each.key+"++) {\n"+" var "+each.val+" = "+each.obj+"["+each.key+"];\n"),this.visit(each.block),this.buf.push(" }\n } else {\n for (var "+each.key+" in "+each.obj+") {\n"+" if ("+each.obj+".hasOwnProperty("+each.key+")){"+" var "+each.val+" = "+each.obj+"["+each.key+"];\n"),this.visit(each.block),this.buf.push(" }\n"),this.buf.push(" }\n }\n}).call(this);\n")},visitAttributes:function(attrs){var buf=[],classes=[];this.terse&&buf.push("terse: true"),attrs.forEach(function(attr){if(attr.name=="class")classes.push("("+attr.val+")");else{var pair="'"+attr.name+"':("+attr.val+")";buf.push(pair)}}),classes.length&&(classes=classes.join(" + ' ' + "),buf.push("class: "+classes)),buf=buf.join(", ").replace("class:",'"class":'),this.buf.push("buf.push(attrs({ "+buf+" }));")}};function escape(html){return String(html).replace(/&(?!\w+;)/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}}),require.register("doctypes.js",function(module,exports,require){module.exports={5:"<!DOCTYPE html>",html:"<!DOCTYPE html>",xml:'<?xml version="1.0" encoding="utf-8" ?>',"default":'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',transitional:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',strict:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',frameset:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',1.1:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',basic:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',mobile:'<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'}}),require.register("filters.js",function(module,exports,require){module.exports={cdata:function(str){return"<![CDATA[\\n"+str+"\\n]]>"},sass:function(str){str=str.replace(/\\n/g,"\n");var sass=require("sass").render(str).replace(/\n/g,"\\n");return"<style>"+sass+"</style>"},stylus:function(str,options){var ret;str=str.replace(/\\n/g,"\n");var stylus=require("stylus");stylus(str,options).render(function(err,css){if(err)throw err;ret=css.replace(/\n/g,"\\n")});return"<style>"+ret+"</style>"},less:function(str){var ret;str=str.replace(/\\n/g,"\n"),require("less").render(str,function(err,css){if(err)throw err;ret="<style>"+css.replace(/\n/g,"\\n")+"</style>"});return ret},markdown:function(str){var md;try{md=require("markdown")}catch(err){try{md=require("discount")}catch(err){try{md=require("markdown-js")}catch(err){throw new Error("Cannot find markdown library, install markdown or discount")}}}str=str.replace(/\\n/g,"\n");return md.parse(str).replace(/\n/g,"\\n").replace(/'/g,"'")},coffeescript:function(str){str=str.replace(/\\n/g,"\n");var js=require("coffee-script").compile(str).replace(/\n/g,"\\n");return'<script type="text/javascript">\\n'+js+"</script>"}}}),require.register("jade.js",function(module,exports,require){var Parser=require("./parser"),Compiler=require("./compiler");exports.version="0.12.1";var cache=exports.cache={};exports.selfClosing=require("./self-closing"),exports.doctypes=require("./doctypes"),exports.filters=require("./filters"),exports.utils=require("./utils"),exports.Compiler=Compiler,exports.Parser=Parser,exports.nodes=require("./nodes");function attrs(obj){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i<len;++i){var key=keys[i],val=obj[key];typeof val=="boolean"||val===""||val==null?val&&(terse?buf.push(key):buf.push(key+'="'+key+'"')):buf.push(key+'="'+escape(val)+'"')}}return buf.join(" ")}function escape(html){return String(html).replace(/&(?!\w+;)/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function rethrow(err,str,filename,lineno){var context=3,lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}function parse(str,options){var filename=options.filename;try{var parser=new Parser(str,filename);options.debug&&parser.debug();var compiler=new(options.compiler||Compiler)(parser.parse(),options),js=compiler.compile();options.debug&&console.log("\n\e[1mCompiled Function\e[0m:\n\n%s",js.replace(/^/gm," "));try{return""+attrs.toString()+"\n\n"+escape.toString()+"\n\n"+"var buf = [];\n"+(options.self?"var self = locals || {}, __ = __ || locals.__;\n"+js:"with (locals || {}) {"+js+"}")+'return buf.join("");'}catch(err){process.compile(js,filename||"Jade");return}}catch(err){rethrow(err,str,filename,parser.lexer.lineno)}}exports.compile=function(str,options){var options=options||{},input=JSON.stringify(str),filename=options.filename?JSON.stringify(options.filename):"undefined",fn=["var __ = { lineno: 1, input: "+input+", filename: "+filename+" };",rethrow.toString(),"try {",parse(String(str),options||{}),"} catch (err) {"," rethrow(err, __.input, __.filename, __.lineno);","}"].join("\n");return new Function("locals",fn)},exports.render=function(str,options){var fn,options=options||{},filename=options.filename;str=String(str);if(options.cache)if(filename)cache[filename]?fn=cache[filename]:fn=cache[filename]=new Function("locals",parse(str,options));else throw new Error("filename is required when using the cache option");else fn=new Function("locals",parse(str,options));try{var locals=options.locals||{},meta={lineno:1};locals.__=meta;return fn.call(options.scope,locals)}catch(err){rethrow(err,str,filename,meta.lineno)}},exports.renderFile=function(path,options,fn){var ret;typeof options=="function"&&(fn=options,options={}),options.filename=path;if(options.cache&&cache[path]){try{ret=exports.render("",options)}catch(err){return fn(err)}fn(null,ret)}else fs.readFile(path,"utf8",function(err,str){if(err)return fn(err);try{ret=exports.render(str,options)}catch(err){return fn(err)}fn(null,ret)})}}),require.register("lexer.js",function(module,exports,require){var Lexer=module.exports=function Lexer(str){this.input=str.replace(/\r\n|\r/g,"\n"),this.deferredTokens=[],this.lastIndents=0,this.lineno=1,this.stash=[],this.indentStack=[],this.indentRe=null,this.pipeless=!1};Lexer.prototype={tok:function(type,val){return{type:type,line:this.lineno,val:val}},consume:function(len){this.input=this.input.substr(len)},scan:function(regexp,type){var captures;if(captures=regexp.exec(this.input)){this.consume(captures[0].length);return this.tok(type,captures[1])}},defer:function(tok){this.deferredTokens.push(tok)},lookahead:function(n){var fetch=n-this.stash.length;while(fetch-->0)this.stash.push(this.next());return this.stash[--n]},indexOfDelimiters:function(start,end){var str=this.input,nstart=0,nend=0,pos=0;for(var i=0,len=str.length;i<len;++i)if(start==str[i])++nstart;else if(end==str[i]&&++nend==nstart){pos=i;break}return pos},stashed:function(){return this.stash.length&&this.stash.shift()},deferred:function(){return this.deferredTokens.length&&this.deferredTokens.shift()},eos:function(){if(!this.input.length){if(this.indentStack.length){this.indentStack.shift();return this.tok("outdent")}return this.tok("eos")}},blockComment:function(){var captures;if(captures=/^\/([^\n]+)/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("block-comment",captures[1]);return tok}},comment:function(){var captures;if(captures=/^ *\/\/(-)?([^\n]+)/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("comment",captures[2]);tok.buffer="-"!=captures[1];return tok}},tag:function(){var captures;if(captures=/^(\w[-:\w]*)/.exec(this.input)){this.consume(captures[0].length);var tok,name=captures[1];if(":"==name[name.length-1]){name=name.slice(0,-1),tok=this.tok("tag",name),this.deferredTokens.push(this.tok(":"));while(" "==this.input[0])this.input=this.input.substr(1)}else tok=this.tok("tag",name);return tok}},filter:function(){return this.scan(/^:(\w+)/,"filter")},doctype:function(){return this.scan(/^(?:!!!|doctype) *(\w+)?/,"doctype")},id:function(){return this.scan(/^#([\w-]+)/,"id")},className:function(){return this.scan(/^\.([\w-]+)/,"class")},text:function(){return this.scan(/^(?:\| ?)?([^\n]+)/,"text")},each:function(){var captures;if(captures=/^- *each *(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("each",captures[1]);tok.key=captures[2]||"index",tok.code=captures[3];return tok}},code:function(){var captures;if(captures=/^(!?=|-)([^\n]+)/.exec(this.input)){this.consume(captures[0].length);var flags=captures[1];captures[1]=captures[2];var tok=this.tok("code",captures[1]);tok.escape=flags[0]==="=",tok.buffer=flags[0]==="="||flags[1]==="=";return tok}},attrs:function(){if("("==this.input[0]){var index=this.indexOfDelimiters("(",")"),str=this.input.substr(1,index-1),tok=this.tok("attrs"),len=str.length,states=["key"],key="",val="",quote,c;function state(){return states[states.length-1]}function interpolate(attr){return attr.replace(/#\{([^}]+)\}/g,function(_,expr){return quote+" + ("+expr+") + "+quote})}this.consume(index+1),tok.attrs={};function parse(c){switch(c){case",":case"\n":switch(state()){case"expr":case"array":case"string":case"object":val+=c;break;default:states.push("key"),val=val.trim(),key=key.trim();if(""==key)return;tok.attrs[key.replace(/^['"]|['"]$/g,"")]=""==val?!0:interpolate(val),key=val=""}break;case"=":switch(state()){case"key char":key+=c;break;case"val":case"expr":case"array":case"string":case"object":val+=c;break;default:states.push("val")}break;case"(":"val"==state()&&states.push("expr"),val+=c;break;case")":"expr"==state()&&states.pop(),val+=c;break;case"{":"val"==state()&&states.push("object"),val+=c;break;case"}":"object"==state()&&states.pop(),val+=c;break;case"[":"val"==state()&&states.push("array"),val+=c;break;case"]":"array"==state()&&states.pop(),val+=c;break;case'"':case"'":switch(state()){case"key":states.push("key char");break;case"key char":states.pop();break;case"string":c==quote&&states.pop(),val+=c;break;default:states.push("string"),val+=c,quote=c}break;case"":break;default:switch(state()){case"key":case"key char":key+=c;break;default:val+=c}}}for(var i=0;i<len;++i)parse(str[i]);parse(",");return tok}},indent:function(){var captures,re;this.indentRe?captures=this.indentRe.exec(this.input):(re=/^\n(\t*) */,captures=re.exec(this.input),captures&&!captures[1].length&&(re=/^\n( *)/,captures=re.exec(this.input)),captures&&captures[1].length&&(this.indentRe=re));if(captures){var tok,indents=captures[1].length;++this.lineno,this.consume(indents+1);if(" "==this.input[0]||"\t"==this.input[0])throw new Error("Invalid indentation, you can use tabs or spaces but not both");if("\n"==this.input[0])return this.tok("newline");if(this.indentStack.length&&indents<this.indentStack[0]){while(this.indentStack.length&&this.indentStack[0]>indents)this.stash.push(this.tok("outdent")),this.indentStack.shift();tok=this.stash.pop()}else indents&&indents!=this.indentStack[0]?(this.indentStack.unshift(indents),tok=this.tok("indent",indents)):tok=this.tok("newline");return tok}},pipelessText:function(){if(this.pipeless){if("\n"==this.input[0])return;var i=this.input.indexOf("\n");-1==i&&(i=this.input.length);var str=this.input.substr(0,i);this.consume(str.length);return this.tok("text",str)}},colon:function(){return this.scan(/^: */,":")},advance:function(){return this.stashed()||this.next()},next:function(){return this.deferred()||this.eos()||this.pipelessText()||this.doctype()||this.tag()||this.filter()||this.each()||this.code()||this.id()||this.className()||this.attrs()||this.indent()||this.comment()||this.blockComment()||this.colon()||this.text()}}}),require.register("nodes/block-comment.js",function(module,exports,require){var Node=require("./node"),BlockComment=module.exports=function BlockComment(val,block){this.block=block,this.val=val};BlockComment.prototype=new Node,BlockComment.prototype.constructor=BlockComment}),require.register("nodes/block.js",function(module,exports,require){var Node=require("./node"),Block=module.exports=function Block(node){this.nodes=[],node&&this.push(node)};Block.prototype=new Node,Block.prototype.constructor=Block,Block.prototype.push=function(node){return this.nodes.push(node)},Block.prototype.unshift=function(node){return this.nodes.unshift(node)}}),require.register("nodes/code.js",function(module,exports,require){var Node=require("./node"),Code=module.exports=function Code(val,buffer,escape){this.val=val,this.buffer=buffer,this.escape=escape,/^ *else/.test(val)&&(this.instrumentLineNumber=!1)};Code.prototype=new Node,Code.prototype.constructor=Code}),require.register("nodes/comment.js",function(module,exports,require){var Node=require("./node"),Comment=module.exports=function Comment(val,buffer){this.val=val,this.buffer=buffer};Comment.prototype=new Node,Comment.prototype.constructor=Comment}),require.register("nodes/doctype.js",function(module,exports,require){var Node=require("./node"),Doctype=module.exports=function Doctype(val){this.val=val};Doctype.prototype=new Node,Doctype.prototype.constructor=Doctype}),require.register("nodes/each.js",function(module,exports,require){var Node=require("./node"),Each=module.exports=function Each(obj,val,key,block){this.obj=obj,this.val=val,this.key=key,this.block=block};Each.prototype=new Node,Each.prototype.constructor=Each}),require.register("nodes/filter.js",function(module,exports,require){var Node=require("./node"),Block=require("./block"),Filter=module.exports=function Filter(name,block,attrs){this.name=name,this.block=block,this.attrs=attrs,this.isASTFilter=block instanceof Block};Filter.prototype=new Node,Filter.prototype.constructor=Filter}),require.register("nodes/index.js",function(module,exports,require){exports.Node=require("./node"),exports.Tag=require("./tag"),exports.Code=require("./code"),exports.Each=require("./each"),exports.Text=require("./text"),exports.Block=require("./block"),exports.Filter=require("./filter"),exports.Comment=require("./comment"),exports.BlockComment=require("./block-comment"),exports.Doctype=require("./doctype")}),require.register("nodes/node.js",function(module,exports,require){var Node=module.exports=function(){}}),require.register("nodes/tag.js",function(module,exports,require){var Node=require("./node"),Block=require("./block"),Tag=module.exports=function Tag(name,block){this.name=name,this.attrs=[],this.block=block||new Block};Tag.prototype=new Node,Tag.prototype.constructor=Tag,Tag.prototype.setAttribute=function(name,val){this.attrs.push({name:name,val:val});return this},Tag.prototype.removeAttribute=function(name){for(var i=0,len=this.attrs.length;i<len;++i)this.attrs[i]&&this.attrs[i].name==name&&delete this.attrs[i]},Tag.prototype.getAttribute=function(name){for(var i=0,len=this.attrs.length;i<len;++i)if(this.attrs[i]&&this.attrs[i].name==name)return this.attrs[i].val}}),require.register("nodes/text.js",function(module,exports,require){var Node=require("./node"),Text=module.exports=function Text(line){this.nodes=[],"string"==typeof line&&this.push(line)};Text.prototype=new Node,Text.prototype.constructor=Text,Text.prototype.push=function(node){return this.nodes.push(node)}}),require.register("parser.js",function(module,exports,require){var Lexer=require("./lexer"),nodes=require("./nodes"),Parser=exports=module.exports=function Parser(str,filename){this.input=str,this.lexer=new Lexer(str),this.filename=filename},textOnly=exports.textOnly=["code","script","textarea","style"];Parser.prototype={debug:function(){var lexer=new Lexer(this.input),tree=require("sys").inspect(this.parse(),!1,12,!0);console.log("\n\e[1mParse Tree\e[0m:\n"),console.log(tree),this.lexer=lexer},advance:function(){return this.lexer.advance()},peek:function(){return this.lookahead(1)},line:function(){return this.lexer.lineno},lookahead:function(n){return this.lexer.lookahead(n)},parse:function(){var block=new nodes.Block;block.line=this.line();while("eos"!=this.peek().type)"newline"==this.peek().type?this.advance():block.push(this.parseExpr());return block},expect:function(type){if(this.peek().type===type)return this.advance();throw new Error('expected "'+type+'", but got "'+this.peek().type+'"')},accept:function(type){if(this.peek().type===type)return this.advance()},parseExpr:function(){switch(this.peek().type){case"tag":return this.parseTag();case"doctype":return this.parseDoctype();case"filter":return this.parseFilter();case"comment":return this.parseComment();case"block-comment":return this.parseBlockComment();case"text":return this.parseText();case"each":return this.parseEach();case"code":return this.parseCode();case"id":case"class":var tok=this.advance();this.lexer.defer(this.lexer.tok("tag","div")),this.lexer.defer(tok);return this.parseExpr();default:throw new Error('unexpected token "'+this.peek().type+'"')}},parseText:function(){var tok=this.expect("text"),node=new nodes.Text(tok.val);node.line=this.line();return node},parseCode:function(){var tok=this.expect("code"),node=new nodes.Code(tok.val,tok.buffer,tok.escape);node.line=this.line(),"indent"==this.peek().type&&(node.block=this.parseBlock());return node},parseBlockComment:function(){var tok=this.expect("block-comment"),node=new nodes.BlockComment(tok.val,this.parseBlock());node.line=this.line();return node},parseComment:function(){var tok=this.expect("comment"),node=new nodes.Comment(tok.val,tok.buffer);node.line=this.line();return node},parseDoctype:function(){var tok=this.expect("doctype"),node=new nodes.Doctype(tok.val);node.line=this.line();return node},parseFilter:function(){var block,tok=this.expect("filter"),attrs=this.accept("attrs");this.lexer.pipeless=!0,block=this.parseTextBlock(),this.lexer.pipeless=!1;var node=new nodes.Filter(tok.val,block,attrs&&attrs.attrs);node.line=this.line();return node},parseASTFilter:function(){var block,tok=this.expect("tag"),attrs=this.accept("attrs");this.expect(":"),block=this.parseBlock();var node=new nodes.Filter(tok.val,block,attrs&&attrs.attrs);node.line=this.line();return node},parseEach:function(){var tok=this.expect("each"),node=new nodes.Each(tok.code,tok.val,tok.key,this.parseBlock());node.line=this.line();return node},parseTextBlock:function(){var text=new nodes.Text;text.line=this.line();var spaces=this.expect("indent").val;null==this._spaces&&(this._spaces=spaces);var indent=Array(spaces-this._spaces+1).join(" ");while("outdent"!=this.peek().type)switch(this.peek().type){case"newline":text.push("\\n"),this.advance();break;case"indent":text.push("\\n"),this.parseTextBlock().nodes.forEach(function(node){text.push(node)}),text.push("\\n");break;default:text.push(indent+this.advance().val)}this._spaces=null,this.expect("outdent");return text},parseBlock:function(){var block=new nodes.Block;block.line=this.line(),this.expect("indent");while("outdent"!=this.peek().type)"newline"==this.peek().type?this.advance():block.push(this.parseExpr());this.expect("outdent");return block},parseTag:function(){var i=2;"attrs"==this.lookahead(i).type&&++i;if(":"==this.lookahead(i).type&&"indent"==this.lookahead(++i).type)return this.parseASTFilter();var name=this.advance().val,tag=new nodes.Tag(name);tag.line=this.line();out:for(;;)switch(this.peek().type){case"id":case"class":var tok=this.advance();tag.setAttribute(tok.type,"'"+tok.val+"'");continue;case"attrs":var obj=this.advance().attrs,names=Object.keys(obj);for(var i=0,len=names.length;i<len;++i){var name=names[i],val=obj[name];tag.setAttribute(name,val)}continue;default:break out}"."==this.peek().val&&(tag.textOnly=!0,this.advance());switch(this.peek().type){case"text":tag.text=this.parseText();break;case"code":tag.code=this.parseCode();break;case":":this.advance(),tag.block=new nodes.Block,tag.block.push(this.parseTag())}while("newline"==this.peek().type)this.advance();tag.textOnly=tag.textOnly||~textOnly.indexOf(tag.name);if("script"==tag.name){var type=tag.getAttribute("type");type&&"text/javascript"!=type.replace(/^['"]|['"]$/g,"")&&(tag.textOnly=!1)}if("indent"==this.peek().type)if(tag.textOnly)this.lexer.pipeless=!0,tag.block=this.parseTextBlock(),this.lexer.pipeless=!1;else{var block=this.parseBlock();if(tag.block)for(var i=0,len=block.nodes.length;i<len;++i)tag.block.push(block.nodes[i]);else tag.block=block}return tag}}}),require.register("self-closing.js",function(module,exports,require){module.exports=["meta","img","link","input","area","base","col","br","hr"]}),require.register("utils.js",function(module,exports,require){var interpolate=exports.interpolate=function(str){return str.replace(/(\\)?([#!]){(.*?)}/g,function(str,escape,flag,code){return escape?str:"' + "+("!"==flag?"":"escape")+"((interp = "+code.replace(/\\'/g,"'")+") == null ? '' : interp) + '"})},escape=exports.escape=function(str){return str.replace(/'/g,"\\'")};exports.text=function(str){return interpolate(escape(str))}})
\ No newline at end of file
+function require(p){var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');mod.exports||(mod.exports={},mod.call(mod.exports,mod,mod.exports,require.relative(path)));return mod.exports}require.modules={},require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&®||require.modules[index]&&index||orig},require.register=function(path,fn){require.modules[path]=fn},require.relative=function(parent){return function(p){if("."!=p[0])return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i<segs.length;i++){var seg=segs[i];".."==seg?path.pop():"."!=seg&&path.push(seg)}return require(path.join("/"))}},require.register("compiler.js",function(module,exports,require){var nodes=require("./nodes"),filters=require("./filters"),doctypes=require("./doctypes"),selfClosing=require("./self-closing"),inlineTags=require("./inline-tags"),utils=require("./utils");Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(obj);return arr}),String.prototype.trimLeft||(String.prototype.trimLeft=function(){return this.replace(/^\s+/,"")});var Compiler=module.exports=function Compiler(node,options){this.options=options=options||{},this.node=node,this.hasCompiledDoctype=!1,this.hasCompiledTag=!1,this.pp=options.pretty||!1,this.debug=!1!==options.compileDebug,this.indents=0,options.doctype&&this.setDoctype(options.doctype)};Compiler.prototype={compile:function(){this.buf=["var interp;"],this.lastBufferedIdx=-1,this.visit(this.node);return this.buf.join("\n")},setDoctype:function(name){var doctype=doctypes[(name||"default").toLowerCase()];if(!doctype)throw new Error('unknown doctype "'+name+'"');this.doctype=doctype,this.terse="5"==name||"html"==name,this.xml=0==this.doctype.indexOf("<?xml")},buffer:function(str,esc){esc&&(str=utils.escape(str)),this.lastBufferedIdx==this.buf.length?(this.lastBuffered+=str,this.buf[this.lastBufferedIdx-1]="buf.push('"+this.lastBuffered+"');"):(this.buf.push("buf.push('"+str+"');"),this.lastBuffered=str,this.lastBufferedIdx=this.buf.length)},line:function(node){!1!==node.instrumentLineNumber&&this.buf.push("__.lineno = "+node.line+";")},visit:function(node){this.debug&&this.line(node);return this.visitNode(node)},visitNode:function(node){var name=node.constructor.name||node.constructor.toString().match(/function ([^(\s]+)()/)[1];return this["visit"+name](node)},visitLiteral:function(node){var str=node.str.replace(/\n/g,"\\\\n");this.buffer(str)},visitBlock:function(block){var len=block.nodes.length;for(var i=0;i<len;++i)this.visit(block.nodes[i])},visitDoctype:function(doctype){doctype&&(doctype.val||!this.doctype)&&this.setDoctype(doctype.val||"default"),this.doctype&&this.buffer(this.doctype),this.hasCompiledDoctype=!0},visitMixin:function(mixin){var name=mixin.name.replace(/-/g,"_")+"_mixin",args=mixin.args||"";mixin.block?(this.buf.push("var "+name+" = function("+args+"){"),this.visit(mixin.block),this.buf.push("}")):this.buf.push(name+"("+args+");")},visitTag:function(tag){this.indents++;var name=tag.name;this.hasCompiledTag||(!this.hasCompiledDoctype&&"html"==name&&this.visitDoctype(),this.hasCompiledTag=!0),this.pp&&inlineTags.indexOf(name)==-1&&this.buffer("\\n"+Array(this.indents).join(" ")),~selfClosing.indexOf(name)&&!this.xml?(this.buffer("<"+name),this.visitAttributes(tag.attrs),this.terse?this.buffer(">"):this.buffer("/>")):(tag.attrs.length?(this.buffer("<"+name),tag.attrs.length&&this.visitAttributes(tag.attrs),this.buffer(">")):this.buffer("<"+name+">"),tag.code&&this.visitCode(tag.code),tag.text&&this.buffer(utils.text(tag.text.nodes[0].trimLeft())),this.escape="pre"==tag.name,this.visit(tag.block),this.pp&&!~inlineTags.indexOf(name)&&!tag.textOnly&&this.buffer("\\n"+Array(this.indents).join(" ")),this.buffer("</"+name+">")),this.indents--},visitFilter:function(filter){var fn=filters[filter.name];if(!fn)throw filter.isASTFilter?new Error('unknown ast filter "'+filter.name+':"'):new Error('unknown filter ":'+filter.name+'"');if(filter.isASTFilter)this.buf.push(fn(filter.block,this,filter.attrs));else{var text=filter.block.nodes.join("");this.buffer(utils.text(fn(text,filter.attrs)))}},visitText:function(text){text=utils.text(text.nodes.join("")),this.escape&&(text=escape(text)),this.buffer(text),this.buffer("\\n")},visitComment:function(comment){!comment.buffer||(this.pp&&this.buffer("\\n"+Array(this.indents+1).join(" ")),this.buffer("<!--"+utils.escape(comment.val)+"-->"))},visitBlockComment:function(comment){!comment.buffer||(0==comment.val.indexOf("if")?(this.buffer("<!--["+comment.val+"]>"),this.visit(comment.block),this.buffer("<![endif]-->")):(this.buffer("<!--"+comment.val),this.visit(comment.block),this.buffer("-->")))},visitCode:function(code){if(code.buffer){var val=code.val.trimLeft();this.buf.push("var __val__ = "+val),val='null == __val__ ? "" : __val__',code.escape&&(val="escape("+val+")"),this.buf.push("buf.push("+val+");")}else this.buf.push(code.val);code.block&&(code.buffer||this.buf.push("{"),this.visit(code.block),code.buffer||this.buf.push("}"))},visitEach:function(each){this.buf.push("// iterate "+each.obj+"\n"+"(function(){\n"+" if ('number' == typeof "+each.obj+".length) {\n"+" for (var "+each.key+" = 0, $$l = "+each.obj+".length; "+each.key+" < $$l; "+each.key+"++) {\n"+" var "+each.val+" = "+each.obj+"["+each.key+"];\n"),this.visit(each.block),this.buf.push(" }\n } else {\n for (var "+each.key+" in "+each.obj+") {\n"+" if ("+each.obj+".hasOwnProperty("+each.key+")){"+" var "+each.val+" = "+each.obj+"["+each.key+"];\n"),this.visit(each.block),this.buf.push(" }\n"),this.buf.push(" }\n }\n}).call(this);\n")},visitAttributes:function(attrs){var buf=[],classes=[];this.terse&&buf.push("terse: true"),attrs.forEach(function(attr){if(attr.name=="class")classes.push("("+attr.val+")");else{var pair="'"+attr.name+"':("+attr.val+")";buf.push(pair)}}),classes.length&&(classes=classes.join(" + ' ' + "),buf.push("class: "+classes)),buf=buf.join(", ").replace("class:",'"class":'),this.buf.push("buf.push(attrs({ "+buf+" }));")}};function escape(html){return String(html).replace(/&(?!\w+;)/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}}),require.register("doctypes.js",function(module,exports,require){module.exports={5:"<!DOCTYPE html>",html:"<!DOCTYPE html>",xml:'<?xml version="1.0" encoding="utf-8" ?>',"default":'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',transitional:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',strict:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',frameset:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',1.1:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',basic:'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',mobile:'<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'}}),require.register("filters.js",function(module,exports,require){module.exports={cdata:function(str){return"<![CDATA[\\n"+str+"\\n]]>"},sass:function(str){str=str.replace(/\\n/g,"\n");var sass=require("sass").render(str).replace(/\n/g,"\\n");return"<style>"+sass+"</style>"},stylus:function(str,options){var ret;str=str.replace(/\\n/g,"\n");var stylus=require("stylus");stylus(str,options).render(function(err,css){if(err)throw err;ret=css.replace(/\n/g,"\\n")});return"<style>"+ret+"</style>"},less:function(str){var ret;str=str.replace(/\\n/g,"\n"),require("less").render(str,function(err,css){if(err)throw err;ret="<style>"+css.replace(/\n/g,"\\n")+"</style>"});return ret},markdown:function(str){var md;try{md=require("markdown")}catch(err){try{md=require("discount")}catch(err){try{md=require("markdown-js")}catch(err){throw new Error("Cannot find markdown library, install markdown or discount")}}}str=str.replace(/\\n/g,"\n");return md.parse(str).replace(/\n/g,"\\n").replace(/'/g,"'")},coffeescript:function(str){str=str.replace(/\\n/g,"\n");var js=require("coffee-script").compile(str).replace(/\n/g,"\\n");return'<script type="text/javascript">\\n'+js+"</script>"}}}),require.register("inline-tags.js",function(module,exports,require){module.exports=["a","abbr","acronym","b","br","code","em","font","i","img","ins","kbd","map","samp","small","span","strong","sub","sup"]}),require.register("jade.js",function(module,exports,require){var Parser=require("./parser"),Compiler=require("./compiler"),runtime=require("./runtime");exports.version="0.15.4";var cache=exports.cache={};exports.selfClosing=require("./self-closing"),exports.doctypes=require("./doctypes"),exports.filters=require("./filters"),exports.utils=require("./utils"),exports.Compiler=Compiler,exports.Parser=Parser,exports.nodes=require("./nodes"),exports.runtime=runtime;function parse(str,options){var filename=options.filename;try{var parser=new Parser(str,filename,options),compiler=new(options.compiler||Compiler)(parser.parse(),options),js=compiler.compile();options.debug&&console.log("\n\e[1mCompiled Function\e[0m:\n\n%s",js.replace(/^/gm," "));try{return"var buf = [];\n"+(options.self?"var self = locals || {};\n"+js:"with (locals || {}) {\n"+js+"\n}\n")+'return buf.join("");'}catch(err){process.compile(js,filename||"Jade");return}}catch(err){runtime.rethrow(err,str,filename,parser.lexer.lineno)}}exports.compile=function(str,options){var options=options||{},input=JSON.stringify(str),client=options.client,filename=options.filename?JSON.stringify(options.filename):"undefined",fn;options.compileDebug!==!1?fn=["var __ = { lineno: 1, input: "+input+", filename: "+filename+" };","try {",parse(String(str),options||{}),"} catch (err) {"," rethrow(err, __.input, __.filename, __.lineno);","}"].join("\n"):fn=parse(String(str),options||{}),client&&(fn="var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\n"+fn),fn=new Function("locals, attrs, escape, rethrow",fn);if(client)return fn;return function(locals){return fn(locals,runtime.attrs,runtime.escape,runtime.rethrow)}}}),require.register("lexer.js",function(module,exports,require){var Lexer=module.exports=function Lexer(str,options){options=options||{},this.input=str.replace(/\r\n|\r/g,"\n"),this.colons=options.colons,this.deferredTokens=[],this.lastIndents=0,this.lineno=1,this.stash=[],this.indentStack=[],this.indentRe=null,this.pipeless=!1};Lexer.prototype={tok:function(type,val){return{type:type,line:this.lineno,val:val}},consume:function(len){this.input=this.input.substr(len)},scan:function(regexp,type){var captures;if(captures=regexp.exec(this.input)){this.consume(captures[0].length);return this.tok(type,captures[1])}},defer:function(tok){this.deferredTokens.push(tok)},lookahead:function(n){var fetch=n-this.stash.length;while(fetch-->0)this.stash.push(this.next());return this.stash[--n]},indexOfDelimiters:function(start,end){var str=this.input,nstart=0,nend=0,pos=0;for(var i=0,len=str.length;i<len;++i)if(start==str[i])++nstart;else if(end==str[i]&&++nend==nstart){pos=i;break}return pos},stashed:function(){return this.stash.length&&this.stash.shift()},deferred:function(){return this.deferredTokens.length&&this.deferredTokens.shift()},eos:function(){if(!this.input.length){if(this.indentStack.length){this.indentStack.shift();return this.tok("outdent")}return this.tok("eos")}},comment:function(){var captures;if(captures=/^ *\/\/(-)?([^\n]*)/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("comment",captures[2]);tok.buffer="-"!=captures[1];return tok}},tag:function(){var captures;if(captures=/^(\w[-:\w]*)/.exec(this.input)){this.consume(captures[0].length);var tok,name=captures[1];if(":"==name[name.length-1]){name=name.slice(0,-1),tok=this.tok("tag",name),this.deferredTokens.push(this.tok(":"));while(" "==this.input[0])this.input=this.input.substr(1)}else tok=this.tok("tag",name);return tok}},filter:function(){return this.scan(/^:(\w+)/,"filter")},doctype:function(){return this.scan(/^(?:!!!|doctype) *(\w+)?/,"doctype")},id:function(){return this.scan(/^#([\w-]+)/,"id")},className:function(){return this.scan(/^\.([\w-]+)/,"class")},text:function(){return this.scan(/^(?:\| ?)?([^\n]+)/,"text")},include:function(){return this.scan(/^include +([^\n]+)/,"include")},mixin:function(){var captures;if(captures=/^mixin +([-\w]+)(?:\(([^\)]+)\))?/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("mixin",captures[1]);tok.args=captures[2];return tok}},conditional:function(){var captures;if(captures=/^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)){this.consume(captures[0].length);var type=captures[1],js=captures[2];switch(type){case"if":js="if ("+js+")";break;case"unless":js="if (!("+js+"))";break;case"else if":js="else if ("+js+")";break;case"else":js="else"}return this.tok("code",js)}},each:function(){var captures;if(captures=/^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("each",captures[1]);tok.key=captures[2]||"index",tok.code=captures[3];return tok}},code:function(){var captures;if(captures=/^(!?=|-)([^\n]+)/.exec(this.input)){this.consume(captures[0].length);var flags=captures[1];captures[1]=captures[2];var tok=this.tok("code",captures[1]);tok.escape=flags[0]==="=",tok.buffer=flags[0]==="="||flags[1]==="=";return tok}},attrs:function(){if("("==this.input[0]){var index=this.indexOfDelimiters("(",")"),str=this.input.substr(1,index-1),tok=this.tok("attrs"),len=str.length,colons=this.colons,states=["key"],key="",val="",quote,c;function state(){return states[states.length-1]}function interpolate(attr){return attr.replace(/#\{([^}]+)\}/g,function(_,expr){return quote+" + ("+expr+") + "+quote})}this.consume(index+1),tok.attrs={};function parse(c){var real=c;colons&&":"==c&&(c="=");switch(c){case",":case"\n":switch(state()){case"expr":case"array":case"string":case"object":val+=c;break;default:states.push("key"),val=val.trim(),key=key.trim();if(""==key)return;tok.attrs[key.replace(/^['"]|['"]$/g,"")]=""==val?!0:interpolate(val),key=val=""}break;case"=":switch(state()){case"key char":key+=real;break;case"val":case"expr":case"array":case"string":case"object":val+=real;break;default:states.push("val")}break;case"(":"val"==state()&&states.push("expr"),val+=c;break;case")":"expr"==state()&&states.pop(),val+=c;break;case"{":"val"==state()&&states.push("object"),val+=c;break;case"}":"object"==state()&&states.pop(),val+=c;break;case"[":"val"==state()&&states.push("array"),val+=c;break;case"]":"array"==state()&&states.pop(),val+=c;break;case'"':case"'":switch(state()){case"key":states.push("key char");break;case"key char":states.pop();break;case"string":c==quote&&states.pop(),val+=c;break;default:states.push("string"),val+=c,quote=c}break;case"":break;default:switch(state()){case"key":case"key char":key+=c;break;default:val+=c}}}for(var i=0;i<len;++i)parse(str[i]);parse(",");return tok}},indent:function(){var captures,re;this.indentRe?captures=this.indentRe.exec(this.input):(re=/^\n(\t*) */,captures=re.exec(this.input),captures&&!captures[1].length&&(re=/^\n( *)/,captures=re.exec(this.input)),captures&&captures[1].length&&(this.indentRe=re));if(captures){var tok,indents=captures[1].length;++this.lineno,this.consume(indents+1);if(" "==this.input[0]||"\t"==this.input[0])throw new Error("Invalid indentation, you can use tabs or spaces but not both");if("\n"==this.input[0])return this.tok("newline");if(this.indentStack.length&&indents<this.indentStack[0]){while(this.indentStack.length&&this.indentStack[0]>indents)this.stash.push(this.tok("outdent")),this.indentStack.shift();tok=this.stash.pop()}else indents&&indents!=this.indentStack[0]?(this.indentStack.unshift(indents),tok=this.tok("indent",indents)):tok=this.tok("newline");return tok}},pipelessText:function(){if(this.pipeless){if("\n"==this.input[0])return;var i=this.input.indexOf("\n");-1==i&&(i=this.input.length);var str=this.input.substr(0,i);this.consume(str.length);return this.tok("text",str)}},colon:function(){return this.scan(/^: */,":")},advance:function(){return this.stashed()||this.next()},next:function(){return this.deferred()||this.eos()||this.pipelessText()||this.doctype()||this.include()||this.mixin()||this.conditional()||this.each()||this.tag()||this.filter()||this.code()||this.id()||this.className()||this.attrs()||this.indent()||this.comment()||this.colon()||this.text()}}}),require.register("nodes/block-comment.js",function(module,exports,require){var Node=require("./node"),BlockComment=module.exports=function BlockComment(val,block,buffer){this.block=block,this.val=val,this.buffer=buffer};BlockComment.prototype=new Node,BlockComment.prototype.constructor=BlockComment}),require.register("nodes/block.js",function(module,exports,require){var Node=require("./node"),Block=module.exports=function Block(node){this.nodes=[],node&&this.push(node)};Block.prototype=new Node,Block.prototype.constructor=Block,Block.prototype.push=function(node){return this.nodes.push(node)},Block.prototype.unshift=function(node){return this.nodes.unshift(node)}}),require.register("nodes/code.js",function(module,exports,require){var Node=require("./node"),Code=module.exports=function Code(val,buffer,escape){this.val=val,this.buffer=buffer,this.escape=escape,/^ *else/.test(val)&&(this.instrumentLineNumber=!1)};Code.prototype=new Node,Code.prototype.constructor=Code}),require.register("nodes/comment.js",function(module,exports,require){var Node=require("./node"),Comment=module.exports=function Comment(val,buffer){this.val=val,this.buffer=buffer};Comment.prototype=new Node,Comment.prototype.constructor=Comment}),require.register("nodes/doctype.js",function(module,exports,require){var Node=require("./node"),Doctype=module.exports=function Doctype(val){this.val=val};Doctype.prototype=new Node,Doctype.prototype.constructor=Doctype}),require.register("nodes/each.js",function(module,exports,require){var Node=require("./node"),Each=module.exports=function Each(obj,val,key,block){this.obj=obj,this.val=val,this.key=key,this.block=block};Each.prototype=new Node,Each.prototype.constructor=Each}),require.register("nodes/filter.js",function(module,exports,require){var Node=require("./node"),Block=require("./block"),Filter=module.exports=function Filter(name,block,attrs){this.name=name,this.block=block,this.attrs=attrs,this.isASTFilter=block instanceof Block};Filter.prototype=new Node,Filter.prototype.constructor=Filter}),require.register("nodes/index.js",function(module,exports,require){exports.Node=require("./node"),exports.Tag=require("./tag"),exports.Code=require("./code"),exports.Each=require("./each"),exports.Text=require("./text"),exports.Block=require("./block"),exports.Mixin=require("./mixin"),exports.Filter=require("./filter"),exports.Comment=require("./comment"),exports.Literal=require("./literal"),exports.BlockComment=require("./block-comment"),exports.Doctype=require("./doctype")}),require.register("nodes/literal.js",function(module,exports,require){var Node=require("./node"),Literal=module.exports=function Literal(str){this.str=str};Literal.prototype=new Node,Literal.prototype.constructor=Literal}),require.register("nodes/mixin.js",function(module,exports,require){var Node=require("./node"),Mixin=module.exports=function Mixin(name,args,block){this.name=name,this.args=args,this.block=block};Mixin.prototype=new Node,Mixin.prototype.constructor=Mixin}),require.register("nodes/node.js",function(module,exports,require){var Node=module.exports=function(){}}),require.register("nodes/tag.js",function(module,exports,require){var Node=require("./node"),Block=require("./block"),Tag=module.exports=function Tag(name,block){this.name=name,this.attrs=[],this.block=block||new Block};Tag.prototype=new Node,Tag.prototype.constructor=Tag,Tag.prototype.setAttribute=function(name,val){this.attrs.push({name:name,val:val});return this},Tag.prototype.removeAttribute=function(name){for(var i=0,len=this.attrs.length;i<len;++i)this.attrs[i]&&this.attrs[i].name==name&&delete this.attrs[i]},Tag.prototype.getAttribute=function(name){for(var i=0,len=this.attrs.length;i<len;++i)if(this.attrs[i]&&this.attrs[i].name==name)return this.attrs[i].val}}),require.register("nodes/text.js",function(module,exports,require){var Node=require("./node"),Text=module.exports=function Text(line){this.nodes=[],"string"==typeof line&&this.push(line)};Text.prototype=new Node,Text.prototype.constructor=Text,Text.prototype.push=function(node){return this.nodes.push(node)}}),require.register("parser.js",function(module,exports,require){var Lexer=require("./lexer"),nodes=require("./nodes"),Parser=exports=module.exports=function Parser(str,filename,options){this.input=str,this.lexer=new Lexer(str,options),this.filename=filename},textOnly=exports.textOnly=["code","script","textarea","style","title"];Parser.prototype={advance:function(){return this.lexer.advance()},skip:function(n){while(n--)this.advance()},peek:function(){return this.lookahead(1)},line:function(){return this.lexer.lineno},lookahead:function(n){return this.lexer.lookahead(n)},parse:function(){var block=new nodes.Block;block.line=this.line();while("eos"!=this.peek().type)"newline"==this.peek().type?this.advance():block.push(this.parseExpr());return block},expect:function(type){if(this.peek().type===type)return this.advance();throw new Error('expected "'+type+'", but got "'+this.peek().type+'"')},accept:function(type){if(this.peek().type===type)return this.advance()},parseExpr:function(){switch(this.peek().type){case"tag":return this.parseTag();case"mixin":return this.parseMixin();case"include":return this.parseInclude();case"doctype":return this.parseDoctype();case"filter":return this.parseFilter();case"comment":return this.parseComment();case"text":return this.parseText();case"each":return this.parseEach();case"code":return this.parseCode();case"id":case"class":var tok=this.advance();this.lexer.defer(this.lexer.tok("tag","div")),this.lexer.defer(tok);return this.parseExpr();default:throw new Error('unexpected token "'+this.peek().type+'"')}},parseText:function(){var tok=this.expect("text"),node=new nodes.Text(tok.val);node.line=this.line();return node},parseCode:function(){var tok=this.expect("code"),node=new nodes.Code(tok.val,tok.buffer,tok.escape),block,i=1;node.line=this.line();while(this.lookahead(i)&&"newline"==this.lookahead(i).type)++i;block="indent"==this.lookahead(i).type,block&&(this.skip(i-1),node.block=this.parseBlock());return node},parseComment:function(){var tok=this.expect("comment"),node;"indent"==this.peek().type?node=new nodes.BlockComment(tok.val,this.parseBlock(),tok.buffer):node=new nodes.Comment(tok.val,tok.buffer),node.line=this.line();return node},parseDoctype:function(){var tok=this.expect("doctype"),node=new nodes.Doctype(tok.val);node.line=this.line();return node},parseFilter:function(){var block,tok=this.expect("filter"),attrs=this.accept("attrs");this.lexer.pipeless=!0,block=this.parseTextBlock(),this.lexer.pipeless=!1;var node=new nodes.Filter(tok.val,block,attrs&&attrs.attrs);node.line=this.line();return node},parseASTFilter:function(){var block,tok=this.expect("tag"),attrs=this.accept("attrs");this.expect(":"),block=this.parseBlock();var node=new nodes.Filter(tok.val,block,attrs&&attrs.attrs);node.line=this.line();return node},parseEach:function(){var tok=this.expect("each"),node=new nodes.Each(tok.code,tok.val,tok.key,this.parseBlock());node.line=this.line();return node},parseInclude:function(){var path=require("path"),fs=require("fs"),dirname=path.dirname,basename=path.basename,join=path.join;if(!this.filename)throw new Error('the "filename" option is required to use includes');var path=name=this.expect("include").val.trim(),dir=dirname(this.filename);if(~basename(path).indexOf(".")){var path=join(dir,path),str=fs.readFileSync(path,"utf8");return new nodes.Literal(str)}var path=join(dir,path+".jade"),str=fs.readFileSync(path,"utf8"),parser=new Parser(str,path),ast=parser.parse();return ast},parseMixin:function(){var tok=this.expect("mixin"),name=tok.val,args=tok.args,block="indent"==this.peek().type?this.parseBlock():null;return new nodes.Mixin(name,args,block)},parseTextBlock:function(){var text=new nodes.Text;text.line=this.line();var spaces=this.expect("indent").val;null==this._spaces&&(this._spaces=spaces);var indent=Array(spaces-this._spaces+1).join(" ");while("outdent"!=this.peek().type)switch(this.peek().type){case"newline":text.push("\\n"),this.advance();break;case"indent":text.push("\\n"),this.parseTextBlock().nodes.forEach(function(node){text.push(node)}),text.push("\\n");break;default:text.push(indent+this.advance().val)}spaces==this._spaces&&(this._spaces=null),this.expect("outdent");return text},parseBlock:function(){var block=new nodes.Block;block.line=this.line(),this.expect("indent");while("outdent"!=this.peek().type)"newline"==this.peek().type?this.advance():block.push(this.parseExpr());this.expect("outdent");return block},parseTag:function(){var i=2;"attrs"==this.lookahead(i).type&&++i;if(":"==this.lookahead(i).type&&"indent"==this.lookahead(++i).type)return this.parseASTFilter();var name=this.advance().val,tag=new nodes.Tag(name),dot;tag.line=this.line();out:for(;;)switch(this.peek().type){case"id":case"class":var tok=this.advance();tag.setAttribute(tok.type,"'"+tok.val+"'");continue;case"attrs":var obj=this.advance().attrs,names=Object.keys(obj);for(var i=0,len=names.length;i<len;++i){var name=names[i],val=obj[name];tag.setAttribute(name,val)}continue;default:break out}"."==this.peek().val&&(dot=tag.textOnly=!0,this.advance());switch(this.peek().type){case"text":tag.text=this.parseText();break;case"code":tag.code=this.parseCode();break;case":":this.advance(),tag.block=new nodes.Block,tag.block.push(this.parseTag())}while("newline"==this.peek().type)this.advance();tag.textOnly=tag.textOnly||~textOnly.indexOf(tag.name);if("script"==tag.name){var type=tag.getAttribute("type");!dot&&type&&"text/javascript"!=type.replace(/^['"]|['"]$/g,"")&&(tag.textOnly=!1)}if("indent"==this.peek().type)if(tag.textOnly)this.lexer.pipeless=!0,tag.block=this.parseTextBlock(),this.lexer.pipeless=!1;else{var block=this.parseBlock();if(tag.block)for(var i=0,len=block.nodes.length;i<len;++i)tag.block.push(block.nodes[i]);else tag.block=block}return tag}}}),require.register("runtime.js",function(module,exports,require){Array.isArray||(Array.isArray=function(arr){return"[object Array]"==toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(obj);return arr}),exports.attrs=function(obj){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i<len;++i){var key=keys[i],val=obj[key];"boolean"==typeof val||null==val?val&&(terse?buf.push(key):buf.push(key+'="'+key+'"')):"class"==key&&Array.isArray(val)?buf.push(key+'="'+exports.escape(val.join(" "))+'"'):buf.push(key+'="'+exports.escape(val)+'"')}}return buf.join(" ")},exports.escape=function(html){return String(html).replace(/&(?!\w+;)/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},exports.rethrow=function(err,str,filename,lineno){var context=3,lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}}),require.register("self-closing.js",function(module,exports,require){module.exports=["meta","img","link","input","area","base","col","br","hr"]}),require.register("utils.js",function(module,exports,require){var interpolate=exports.interpolate=function(str){return str.replace(/(\\)?([#!]){(.*?)}/g,function(str,escape,flag,code){return escape?str:"' + "+("!"==flag?"":"escape")+"((interp = "+code.replace(/\\'/g,"'")+") == null ? '' : interp) + '"})},escape=exports.escape=function(str){return str.replace(/'/g,"\\'")};exports.text=function(str){return interpolate(escape(str))}})
\ No newline at end of file
this.hasCompiledDoctype = false;
this.hasCompiledTag = false;
this.pp = options.pretty || false;
+ this.debug = false !== options.compileDebug;
this.indents = 0;
if (options.doctype) this.setDoctype(options.doctype);
};
*/
compile: function(){
- this.buf = [];
+ this.buf = ['var interp;'];
+ this.lastBufferedIdx = -1
this.visit(this.node);
return this.buf.join('\n');
},
buffer: function(str, esc){
if (esc) str = utils.escape(str);
- this.buf.push("buf.push('" + str + "');");
+
+ if (this.lastBufferedIdx == this.buf.length) {
+ this.lastBuffered += str;
+ this.buf[this.lastBufferedIdx - 1] = "buf.push('" + this.lastBuffered + "');"
+ } else {
+ this.buf.push("buf.push('" + str + "');");
+ this.lastBuffered = str;
+ this.lastBufferedIdx = this.buf.length;
+ }
},
/**
*/
line: function(node){
- if (node.instrumentLineNumber === false) return;
+ if (false === node.instrumentLineNumber) return;
this.buf.push('__.lineno = ' + node.line + ';');
},
*/
visit: function(node){
- this.line(node);
+ if (this.debug) this.line(node);
return this.visitNode(node);
},
|| node.constructor.toString().match(/function ([^(\s]+)()/)[1];
return this['visit' + name](node);
},
-
+
+ /**
+ * Visit literal `node`.
+ *
+ * @param {Literal} node
+ * @api public
+ */
+
+ visitLiteral: function(node){
+ var str = node.str.replace(/\n/g, '\\\\n');
+ this.buffer(str);
+ },
+
/**
* Visit all nodes in `block`.
*
*/
visitBlock: function(block){
- var len = len = block.nodes.length;
+ var len = block.nodes.length;
for (var i = 0; i < len; ++i) {
this.visit(block.nodes[i]);
}
var Parser = require('./parser')
, Compiler = require('./compiler')
+ , runtime = require('./runtime')
// if node
, fs = require('fs');
// end
* Library version.
*/
-exports.version = '0.13.0';
+exports.version = '0.15.4';
/**
* Intermediate JavaScript cache.
exports.nodes = require('./nodes');
/**
- * Render the given attributes object.
- *
- * @param {Object} obj
- * @return {String}
- * @api private
- */
-
-function attrs(obj){
- var buf = []
- , terse = obj.terse;
- delete obj.terse;
- var keys = Object.keys(obj)
- , len = keys.length;
- if (len) {
- buf.push('');
- for (var i = 0; i < len; ++i) {
- var key = keys[i]
- , val = obj[key];
- if ('boolean' == typeof val || null == val) {
- if (val) {
- terse
- ? buf.push(key)
- : buf.push(key + '="' + key + '"');
- }
- } else if ('class' == key && Array.isArray(val)) {
- buf.push(key + '="' + escape(val.join(' ')) + '"');
- } else {
- buf.push(key + '="' + escape(val) + '"');
- }
- }
- }
- return buf.join(' ');
-}
-
-/**
- * Escape the given string of `html`.
- *
- * @param {String} html
- * @return {String}
- * @api private
+ * Jade runtime helpers.
*/
-function escape(html){
- return String(html)
- .replace(/&(?!\w+;)/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"');
-}
-
-/**
- * Re-throw the given `err` in context to the
- * `str` of jade, `filename`, and `lineno`.
- *
- * @param {Error} err
- * @param {String} str
- * @param {String} filename
- * @param {String} lineno
- * @api private
- */
-
-function rethrow(err, str, filename, lineno){
- var context = 3
- , lines = str.split('\n')
- , start = Math.max(lineno - context, 0)
- , end = Math.min(lines.length, lineno + context);
-
- // Error context
- var context = lines.slice(start, end).map(function(line, i){
- var curr = i + start + 1;
- return (curr == lineno ? ' > ' : ' ')
- + curr
- + '| '
- + line;
- }).join('\n');
-
- // Alter exception message
- err.path = filename;
- err.message = (filename || 'Jade') + ':' + lineno
- + '\n' + context + '\n\n' + err.message;
- throw err;
-}
+exports.runtime = runtime;
/**
* Parse the given `str` of jade and return a function body.
function parse(str, options){
var filename = options.filename;
+
try {
// Parse
- var parser = new Parser(str, filename);
- if (options.debug) parser.debug();
+ var parser = new Parser(str, filename, options);
// Compile
var compiler = new (options.compiler || Compiler)(parser.parse(), options)
try {
return ''
- + attrs.toString() + '\n\n'
- + escape.toString() + '\n\n'
+ 'var buf = [];\n'
+ (options.self
- ? 'var self = locals || {}, __ = __ || locals.__;\n' + js
- : 'with (locals || {}) {' + js + '}')
+ ? 'var self = locals || {};\n' + js
+ : 'with (locals || {}) {\n' + js + '\n}\n')
+ 'return buf.join("");';
+
} catch (err) {
process.compile(js, filename || 'Jade');
return;
}
} catch (err) {
- rethrow(err, str, filename, parser.lexer.lineno);
+ runtime.rethrow(err, str, filename, parser.lexer.lineno);
}
}
/**
* Compile a `Function` representation of the given jade `str`.
*
+ * Options:
+ *
+ * - `compileDebug` when `false` debugging code is stripped from the compiled template
+ * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()`
+ * for use with the Jade client-side runtime.js
+ *
* @param {String} str
* @param {Options} options
* @return {Function}
exports.compile = function(str, options){
var options = options || {}
, input = JSON.stringify(str)
+ , client = options.client
, filename = options.filename
? JSON.stringify(options.filename)
- : 'undefined';
-
- // Reduce closure madness by injecting some locals
- var fn = [
- 'var __ = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };'
- , rethrow.toString()
- , 'try {'
- , parse(String(str), options || {})
- , '} catch (err) {'
- , ' rethrow(err, __.input, __.filename, __.lineno);'
- , '}'
- ].join('\n');
-
- return new Function('locals', fn);
-};
-
-/**
- * Render the given `str` of jade.
- *
- * Options:
- *
- * - `scope` Evaluation scope (`this`)
- * - `locals` Local variable object
- * - `filename` Used in exceptions, and required by `cache`
- * - `cache` Cache intermediate JavaScript in memory keyed by `filename`
- * - `compiler` Compiler to replade jade's default
- * - `doctype` Specify the default doctype
- *
- * @param {String|Buffer} str
- * @param {Object} options
- * @return {String}
- * @api public
- */
-
-exports.render = function(str, options){
- var fn
- , options = options || {}
- , filename = options.filename;
-
- // Accept Buffers
- str = String(str);
-
- // Cache support
- if (options.cache) {
- if (filename) {
- if (cache[filename]) {
- fn = cache[filename];
- } else {
- fn = cache[filename] = new Function('locals', parse(str, options));
- }
- } else {
- throw new Error('filename is required when using the cache option');
- }
+ : 'undefined'
+ , fn;
+
+ if (options.compileDebug !== false) {
+ fn = [
+ 'var __ = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };'
+ , 'try {'
+ , parse(String(str), options || {})
+ , '} catch (err) {'
+ , ' rethrow(err, __.input, __.filename, __.lineno);'
+ , '}'
+ ].join('\n');
} else {
- fn = new Function('locals', parse(str, options));
+ fn = parse(String(str), options || {});
}
- // Render the template
- try {
- var locals = options.locals || {}
- , meta = { lineno: 1 };
- locals.__ = meta;
- return fn.call(options.scope, locals);
- } catch (err) {
- rethrow(err, str, filename, meta.lineno);
+ if (client) {
+ fn = 'var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\n' + fn;
}
-};
-/**
- * Render jade template at the given `path`.
- *
- * @param {String} path
- * @param {Object} options
- * @param {Function} fn
- * @api public
- */
+ fn = new Function('locals, attrs, escape, rethrow', fn);
-exports.renderFile = function(path, options, fn){
- var ret;
+ if (client) return fn;
- if (typeof options === 'function') {
- fn = options;
- options = {};
- }
- options.filename = path;
-
- // Primed cache
- if (options.cache && cache[path]) {
- try {
- ret = exports.render('', options);
- } catch (err) {
- return fn(err);
- }
- fn(null, ret);
- } else {
- fs.readFile(path, 'utf8', function(err, str){
- if (err) return fn(err);
- try {
- ret = exports.render(str, options);
- } catch (err) {
- return fn(err);
- }
- fn(null, ret);
- });
- }
+ return function(locals){
+ return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow);
+ };
};
/**
* Initialize `Lexer` with the given `str`.
*
+ * Options:
+ *
+ * - `colons` allow colons for attr delimiters
+ *
* @param {String} str
+ * @param {Object} options
* @api private
*/
-var Lexer = module.exports = function Lexer(str) {
+var Lexer = module.exports = function Lexer(str, options) {
+ options = options || {};
this.input = str.replace(/\r\n|\r/g, '\n');
+ this.colons = options.colons;
this.deferredTokens = [];
this.lastIndents = 0;
this.lineno = 1;
}
},
+ /**
+ * Conditional.
+ */
+
+ conditional: function() {
+ var captures;
+ if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
+ this.consume(captures[0].length);
+ var type = captures[1]
+ , js = captures[2];
+
+ switch (type) {
+ case 'if': js = 'if (' + js + ')'; break;
+ case 'unless': js = 'if (!(' + js + '))'; break;
+ case 'else if': js = 'else if (' + js + ')'; break;
+ case 'else': js = 'else'; break;
+ }
+
+ return this.tok('code', js);
+ }
+ },
+
/**
* Each.
*/
each: function() {
var captures;
- if (captures = /^- *each *(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
+ if (captures = /^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('each', captures[1]);
tok.key = captures[2] || 'index';
, str = this.input.substr(1, index-1)
, tok = this.tok('attrs')
, len = str.length
+ , colons = this.colons
, states = ['key']
, key = ''
, val = ''
tok.attrs = {};
function parse(c) {
+ var real = c;
+ // TODO: remove when people fix ":"
+ if (colons && ':' == c) c = '=';
switch (c) {
case ',':
case '\n':
case '=':
switch (state()) {
case 'key char':
- key += c;
+ key += real;
break;
case 'val':
case 'expr':
case 'array':
case 'string':
case 'object':
- val += c;
+ val += real;
break;
default:
states.push('val');
|| this.doctype()
|| this.include()
|| this.mixin()
+ || this.conditional()
+ || this.each()
|| this.tag()
|| this.filter()
- || this.each()
|| this.code()
|| this.id()
|| this.className()
exports.Mixin = require('./mixin');
exports.Filter = require('./filter');
exports.Comment = require('./comment');
+exports.Literal = require('./literal');
exports.BlockComment = require('./block-comment');
exports.Doctype = require('./doctype');
--- /dev/null
+
+/*!
+ * Jade - nodes - Literal
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Literal` node with the given `str.
+ *
+ * @param {String} str
+ * @api public
+ */
+
+var Literal = module.exports = function Literal(str) {
+ this.str = str;
+};
+
+/**
+ * Inherit from `Node`.
+ */
+
+Literal.prototype.__proto__ = Node.prototype;
*/
var Lexer = require('./lexer')
- , nodes = require('./nodes')
- , path = require('path')
- , dirname = path.dirname
- , join = path.join
- , fs = require('fs');
+ , nodes = require('./nodes');
/**
* Initialize `Parser` with the given input `str` and `filename`.
*
* @param {String} str
* @param {String} filename
+ * @param {Object} options
* @api public
*/
-var Parser = exports = module.exports = function Parser(str, filename){
+var Parser = exports = module.exports = function Parser(str, filename, options){
this.input = str;
- this.lexer = new Lexer(str);
+ this.lexer = new Lexer(str, options);
this.filename = filename;
};
Parser.prototype = {
- /**
- * Output parse tree to stdout.
- *
- * @api public
- */
-
- debug: function(){
- var lexer = new Lexer(this.input)
- , tree = require('sys').inspect(this.parse(), false, 12, true);
- console.log('\n\x1b[1mParse Tree\x1b[0m:\n');
- console.log(tree);
- this.lexer = lexer;
- },
-
/**
* Return the next token object.
*
advance: function(){
return this.lexer.advance();
},
+
+ /**
+ * Skip `n` tokens.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+ skip: function(n){
+ while (n--) this.advance();
+ },
/**
* Single token lookahead.
parseCode: function(){
var tok = this.expect('code')
- , node = new nodes.Code(tok.val, tok.buffer, tok.escape);
+ , node = new nodes.Code(tok.val, tok.buffer, tok.escape)
+ , block
+ , i = 1;
node.line = this.line();
- if ('indent' == this.peek().type) {
+ while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i;
+ block = 'indent' == this.lookahead(i).type;
+ if (block) {
+ this.skip(i-1);
node.block = this.parseBlock();
}
return node;
*/
parseInclude: function(){
+ var path = require('path')
+ , fs = require('fs')
+ , dirname = path.dirname
+ , basename = path.basename
+ , join = path.join;
+
if (!this.filename)
throw new Error('the "filename" option is required to use includes');
var path = name = this.expect('include').val.trim()
- , dir = dirname(this.filename)
- , path = join(dir, path + '.jade');
+ , dir = dirname(this.filename);
+
+ // non-jade
+ if (~basename(path).indexOf('.')) {
+ var path = join(dir, path)
+ , str = fs.readFileSync(path, 'utf8');
+ return new nodes.Literal(str);
+ }
- var str = fs.readFileSync(path, 'utf8')
+ var path = join(dir, path + '.jade')
+ , str = fs.readFileSync(path, 'utf8')
, parser = new Parser(str, path)
, ast = parser.parse();
}
var name = this.advance().val
- , tag = new nodes.Tag(name);
+ , tag = new nodes.Tag(name)
+ , dot;
tag.line = this.line();
// check immediate '.'
if ('.' == this.peek().val) {
- tag.textOnly = true;
+ dot = tag.textOnly = true;
this.advance();
}
// script special-case
if ('script' == tag.name) {
var type = tag.getAttribute('type');
- if (type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
+ if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
tag.textOnly = false;
}
}
--- /dev/null
+
+/*!
+ * Jade - runtime
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Lame Array.isArray() polyfill for now.
+ */
+
+if (!Array.isArray) {
+ Array.isArray = function(arr){
+ return '[object Array]' == toString.call(arr);
+ };
+}
+
+/**
+ * Lame Object.keys() polyfill for now.
+ */
+
+if (!Object.keys) {
+ Object.keys = function(obj){
+ var arr = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ arr.push(obj);
+ }
+ }
+ return arr;
+ }
+}
+
+/**
+ * Render the given attributes object.
+ *
+ * @param {Object} obj
+ * @return {String}
+ * @api private
+ */
+
+exports.attrs = function attrs(obj){
+ var buf = []
+ , terse = obj.terse;
+ delete obj.terse;
+ var keys = Object.keys(obj)
+ , len = keys.length;
+ if (len) {
+ buf.push('');
+ for (var i = 0; i < len; ++i) {
+ var key = keys[i]
+ , val = obj[key];
+ if ('boolean' == typeof val || null == val) {
+ if (val) {
+ terse
+ ? buf.push(key)
+ : buf.push(key + '="' + key + '"');
+ }
+ } else if ('class' == key && Array.isArray(val)) {
+ buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
+ } else {
+ buf.push(key + '="' + exports.escape(val) + '"');
+ }
+ }
+ }
+ return buf.join(' ');
+};
+
+/**
+ * Escape the given string of `html`.
+ *
+ * @param {String} html
+ * @return {String}
+ * @api private
+ */
+
+exports.escape = function escape(html){
+ return String(html)
+ .replace(/&(?!\w+;)/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"');
+};
+
+/**
+ * Re-throw the given `err` in context to the
+ * `str` of jade, `filename`, and `lineno`.
+ *
+ * @param {Error} err
+ * @param {String} str
+ * @param {String} filename
+ * @param {String} lineno
+ * @api private
+ */
+
+exports.rethrow = function rethrow(err, str, filename, lineno){
+ var context = 3
+ , lines = str.split('\n')
+ , start = Math.max(lineno - context, 0)
+ , end = Math.min(lines.length, lineno + context);
+
+ // Error context
+ var context = lines.slice(start, end).map(function(line, i){
+ var curr = i + start + 1;
+ return (curr == lineno ? ' > ' : ' ')
+ + curr
+ + '| '
+ + line;
+ }).join('\n');
+
+ // Alter exception message
+ err.path = filename;
+ err.message = (filename || 'Jade') + ':' + lineno
+ + '\n' + context + '\n\n' + err.message;
+ throw err;
+};
{
"name": "jade",
"description": "Jade template engine",
- "version": "0.13.0",
+ "version": "0.15.4",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"repository": "git://github.com/visionmedia/jade",
"main": "./index.js",
"bin": { "jade": "./bin/jade" },
+ "dependencies": {
+ "commander": "0.1.0",
+ "mkdirp": "0.0.6"
+ },
"devDependencies": {
"expresso": "0.6.4",
"coffee-script": ">= 0.0.1",
"sass": ">= 0.0.1",
"less": ">= 0.0.1",
"markdown": ">= 0.0.1",
- "stylus": ">= 0.0.1"
+ "stylus": ">= 0.0.1",
+ "uubench": "0.0.1"
},
"scripts" : { "prepublish" : "npm prune" },
"engines": { "node": ">= 0.1.98" }
--- /dev/null
+
+var jade = (function(exports){
+/*!
+ * Jade - runtime
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Lame Array.isArray() polyfill for now.
+ */
+
+if (!Array.isArray) {
+ Array.isArray = function(arr){
+ return '[object Array]' == toString.call(arr);
+ };
+}
+
+/**
+ * Lame Object.keys() polyfill for now.
+ */
+
+if (!Object.keys) {
+ Object.keys = function(obj){
+ var arr = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ arr.push(obj);
+ }
+ }
+ return arr;
+ }
+}
+
+/**
+ * Render the given attributes object.
+ *
+ * @param {Object} obj
+ * @return {String}
+ * @api private
+ */
+
+exports.attrs = function attrs(obj){
+ var buf = []
+ , terse = obj.terse;
+ delete obj.terse;
+ var keys = Object.keys(obj)
+ , len = keys.length;
+ if (len) {
+ buf.push('');
+ for (var i = 0; i < len; ++i) {
+ var key = keys[i]
+ , val = obj[key];
+ if ('boolean' == typeof val || null == val) {
+ if (val) {
+ terse
+ ? buf.push(key)
+ : buf.push(key + '="' + key + '"');
+ }
+ } else if ('class' == key && Array.isArray(val)) {
+ buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
+ } else {
+ buf.push(key + '="' + exports.escape(val) + '"');
+ }
+ }
+ }
+ return buf.join(' ');
+};
+
+/**
+ * Escape the given string of `html`.
+ *
+ * @param {String} html
+ * @return {String}
+ * @api private
+ */
+
+exports.escape = function escape(html){
+ return String(html)
+ .replace(/&(?!\w+;)/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"');
+};
+
+/**
+ * Re-throw the given `err` in context to the
+ * `str` of jade, `filename`, and `lineno`.
+ *
+ * @param {Error} err
+ * @param {String} str
+ * @param {String} filename
+ * @param {String} lineno
+ * @api private
+ */
+
+exports.rethrow = function rethrow(err, str, filename, lineno){
+ var context = 3
+ , lines = str.split('\n')
+ , start = Math.max(lineno - context, 0)
+ , end = Math.min(lines.length, lineno + context);
+
+ // Error context
+ var context = lines.slice(start, end).map(function(line, i){
+ var curr = i + start + 1;
+ return (curr == lineno ? ' > ' : ' ')
+ + curr
+ + '| '
+ + line;
+ }).join('\n');
+
+ // Alter exception message
+ err.path = filename;
+ err.message = (filename || 'Jade') + ':' + lineno
+ + '\n' + context + '\n\n' + err.message;
+ throw err;
+};
+
+ return exports;
+
+})({});
\ No newline at end of file
--- /dev/null
+var jade=function(exports){Array.isArray||(Array.isArray=function(arr){return"[object Array]"==toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(obj);return arr}),exports.attrs=function(obj){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i<len;++i){var key=keys[i],val=obj[key];"boolean"==typeof val||null==val?val&&(terse?buf.push(key):buf.push(key+'="'+key+'"')):"class"==key&&Array.isArray(val)?buf.push(key+'="'+exports.escape(val.join(" "))+'"'):buf.push(key+'="'+exports.escape(val)+'"')}}return buf.join(" ")},exports.escape=function(html){return String(html).replace(/&(?!\w+;)/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},exports.rethrow=function(err,str,filename,lineno){var context=3,lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err};return exports}({})
\ No newline at end of file
--- /dev/null
+
+/**
+ * Module dependencies.
+ */
+
+var uubench = require('uubench')
+ , jade = require('../');
+
+
+var suite = new uubench.Suite({
+ min: 200,
+ result: function(name, stats){
+ var persec = 1000 / stats.elapsed
+ , ops = stats.iterations * persec;
+ console.log('%s: %d', name, ops | 0);
+ }
+});
+
+var str = 'html\n body\n h1 Title'
+ , fn = jade.compile(str);
+
+suite.bench('tiny', function(next){
+ fn();
+ next();
+});
+
+str = '\
+html\n\
+ body\n\
+ h1 Title\n\
+ ul#menu\n\
+ li: a(href="#") Home\n\
+ li: a(href="#") About Us\n\
+ li: a(href="#") Store\n\
+ li: a(href="#") FAQ\n\
+ li: a(href="#") Contact\n\
+';
+
+var fn2 = jade.compile(str);
+
+suite.bench('small', function(next){
+ fn2();
+ next();
+});
+
+str = '\
+html\n\
+ body\n\
+ h1 #{title}\n\
+ ul#menu\n\
+ - each link in links\r\n\
+ li: a(href="#")= link\r\n\
+';
+
+var fn3 = jade.compile(str);
+
+suite.bench('small locals', function(next){
+ fn3({ title: 'Title', links: ['Home', 'About Us', 'Store', 'FAQ', 'Contact'] });
+ next();
+});
+
+suite.run();
\ No newline at end of file
--- /dev/null
+
+ return exports;
+
+})({});
\ No newline at end of file
--- /dev/null
+
+var jade = (function(exports){
\ No newline at end of file
* Module dependencies.
*/
-var jade = require('../'),
- Compiler = jade.Compiler,
- render = jade.render,
- nodes = jade.nodes;
+var jade = require('../')
+ , Compiler = jade.Compiler
+ , nodes = jade.nodes;
+
+// Shortcut
+
+var render = function(str, options){
+ var fn = jade.compile(str, options);
+ return fn(options);
+};
jade.filters.conditionals = function(block, compiler){
return new Visitor(block).compile();
Visitor.prototype.__proto__ = Compiler.prototype;
Visitor.prototype.visit = function(node){
- if (node.name != 'else') this.line(node);
+ if (node.name != 'or') this.line(node);
this.visitNode(node);
};
Visitor.prototype.visitTag = function(node){
switch (node.name) {
- case 'if':
- // First text -> line
- var condition = node.text[0]
- , block = node.block;
- node = new nodes.Code('if (' + condition + ')');
- node.block = block;
- this.visit(node);
- break;
- case 'else':
+ case 'or':
var block = node.block;
node = new nodes.Code('else');
node.block = block;
};
module.exports = {
- 'test :cdata filter': function(assert){
- assert.equal('<![CDATA[\nfoo\n]]>', render(':cdata\n foo'));
- assert.equal('<![CDATA[\nfoo\nbar\n]]>', render(':cdata\n foo\n bar'));
- assert.equal('<![CDATA[\nfoo\nbar\n]]><p>something else</p>', render(':cdata\n foo\n bar\np something else'));
- },
-
- 'test :markdown filter': function(assert){
- assert.equal(
- '<h1>foo</h1>\n\n<ul><li>bar</li><li>baz</li></ul>',
- render(':markdown\n #foo\n - bar\n - baz\n'))
- },
-
- 'test :stylus filter': function(assert){
- assert.equal(
- '<style>body {\n color: #c00;\n}\n</style>',
- render(':stylus\n body\n color #c00'));
- },
-
- 'test :stylus filter with options': function(assert){
- assert.equal(
- '<style>body{color:#c00}\n</style>',
- render(':stylus(compress=true)\n body\n color #c00'));
- },
-
- 'test :less filter': function(assert){
- assert.equal(
- '<style>.class {\n width: 20px;\n}\n</style>',
- render(':less\n .class { width: 10px * 2 }\n'));
- },
-
- 'test :coffeescript filter': function(assert){
- var coffee, js;
- coffee = [
- ':coffeescript',
- ' square = (x) ->',
- ' x * x'
- ].join('\n');
- js = [
- '<script type="text/javascript">',
- '(function() {',
- ' var square;',
- ' square = function(x) {',
- ' return x * x;',
- ' };',
- '}).call(this);',
- '</script>'
- ].join('\n');
-
- assert.equal(js, render(coffee));
-
- coffee = [
- ':coffeescript',
- ' $ ->',
- ' $("#flash").fadeIn ->',
- ' console.log("first line")',
- ' console.log("second line")'
- ].join('\n');
- js = [
- '<script type="text/javascript">',
- '(function() {',
- ' $(function() {',
- ' return $("#flash").fadeIn(function() {',
- ' console.log("first line");',
- ' return console.log("second line");',
- ' });',
- ' });',
- '}).call(this);',
- '</script>'
- ].join('\n');
-
- assert.equal(js, render(coffee));
- },
-
- 'test parse tree': function(assert){
- var str = [
- 'conditionals:',
- ' if false',
- ' | oh noes',
- ' else',
- ' if null == false',
- ' p doh',
- ' else',
- ' p amazing!'
- ].join('\n');
-
- var html = [
- '<p>amazing!</p>'
- ].join('');
-
- assert.equal(html, render(str));
- },
-
- 'test filter attrs': function(assert){
- jade.filters.testing = function(str, attrs){
- return str + ' ' + attrs.stuff;
- };
-
- var str = [
- ':testing(stuff)',
- ' foo bar',
- ].join('\n');
-
- assert.equal('foo bar true', render(str));
- }
+ 'test :cdata filter': function(assert){
+ assert.equal('<![CDATA[\nfoo\n]]>', render(':cdata\n foo'));
+ assert.equal('<![CDATA[\nfoo\nbar\n]]>', render(':cdata\n foo\n bar'));
+ assert.equal('<![CDATA[\nfoo\nbar\n]]><p>something else</p>', render(':cdata\n foo\n bar\np something else'));
+ },
+
+ 'test :markdown filter': function(assert){
+ assert.equal(
+ '<h1>foo</h1>\n\n<ul><li>bar</li><li>baz</li></ul>',
+ render(':markdown\n #foo\n - bar\n - baz\n'))
+ },
+
+ 'test :stylus filter': function(assert){
+ assert.equal(
+ '<style>body {\n color: #c00;\n}\n</style>',
+ render(':stylus\n body\n color #c00'));
+ },
+
+ 'test :stylus filter with options': function(assert){
+ assert.equal(
+ '<style>body{color:#c00}\n</style>',
+ render(':stylus(compress=true)\n body\n color #c00'));
+ },
+
+ 'test :less filter': function(assert){
+ assert.equal(
+ '<style>.class {\n width: 20px;\n}\n</style>',
+ render(':less\n .class { width: 10px * 2 }\n'));
+ },
+
+ 'test :coffeescript filter': function(assert){
+ var coffee, js;
+ coffee = [
+ ':coffeescript',
+ ' square = (x) ->',
+ ' x * x'
+ ].join('\n');
+ js = [
+ '<script type="text/javascript">',
+ '(function() {',
+ ' var square;',
+ ' square = function(x) {',
+ ' return x * x;',
+ ' };',
+ '}).call(this);',
+ '</script>'
+ ].join('\n');
+
+ assert.equal(js, render(coffee));
+
+ coffee = [
+ ':coffeescript',
+ ' $ ->',
+ ' $("#flash").fadeIn ->',
+ ' console.log("first line")',
+ ' console.log("second line")'
+ ].join('\n');
+ js = [
+ '<script type="text/javascript">',
+ '(function() {',
+ ' $(function() {',
+ ' return $("#flash").fadeIn(function() {',
+ ' console.log("first line");',
+ ' return console.log("second line");',
+ ' });',
+ ' });',
+ '}).call(this);',
+ '</script>'
+ ].join('\n');
+
+ assert.equal(js, render(coffee));
+ },
+
+ 'test parse tree': function(assert){
+ var str = [
+ 'conditionals:',
+ ' if false',
+ ' | oh noes',
+ ' or',
+ ' if null == false',
+ ' p doh',
+ ' or',
+ ' p amazing!'
+ ].join('\n');
+
+ var html = [
+ '<p>amazing!</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+ },
+
+ 'test filter attrs': function(assert){
+ jade.filters.testing = function(str, attrs){
+ return str + ' ' + attrs.stuff;
+ };
+
+ var str = [
+ ':testing(stuff)',
+ ' foo bar',
+ ].join('\n');
+
+ assert.equal('foo bar true', render(str));
+ }
};
\ No newline at end of file
* Module dependencies.
*/
-var jade = require('../')
- , ENOENT;
-
-// COMPAT:
-
-try {
- ENOENT = require('constants').ENOENT;
-} catch (err) {
- ENOENT = process.ENOENT;
-}
+var jade = require('../');
// Shortcut
-var render = jade.render;
+var render = function(str, options){
+ var fn = jade.compile(str, options);
+ return fn(options);
+};
module.exports = {
- 'test .version': function(assert){
- assert.ok(/^\d+\.\d+\.\d+$/.test(jade.version), "Invalid version format");
- },
-
- 'test exports': function(assert){
- assert.equal('object', typeof jade.selfClosing, 'exports.selfClosing missing');
- assert.equal('object', typeof jade.doctypes, 'exports.doctypes missing');
- assert.equal('object', typeof jade.filters, 'exports.filters missing');
- assert.equal('object', typeof jade.utils, 'exports.utils missing');
- assert.equal('function', typeof jade.Compiler, 'exports.Compiler missing');
- },
-
- 'test doctypes': function(assert){
- assert.equal('<?xml version="1.0" encoding="utf-8" ?>', render('!!! xml'));
- assert.equal('<!DOCTYPE html>', render('doctype html'));
- assert.equal('<!DOCTYPE html>', render('doctype HTML'));
- assert.equal('<!DOCTYPE html>', render('!!! 5'));
- assert.equal('<!DOCTYPE html>', render('!!!', { doctype:'html' }));
- assert.equal('<!DOCTYPE html>', render('!!! html', { doctype:'xml' }));
- assert.equal('<html></html>', render('html'));
- assert.equal('<!DOCTYPE html><html></html>', render('html', { doctype:'html' }));
- },
-
- 'test Buffers': function(assert){
- assert.equal('<p>foo</p>', render(new Buffer('p foo')));
- },
-
- 'test line endings': function(assert){
- var str = [
- 'p',
- 'div',
- 'img'
- ].join('\r\n');
-
- var html = [
- '<p></p>',
- '<div></div>',
- '<img/>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- 'p',
- 'div',
- 'img'
- ].join('\r');
-
- var html = [
- '<p></p>',
- '<div></div>',
- '<img/>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- 'p',
- 'div',
- 'img'
- ].join('\r\n');
-
- var html = [
- '<p></p>',
- '<div></div>',
- '<img>'
- ].join('');
-
- assert.equal(html, render(str, { doctype:'html' }));
- },
-
- 'test single quotes': function(assert){
- assert.equal("<p>'foo'</p>", render("p 'foo'"));
- assert.equal("<p>'foo'\n</p>", render("p\n | 'foo'"));
- assert.equal('<a href="/foo"></a>', render("- var path = 'foo';\na(href='/' + path)"));
- },
-
- 'test block-expansion': function(assert){
- assert.equal("<li><a>foo</a></li><li><a>bar</a></li><li><a>baz</a></li>", render("li: a foo\nli: a bar\nli: a baz"));
- assert.equal("<li class=\"first\"><a>foo</a></li><li><a>bar</a></li><li><a>baz</a></li>", render("li.first: a foo\nli: a bar\nli: a baz"));
- },
-
- 'test tags': function(assert){
- var str = [
- 'p',
- 'div',
- 'img'
- ].join('\n');
-
- var html = [
- '<p></p>',
- '<div></div>',
- '<img/>'
- ].join('');
-
- assert.equal(html, render(str), 'Test basic tags');
- assert.equal('<fb:foo-bar></fb:foo-bar>', render('fb:foo-bar'), 'Test hyphens');
- assert.equal('<div class="something"></div>', render('div.something'), 'Test classes');
- assert.equal('<div id="something"></div>', render('div#something'), 'Test ids');
- assert.equal('<div class="something"></div>', render('.something'), 'Test stand-alone classes');
- assert.equal('<div id="something"></div>', render('#something'), 'Test stand-alone ids');
- assert.equal('<div id="foo" class="bar"></div>', render('#foo.bar'));
- assert.equal('<div id="foo" class="bar"></div>', render('.bar#foo'));
- assert.equal('<div id="foo" class="bar"></div>', render('div#foo(class="bar")'));
- assert.equal('<div id="foo" class="bar"></div>', render('div(class="bar")#foo'));
- assert.equal('<div id="bar" class="foo"></div>', render('div(id="bar").foo'));
- assert.equal('<div class="foo bar baz"></div>', render('div.foo.bar.baz'));
- assert.equal('<div class="foo bar baz"></div>', render('div(class="foo").bar.baz'));
- assert.equal('<div class="foo bar baz"></div>', render('div.foo(class="bar").baz'));
- assert.equal('<div class="foo bar baz"></div>', render('div.foo.bar(class="baz")'));
- assert.equal('<div class="a-b2"></div>', render('div.a-b2'));
- assert.equal('<div class="a_b2"></div>', render('div.a_b2'));
- assert.equal('<fb:user></fb:user>', render('fb:user'));
- assert.equal('<fb:user:role></fb:user:role>', render('fb:user:role'));
- assert.equal('<colgroup><col class="test"/></colgroup>', render('colgroup\n col.test'));
- },
-
- 'test nested tags': function(assert){
- var str = [
- 'ul',
- ' li a',
- ' li b',
- ' li',
- ' ul',
- ' li c',
- ' li d',
- ' li e',
- ].join('\n');
-
- var html = [
- '<ul>',
- '<li>a</li>',
- '<li>b</li>',
- '<li><ul><li>c</li><li>d</li></ul></li>',
- '<li>e</li>',
- '</ul>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- 'a(href="#")',
- ' | foo ',
- ' | bar ',
- ' | baz'
- ].join('\n');
-
- assert.equal('<a href="#">foo \nbar \nbaz\n</a>', render(str));
-
- var str = [
- 'ul',
- ' li one',
- ' ul',
- ' | two',
- ' li three'
- ].join('\n');
-
- var html = [
- '<ul>',
- '<li>one</li>',
- '<ul>two\n',
- '<li>three</li>',
- '</ul>',
- '</ul>'
- ].join('');
-
- assert.equal(html, render(str));
- },
-
- 'test variable length newlines': function(assert){
- var str = [
- 'ul',
- ' li a',
- ' ',
- ' li b',
- ' ',
- ' ',
- ' li',
- ' ul',
- ' li c',
- '',
- ' li d',
- ' li e',
- ].join('\n');
-
- var html = [
- '<ul>',
- '<li>a</li>',
- '<li>b</li>',
- '<li><ul><li>c</li><li>d</li></ul></li>',
- '<li>e</li>',
- '</ul>'
- ].join('');
-
- assert.equal(html, render(str));
- },
-
- 'test tab conversion': function(assert){
- var str = [
- 'ul',
- '\tli a',
- '\t',
- '\tli b',
- '\t\t',
- '\t\t\t\t\t\t',
- '\tli',
- '\t\tul',
- '\t\t\tli c',
- '',
- '\t\t\tli d',
- '\tli e',
- ].join('\n');
-
- var html = [
- '<ul>',
- '<li>a</li>',
- '<li>b</li>',
- '<li><ul><li>c</li><li>d</li></ul></li>',
- '<li>e</li>',
- '</ul>'
- ].join('');
-
- assert.equal(html, render(str));
- },
-
- 'test newlines': function(assert){
- var str = [
- 'ul',
- ' li a',
- ' ',
- ' ',
- '',
- ' ',
- ' li b',
- ' li',
- ' ',
- ' ',
- ' ',
- ' ul',
- ' ',
- ' li c',
- ' li d',
- ' li e',
- ].join('\n');
-
- var html = [
- '<ul>',
- '<li>a</li>',
- '<li>b</li>',
- '<li><ul><li>c</li><li>d</li></ul></li>',
- '<li>e</li>',
- '</ul>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- 'html',
- ' ',
- ' head',
- ' != "test"',
- ' ',
- ' ',
- ' ',
- ' body'
- ].join('\n');
-
- var html = [
- '<html>',
- '<head>',
- 'test',
- '</head>',
- '<body></body>',
- '</html>'
- ].join('');
-
- assert.equal(html, render(str));
- assert.equal('<foo></foo>something<bar></bar>', render('foo\n= "something"\nbar'));
- assert.equal('<foo></foo>something<bar></bar>else', render('foo\n= "something"\nbar\n= "else"'));
- },
-
- 'test cache': function(assert){
- var err;
- try {
- render('foo', { cache: true });
- } catch (e) {
- err = e;
- }
- assert.equal('filename is required when using the cache option', err.message);
-
- assert.equal('<p></p>', render('p', { cache: true, filename: 'foo.jade' }));
- assert.equal('<p></p>', render('p', { cache: true, filename: 'foo.jade' }));
- assert.ok(typeof jade.cache['foo.jade'] === 'function', 'Test cache');
- },
-
- 'test text': function(assert){
- assert.equal('foo\nbar\nbaz\n', render('| foo\n| bar\n| baz'));
- assert.equal('foo \nbar \nbaz\n', render('| foo \n| bar \n| baz'));
- assert.equal('(hey)\n', render('| (hey)'));
- assert.equal('some random text\n', render('| some random text'));
- assert.equal(' foo\n', render('| foo'));
- assert.equal(' foo \n', render('| foo '));
- assert.equal(' foo \n bar \n', render('| foo \n| bar '));
- },
-
- 'test pipe-less text': function(assert){
- assert.equal('<pre><code>foo\n\nbar\n</code></pre>', render('pre\n code\n foo\n\n bar'));
- assert.equal('<p>foo\n\nbar\n</p>', render('p.\n foo\n\n bar'));
- assert.equal('<p>foo\n\n\n\nbar\n</p>', render('p.\n foo\n\n\n\n bar'));
- assert.equal('<p>foo\n bar\nfoo\n</p>', render('p.\n foo\n bar\n foo'));
- assert.equal('<script>s.parentNode.insertBefore(g,s)\n</script>', render('script\n s.parentNode.insertBefore(g,s)\n'));
- assert.equal('<script>s.parentNode.insertBefore(g,s)\n</script>', render('script\n s.parentNode.insertBefore(g,s)'));
- },
-
- 'test tag text': function(assert){
- assert.equal('<p>some random text</p>', render('p some random text'));
- assert.equal('<p>click\n<a>Google</a>.\n</p>', render('p\n | click\n a Google\n | .'));
- assert.equal('<p>(parens)</p>', render('p (parens)'));
- assert.equal('<p foo="bar">(parens)</p>', render('p(foo="bar") (parens)'));
- assert.equal('<option value="">-- (optional) foo --</option>', render('option(value="") -- (optional) foo --'));
- },
-
- 'test tag text block': function(assert){
- assert.equal('<p>foo \nbar \nbaz\n</p>', render('p\n | foo \n | bar \n | baz'));
- assert.equal('<label>Password:\n<input/></label>', render('label\n | Password:\n input'));
- assert.equal('<label>Password:<input/></label>', render('label Password:\n input'));
- },
-
- 'test tag text interpolation': function(assert){
- assert.equal('yo, jade is cool\n', render('| yo, #{name} is cool\n', { locals: { name: 'jade' }}));
- assert.equal('<p>yo, jade is cool</p>', render('p yo, #{name} is cool', { locals: { name: 'jade' }}));
- assert.equal('yo, jade is cool\n', render('| yo, #{name || "jade"} is cool', { locals: { name: null }}));
- assert.equal('yo, \'jade\' is cool\n', render('| yo, #{name || "\'jade\'"} is cool', { locals: { name: null }}));
- assert.equal('foo <script> bar\n', render('| foo #{code} bar', { locals: { code: '<script>' }}));
- assert.equal('foo <script> bar\n', render('| foo !{code} bar', { locals: { code: '<script>' }}));
- },
-
- 'test flexible indentation': function(assert){
- assert.equal('<html><body><h1>Wahoo</h1><p>test</p></body></html>', render('html\n body\n h1 Wahoo\n p test'));
- },
-
- 'test interpolation values': function(assert){
- assert.equal('<p>Users: 15</p>', render('p Users: #{15}'));
- assert.equal('<p>Users: </p>', render('p Users: #{null}'));
- assert.equal('<p>Users: </p>', render('p Users: #{undefined}'));
- assert.equal('<p>Users: none</p>', render('p Users: #{undefined || "none"}'));
- assert.equal('<p>Users: 0</p>', render('p Users: #{0}'));
- assert.equal('<p>Users: false</p>', render('p Users: #{false}'));
- },
-
- 'test html 5 mode': function(assert){
- assert.equal('<!DOCTYPE html><input type="checkbox" checked>', render('!!! 5\ninput(type="checkbox", checked)'));
- assert.equal('<!DOCTYPE html><input type="checkbox" checked>', render('!!! 5\ninput(type="checkbox", checked=true)'));
- assert.equal('<!DOCTYPE html><input type="checkbox">', render('!!! 5\ninput(type="checkbox", checked= false)'));
- },
-
- 'test multi-line attrs': function(assert){
- assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', render('a(foo="bar"\n bar="baz"\n checked) foo'));
- assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', render('a(foo="bar"\nbar="baz"\nchecked) foo'));
- assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', render('a(foo="bar"\n,bar="baz"\n,checked) foo'));
- assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', render('a(foo="bar",\nbar="baz",\nchecked) foo'));
- },
-
- 'test attrs': function(assert){
- assert.equal('<img src="<script>"/>', render('img(src="<script>")'), 'Test attr escaping');
-
- assert.equal('<a data-attr="bar"></a>', render('a(data-attr="bar")'));
- assert.equal('<a data-attr="bar" data-attr-2="baz"></a>', render('a(data-attr="bar", data-attr-2="baz")'));
-
- assert.equal('<a title="foo,bar"></a>', render('a(title= "foo,bar")'));
- assert.equal('<a title="foo,bar" href="#"></a>', render('a(title= "foo,bar", href="#")'));
-
- assert.equal('<p class="foo"></p>', render("p(class='foo')"), 'Test single quoted attrs');
- assert.equal('<input type="checkbox" checked="checked"/>', render('input( type="checkbox", checked )'));
- assert.equal('<input type="checkbox" checked="checked"/>', render('input( type="checkbox", checked = true )'));
- assert.equal('<input type="checkbox"/>', render('input(type="checkbox", checked= false)'));
- assert.equal('<input type="checkbox"/>', render('input(type="checkbox", checked= null)'));
- assert.equal('<input type="checkbox"/>', render('input(type="checkbox", checked= undefined)'));
-
- assert.equal('<img src="/foo.png"/>', render('img(src="/foo.png")'), 'Test attr =');
- assert.equal('<img src="/foo.png"/>', render('img(src = "/foo.png")'), 'Test attr = whitespace');
- assert.equal('<img src="/foo.png"/>', render('img(src="/foo.png")'), 'Test attr :');
- assert.equal('<img src="/foo.png"/>', render('img(src = "/foo.png")'), 'Test attr : whitespace');
-
- assert.equal('<img src="/foo.png" alt="just some foo"/>', render('img(src="/foo.png", alt="just some foo")'));
- assert.equal('<img src="/foo.png" alt="just some foo"/>', render('img(src = "/foo.png", alt = "just some foo")'));
-
- assert.equal('<p class="foo,bar,baz"></p>', render('p(class="foo,bar,baz")'));
- assert.equal('<a href="http://google.com" title="Some : weird = title"></a>', render('a(href= "http://google.com", title= "Some : weird = title")'));
- assert.equal('<label for="name"></label>', render('label(for="name")'));
- assert.equal('<meta name="viewport" content="width=device-width"/>', render("meta(name= 'viewport', content='width=device-width')"), 'Test attrs that contain attr separators');
- assert.equal('<div style="color= white"></div>', render("div(style='color= white')"));
- assert.equal('<div style="color: white"></div>', render("div(style='color: white')"));
- assert.equal('<p class="foo"></p>', render("p('class'='foo')"), 'Test keys with single quotes');
- assert.equal('<p class="foo"></p>', render("p(\"class\"= 'foo')"), 'Test keys with double quotes');
-
- assert.equal('<p data-lang="en"></p>', render('p(data-lang = "en")'));
- assert.equal('<p data-dynamic="true"></p>', render('p("data-dynamic"= "true")'));
- assert.equal('<p data-dynamic="true" class="name"></p>', render('p("class"= "name", "data-dynamic"= "true")'));
- assert.equal('<p data-dynamic="true"></p>', render('p(\'data-dynamic\'= "true")'));
- assert.equal('<p data-dynamic="true" class="name"></p>', render('p(\'class\'= "name", \'data-dynamic\'= "true")'));
- assert.equal('<p data-dynamic="true" yay="yay" class="name"></p>', render('p(\'class\'= "name", \'data-dynamic\'= "true", yay)'));
-
- assert.equal('<input checked="checked" type="checkbox"/>', render('input(checked, type="checkbox")'));
-
- assert.equal('<a data-foo="{ foo: \'bar\', bar= \'baz\' }"></a>', render('a(data-foo = "{ foo: \'bar\', bar= \'baz\' }")'));
-
- assert.equal('<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>', render('meta(http-equiv="X-UA-Compatible", content="IE=edge,chrome=1")'));
-
- assert.equal('<div style="background: url(/images/test.png)">Foo</div>', render("div(style= 'background: url(/images/test.png)') Foo"));
- assert.equal('<div style="background = url(/images/test.png)">Foo</div>', render("div(style= 'background = url(/images/test.png)') Foo"));
- assert.equal('<div style="foo">Foo</div>', render("div(style= ['foo', 'bar'][0]) Foo"));
- assert.equal('<div style="bar">Foo</div>', render("div(style= { foo: 'bar', baz: 'raz' }['foo']) Foo"));
- assert.equal('<a href="def">Foo</a>', render("a(href='abcdefg'.substr(3,3)) Foo"));
- assert.equal('<a href="def">Foo</a>', render("a(href={test: 'abcdefg'}.test.substr(3,3)) Foo"));
- assert.equal('<a href="def">Foo</a>', render("a(href={test: 'abcdefg'}.test.substr(3,[0,3][1])) Foo"));
-
- assert.equal('<rss xmlns:atom="atom"></rss>', render("rss(xmlns:atom=\"atom\")"));
- assert.equal('<rss xmlns:atom="atom"></rss>', render("rss('xmlns:atom'=\"atom\")"));
- assert.equal('<rss xmlns:atom="atom"></rss>', render("rss(\"xmlns:atom\"='atom')"));
- assert.equal('<rss xmlns:atom="atom" foo="bar"></rss>', render("rss('xmlns:atom'=\"atom\", 'foo'= 'bar')"));
- assert.equal('<a data-obj="{ foo: \'bar\' }"></a>', render("a(data-obj= \"{ foo: 'bar' }\")"));
-
- assert.equal('<meta content="what\'s up? \'weee\'"/>', render('meta(content="what\'s up? \'weee\'")'));
- },
-
- 'test class attr array': function(assert){
- assert.equal('<body class="foo bar baz"></body>', render('body(class=["foo", "bar", "baz"])'));
- },
-
- 'test attr interpolation': function(assert){
- // Test single quote interpolation
- assert.equal('<a href="/user/12">tj</a>'
- , render("a(href='/user/#{id}') #{name}", { locals: { name: 'tj', id: 12 }}));
-
- assert.equal('<a href="/user/12-tj">tj</a>'
- , render("a(href='/user/#{id}-#{name}') #{name}", { locals: { name: 'tj', id: 12 }}));
-
- assert.equal('<a href="/user/<script>">tj</a>'
- , render("a(href='/user/#{id}') #{name}", { locals: { name: 'tj', id: '<script>' }}));
-
- // Test double quote interpolation
- assert.equal('<a href="/user/13">ds</a>'
- , render('a(href="/user/#{id}") #{name}', { locals: { name: 'ds', id: 13 }}));
-
- assert.equal('<a href="/user/13-ds">ds</a>'
- , render('a(href="/user/#{id}-#{name}") #{name}', { locals: { name: 'ds', id: 13 }}));
-
- assert.equal('<a href="/user/<script>">ds</a>'
- , render('a(href="/user/#{id}") #{name}', { locals: { name: 'ds', id: '<script>' }}));
- },
-
- 'test attr parens': function(assert){
- assert.equal('<p foo="bar">baz</p>', render('p(foo=((("bar"))))= ((("baz")))'));
- },
-
- 'test code attrs': function(assert){
- assert.equal('<p></p>', render('p(id= name)', { locals: { name: undefined }}));
- assert.equal('<p></p>', render('p(id= name)', { locals: { name: null }}));
- assert.equal('<p></p>', render('p(id= name)', { locals: { name: false }}));
- assert.equal('<p id=""></p>', render('p(id= name)', { locals: { name: '' }}));
- assert.equal('<p id="tj"></p>', render('p(id= name)', { locals: { name: 'tj' }}));
- assert.equal('<p id="default"></p>', render('p(id= name || "default")', { locals: { name: null }}));
- assert.equal('<p id="something"></p>', render("p(id= 'something')", { locals: { name: null }}));
- assert.equal('<p id="something"></p>', render("p(id = 'something')", { locals: { name: null }}));
- assert.equal('<p id="foo"></p>', render("p(id= (true ? 'foo' : 'bar'))"));
- assert.equal('<option value="">Foo</option>', render("option(value='') Foo"));
- },
-
- 'test code attrs class': function(assert){
- assert.equal('<p class="tj"></p>', render('p(class= name)', { locals: { name: 'tj' }}));
- assert.equal('<p class="tj"></p>', render('p( class= name )', { locals: { name: 'tj' }}));
- assert.equal('<p class="default"></p>', render('p(class= name || "default")', { locals: { name: null }}));
- assert.equal('<p class="foo default"></p>', render('p.foo(class= name || "default")', { locals: { name: null }}));
- assert.equal('<p class="default foo"></p>', render('p(class= name || "default").foo', { locals: { name: null }}));
- assert.equal('<p id="default"></p>', render('p(id = name || "default")', { locals: { name: null }}));
- assert.equal('<p id="user-1"></p>', render('p(id = "user-" + 1)'));
- assert.equal('<p class="user-1"></p>', render('p(class = "user-" + 1)'));
- },
-
- 'test code buffering': function(assert){
- assert.equal('<p></p>', render('p= null'));
- assert.equal('<p></p>', render('p= undefined'));
- assert.equal('<p>0</p>', render('p= 0'));
- assert.equal('<p>false</p>', render('p= false'));
- },
-
- 'test comments': function(assert){
- // Regular
- var str = [
- '//foo',
- 'p bar'
- ].join('\n');
-
- var html = [
- '<!--foo-->',
- '<p>bar</p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- // Arbitrary indentation
-
- var str = [
- ' //foo',
- 'p bar'
- ].join('\n');
-
- var html = [
- '<!--foo-->',
- '<p>bar</p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- // Between tags
-
- var str = [
- 'p foo',
- '// bar ',
- 'p baz'
- ].join('\n');
-
- var html = [
- '<p>foo</p>',
- '<!-- bar -->',
- '<p>baz</p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- // Quotes
-
- var str = "<!-- script(src: '/js/validate.js') -->",
- js = "// script(src: '/js/validate.js') ";
- assert.equal(str, render(js));
- },
-
- 'test unbuffered comments': function(assert){
- var str = [
- '//- foo',
- 'p bar'
- ].join('\n');
-
- var html = [
- '<p>bar</p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- 'p foo',
- '//- bar ',
- 'p baz'
- ].join('\n');
-
- var html = [
- '<p>foo</p>',
- '<p>baz</p>'
- ].join('');
-
- assert.equal(html, render(str));
- },
-
- 'test literal html': function(assert){
- assert.equal('<!--[if IE lt 9]>weeee<![endif]-->\n', render('<!--[if IE lt 9]>weeee<![endif]-->'));
- },
-
- 'test code': function(assert){
- assert.equal('test', render('!= "test"'));
- assert.equal('test', render('= "test"'));
- assert.equal('test', render('- var foo = "test"\n=foo'));
- assert.equal('foo\n<em>test</em>bar\n', render('- var foo = "test"\n| foo\nem= foo\n| bar'));
- assert.equal('test<h2>something</h2>', render('!= "test"\nh2 something'));
-
- var str = [
- '- var foo = "<script>";',
- '= foo',
- '!= foo'
- ].join('\n');
-
- var html = [
- '<script>',
- '<script>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- '- var foo = "<script>";',
- '- if (foo)',
- ' p= foo'
- ].join('\n');
-
- var html = [
- '<p><script></p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- '- var foo = "<script>";',
- '- if (foo)',
- ' p!= foo'
- ].join('\n');
-
- var html = [
- '<p><script></p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- '- var foo;',
- '- if (foo)',
- ' p.hasFoo= foo',
- '- else',
- ' p.noFoo no foo'
- ].join('\n');
-
- var html = [
- '<p class="noFoo">no foo</p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- '- var foo;',
- '- if (foo)',
- ' p.hasFoo= foo',
- '- else if (true)',
- ' p kinda foo',
- '- else',
- ' p.noFoo no foo'
- ].join('\n');
-
- var html = [
- '<p>kinda foo</p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- 'p foo',
- '= "bar"',
- ].join('\n');
-
- var html = [
- '<p>foo</p>bar'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- 'title foo',
- '- if (true)',
- ' p something',
- ].join('\n');
-
- var html = [
- '<title>foo</title><p>something</p>'
- ].join('');
-
- assert.equal(html, render(str));
-
- var str = [
- 'foo',
- ' bar= "bar"',
- ' baz= "baz"',
- ].join('\n');
-
- var html = [
- '<foo>',
- '<bar>bar',
- '<baz>baz</baz>',
- '</bar>',
- '</foo>'
- ].join('');
-
- assert.equal(html, render(str));
- },
-
- 'test - each': function(assert){
- // Array
- var str = [
- '- var items = ["one", "two", "three"];',
- '- each item in items',
- ' li= item'
- ].join('\n');
-
- var html = [
- '<li>one</li>',
- '<li>two</li>',
- '<li>three</li>'
- ].join('');
-
- assert.equal(html, render(str));
-
- // Any enumerable (length property)
- var str = [
- '- var jQuery = { length: 3, 0: 1, 1: 2, 2: 3 };',
- '- each item in jQuery',
- ' li= item'
- ].join('\n');
-
- var html = [
- '<li>1</li>',
- '<li>2</li>',
- '<li>3</li>'
- ].join('');
-
- assert.equal(html, render(str));
-
- // Empty array
- var str = [
- '- var items = [];',
- '- each item in items',
- ' li= item'
- ].join('\n');
-
- assert.equal('', render(str));
-
- // Object
- var str = [
- '- var obj = { foo: "bar", baz: "raz" };',
- '- each val in obj',
- ' li= val'
- ].join('\n');
-
- var html = [
- '<li>bar</li>',
- '<li>raz</li>'
- ].join('');
-
- assert.equal(html, render(str));
-
- // Complex
- var str = [
- '- var obj = { foo: "bar", baz: "raz" };',
- '- each key in Object.keys(obj)',
- ' li= key'
- ].join('\n');
-
- var html = [
- '<li>foo</li>',
- '<li>baz</li>'
- ].join('');
-
- assert.equal(html, render(str));
-
- // Keys
- var str = [
- '- var obj = { foo: "bar", baz: "raz" };',
- '- each val, key in obj',
- ' li #{key}: #{val}'
- ].join('\n');
-
- var html = [
- '<li>foo: bar</li>',
- '<li>baz: raz</li>'
- ].join('');
-
- assert.equal(html, render(str));
-
- // Nested
- var str = [
- '- var users = [{ name: "tj" }]',
- '- each user in users',
- ' - each val, key in user',
- ' li #{key} #{val}',
- ].join('\n');
-
- var html = [
- '<li>name tj</li>'
- ].join('');
-
- assert.equal(html, render(str));
- },
-
- 'test renderFile() fs exception': function(assert, beforeExit){
- var called;
- jade.renderFile('foo', function(err, str){
- called = true;
- assert.equal(ENOENT, err.errno);
- assert.equal(undefined, str);
- });
- beforeExit(function(){
- assert.ok(called);
- });
- },
-
- 'test renderFile() with valid path': function(assert, beforeExit){
- var called;
- jade.renderFile(__dirname + '/fixtures/layout.jade', function(err, str){
- called = true;
- assert.equal(null, err);
- assert.equal('<html><body><h1>Jade</h1></body></html>', str);
- });
- beforeExit(function(){
- assert.ok(called);
- });
- },
-
- 'test renderFile() with options': function(assert, beforeExit){
- var called = 0;
- jade.renderFile(__dirname + '/fixtures/layout.jade', { cache: true }, function(err, str){
- ++called;
- assert.equal(null, err);
- assert.equal('<html><body><h1>Jade</h1></body></html>', str);
-
- jade.renderFile(__dirname + '/fixtures/layout.jade', { cache: true }, function(err, str){
- ++called;
- assert.equal(null, err);
- assert.equal('<html><body><h1>Jade</h1></body></html>', str);
- });
- });
- beforeExit(function(){
- assert.equal(2, called);
- });
- },
-
- 'test renderFile() passing of exceptions': function(assert, beforeExit){
- var called = 0;
- jade.renderFile(__dirname + '/fixtures/invalid.jade', { cache: true }, function(err, str){
- ++called;
- assert.ok(typeof err.message === 'string', 'Test passing of exceptions to renderFile() callback');
- assert.equal(undefined, str);
- });
- beforeExit(function(){
- assert.equal(1, called);
- });
- },
-
- 'test .compile()': function(assert){
- var fn = jade.compile('p foo');
- assert.equal('<p>foo</p>', fn());
- },
-
- 'test .compile() locals': function(assert){
- var fn = jade.compile('p= foo');
- assert.equal('<p>bar</p>', fn({ foo: 'bar' }));
- },
-
- 'test .compile() scope': function(assert){
- var fn = jade.compile('p= this.foo');
- assert.equal('<p>bar</p>', fn.call({ foo: 'bar' }));
- },
-
- 'test null attrs on tag': function(assert){
- var tag = new jade.nodes.Tag('a'),
- name = 'href',
- val = '"/"';
- tag.setAttribute(name, val)
- assert.equal(tag.getAttribute(name), val)
- tag.removeAttribute(name)
- assert.isUndefined(tag.getAttribute(name))
- }
+ 'test .version': function(assert){
+ assert.ok(/^\d+\.\d+\.\d+$/.test(jade.version), "Invalid version format");
+ },
+
+ 'test exports': function(assert){
+ assert.equal('object', typeof jade.selfClosing, 'exports.selfClosing missing');
+ assert.equal('object', typeof jade.doctypes, 'exports.doctypes missing');
+ assert.equal('object', typeof jade.filters, 'exports.filters missing');
+ assert.equal('object', typeof jade.utils, 'exports.utils missing');
+ assert.equal('function', typeof jade.Compiler, 'exports.Compiler missing');
+ },
+
+ 'test doctypes': function(assert){
+ assert.equal('<?xml version="1.0" encoding="utf-8" ?>', render('!!! xml'));
+ assert.equal('<!DOCTYPE html>', render('doctype html'));
+ assert.equal('<!DOCTYPE html>', render('doctype HTML'));
+ assert.equal('<!DOCTYPE html>', render('!!! 5'));
+ assert.equal('<!DOCTYPE html>', render('!!!', { doctype:'html' }));
+ assert.equal('<!DOCTYPE html>', render('!!! html', { doctype:'xml' }));
+ assert.equal('<html></html>', render('html'));
+ assert.equal('<!DOCTYPE html><html></html>', render('html', { doctype:'html' }));
+ },
+
+ 'test Buffers': function(assert){
+ assert.equal('<p>foo</p>', render(new Buffer('p foo')));
+ },
+
+ 'test line endings': function(assert){
+ var str = [
+ 'p',
+ 'div',
+ 'img'
+ ].join('\r\n');
+
+ var html = [
+ '<p></p>',
+ '<div></div>',
+ '<img/>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ 'p',
+ 'div',
+ 'img'
+ ].join('\r');
+
+ var html = [
+ '<p></p>',
+ '<div></div>',
+ '<img/>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ 'p',
+ 'div',
+ 'img'
+ ].join('\r\n');
+
+ var html = [
+ '<p></p>',
+ '<div></div>',
+ '<img>'
+ ].join('');
+
+ assert.equal(html, render(str, { doctype:'html' }));
+ },
+
+ 'test single quotes': function(assert){
+ assert.equal("<p>'foo'</p>", render("p 'foo'"));
+ assert.equal("<p>'foo'\n</p>", render("p\n | 'foo'"));
+ assert.equal('<a href="/foo"></a>', render("- var path = 'foo';\na(href='/' + path)"));
+ },
+
+ 'test block-expansion': function(assert){
+ assert.equal("<li><a>foo</a></li><li><a>bar</a></li><li><a>baz</a></li>", render("li: a foo\nli: a bar\nli: a baz"));
+ assert.equal("<li class=\"first\"><a>foo</a></li><li><a>bar</a></li><li><a>baz</a></li>", render("li.first: a foo\nli: a bar\nli: a baz"));
+ },
+
+ 'test tags': function(assert){
+ var str = [
+ 'p',
+ 'div',
+ 'img'
+ ].join('\n');
+
+ var html = [
+ '<p></p>',
+ '<div></div>',
+ '<img/>'
+ ].join('');
+
+ assert.equal(html, render(str), 'Test basic tags');
+ assert.equal('<fb:foo-bar></fb:foo-bar>', render('fb:foo-bar'), 'Test hyphens');
+ assert.equal('<div class="something"></div>', render('div.something'), 'Test classes');
+ assert.equal('<div id="something"></div>', render('div#something'), 'Test ids');
+ assert.equal('<div class="something"></div>', render('.something'), 'Test stand-alone classes');
+ assert.equal('<div id="something"></div>', render('#something'), 'Test stand-alone ids');
+ assert.equal('<div id="foo" class="bar"></div>', render('#foo.bar'));
+ assert.equal('<div id="foo" class="bar"></div>', render('.bar#foo'));
+ assert.equal('<div id="foo" class="bar"></div>', render('div#foo(class="bar")'));
+ assert.equal('<div id="foo" class="bar"></div>', render('div(class="bar")#foo'));
+ assert.equal('<div id="bar" class="foo"></div>', render('div(id="bar").foo'));
+ assert.equal('<div class="foo bar baz"></div>', render('div.foo.bar.baz'));
+ assert.equal('<div class="foo bar baz"></div>', render('div(class="foo").bar.baz'));
+ assert.equal('<div class="foo bar baz"></div>', render('div.foo(class="bar").baz'));
+ assert.equal('<div class="foo bar baz"></div>', render('div.foo.bar(class="baz")'));
+ assert.equal('<div class="a-b2"></div>', render('div.a-b2'));
+ assert.equal('<div class="a_b2"></div>', render('div.a_b2'));
+ assert.equal('<fb:user></fb:user>', render('fb:user'));
+ assert.equal('<fb:user:role></fb:user:role>', render('fb:user:role'));
+ assert.equal('<colgroup><col class="test"/></colgroup>', render('colgroup\n col.test'));
+ },
+
+ 'test nested tags': function(assert){
+ var str = [
+ 'ul',
+ ' li a',
+ ' li b',
+ ' li',
+ ' ul',
+ ' li c',
+ ' li d',
+ ' li e',
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>a</li>',
+ '<li>b</li>',
+ '<li><ul><li>c</li><li>d</li></ul></li>',
+ '<li>e</li>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ 'a(href="#")',
+ ' | foo ',
+ ' | bar ',
+ ' | baz'
+ ].join('\n');
+
+ assert.equal('<a href="#">foo \nbar \nbaz\n</a>', render(str));
+
+ var str = [
+ 'ul',
+ ' li one',
+ ' ul',
+ ' | two',
+ ' li three'
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>one</li>',
+ '<ul>two\n',
+ '<li>three</li>',
+ '</ul>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, render(str));
+ },
+
+ 'test variable length newlines': function(assert){
+ var str = [
+ 'ul',
+ ' li a',
+ ' ',
+ ' li b',
+ ' ',
+ ' ',
+ ' li',
+ ' ul',
+ ' li c',
+ '',
+ ' li d',
+ ' li e',
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>a</li>',
+ '<li>b</li>',
+ '<li><ul><li>c</li><li>d</li></ul></li>',
+ '<li>e</li>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, render(str));
+ },
+
+ 'test tab conversion': function(assert){
+ var str = [
+ 'ul',
+ '\tli a',
+ '\t',
+ '\tli b',
+ '\t\t',
+ '\t\t\t\t\t\t',
+ '\tli',
+ '\t\tul',
+ '\t\t\tli c',
+ '',
+ '\t\t\tli d',
+ '\tli e',
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>a</li>',
+ '<li>b</li>',
+ '<li><ul><li>c</li><li>d</li></ul></li>',
+ '<li>e</li>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, render(str));
+ },
+
+ 'test newlines': function(assert){
+ var str = [
+ 'ul',
+ ' li a',
+ ' ',
+ ' ',
+ '',
+ ' ',
+ ' li b',
+ ' li',
+ ' ',
+ ' ',
+ ' ',
+ ' ul',
+ ' ',
+ ' li c',
+ ' li d',
+ ' li e',
+ ].join('\n');
+
+ var html = [
+ '<ul>',
+ '<li>a</li>',
+ '<li>b</li>',
+ '<li><ul><li>c</li><li>d</li></ul></li>',
+ '<li>e</li>',
+ '</ul>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ 'html',
+ ' ',
+ ' head',
+ ' != "test"',
+ ' ',
+ ' ',
+ ' ',
+ ' body'
+ ].join('\n');
+
+ var html = [
+ '<html>',
+ '<head>',
+ 'test',
+ '</head>',
+ '<body></body>',
+ '</html>'
+ ].join('');
+
+ assert.equal(html, render(str));
+ assert.equal('<foo></foo>something<bar></bar>', render('foo\n= "something"\nbar'));
+ assert.equal('<foo></foo>something<bar></bar>else', render('foo\n= "something"\nbar\n= "else"'));
+ },
+
+ 'test text': function(assert){
+ assert.equal('foo\nbar\nbaz\n', render('| foo\n| bar\n| baz'));
+ assert.equal('foo \nbar \nbaz\n', render('| foo \n| bar \n| baz'));
+ assert.equal('(hey)\n', render('| (hey)'));
+ assert.equal('some random text\n', render('| some random text'));
+ assert.equal(' foo\n', render('| foo'));
+ assert.equal(' foo \n', render('| foo '));
+ assert.equal(' foo \n bar \n', render('| foo \n| bar '));
+ },
+
+ 'test pipe-less text': function(assert){
+ assert.equal('<pre><code>foo\n\nbar\n</code></pre>', render('pre\n code\n foo\n\n bar'));
+ assert.equal('<p>foo\n\nbar\n</p>', render('p.\n foo\n\n bar'));
+ assert.equal('<p>foo\n\n\n\nbar\n</p>', render('p.\n foo\n\n\n\n bar'));
+ assert.equal('<p>foo\n bar\nfoo\n</p>', render('p.\n foo\n bar\n foo'));
+ assert.equal('<script>s.parentNode.insertBefore(g,s)\n</script>', render('script\n s.parentNode.insertBefore(g,s)\n'));
+ assert.equal('<script>s.parentNode.insertBefore(g,s)\n</script>', render('script\n s.parentNode.insertBefore(g,s)'));
+ },
+
+ 'test tag text': function(assert){
+ assert.equal('<p>some random text</p>', render('p some random text'));
+ assert.equal('<p>click\n<a>Google</a>.\n</p>', render('p\n | click\n a Google\n | .'));
+ assert.equal('<p>(parens)</p>', render('p (parens)'));
+ assert.equal('<p foo="bar">(parens)</p>', render('p(foo="bar") (parens)'));
+ assert.equal('<option value="">-- (optional) foo --</option>', render('option(value="") -- (optional) foo --'));
+ },
+
+ 'test tag text block': function(assert){
+ assert.equal('<p>foo \nbar \nbaz\n</p>', render('p\n | foo \n | bar \n | baz'));
+ assert.equal('<label>Password:\n<input/></label>', render('label\n | Password:\n input'));
+ assert.equal('<label>Password:<input/></label>', render('label Password:\n input'));
+ },
+
+ 'test tag text interpolation': function(assert){
+ assert.equal('yo, jade is cool\n', render('| yo, #{name} is cool\n', { name: 'jade' }));
+ assert.equal('<p>yo, jade is cool</p>', render('p yo, #{name} is cool', { name: 'jade' }));
+ assert.equal('yo, jade is cool\n', render('| yo, #{name || "jade"} is cool', { name: null }));
+ assert.equal('yo, \'jade\' is cool\n', render('| yo, #{name || "\'jade\'"} is cool', { name: null }));
+ assert.equal('foo <script> bar\n', render('| foo #{code} bar', { code: '<script>' }));
+ assert.equal('foo <script> bar\n', render('| foo !{code} bar', { code: '<script>' }));
+ },
+
+ 'test flexible indentation': function(assert){
+ assert.equal('<html><body><h1>Wahoo</h1><p>test</p></body></html>', render('html\n body\n h1 Wahoo\n p test'));
+ },
+
+ 'test interpolation values': function(assert){
+ assert.equal('<p>Users: 15</p>', render('p Users: #{15}'));
+ assert.equal('<p>Users: </p>', render('p Users: #{null}'));
+ assert.equal('<p>Users: </p>', render('p Users: #{undefined}'));
+ assert.equal('<p>Users: none</p>', render('p Users: #{undefined || "none"}'));
+ assert.equal('<p>Users: 0</p>', render('p Users: #{0}'));
+ assert.equal('<p>Users: false</p>', render('p Users: #{false}'));
+ },
+
+ 'test html 5 mode': function(assert){
+ assert.equal('<!DOCTYPE html><input type="checkbox" checked>', render('!!! 5\ninput(type="checkbox", checked)'));
+ assert.equal('<!DOCTYPE html><input type="checkbox" checked>', render('!!! 5\ninput(type="checkbox", checked=true)'));
+ assert.equal('<!DOCTYPE html><input type="checkbox">', render('!!! 5\ninput(type="checkbox", checked= false)'));
+ },
+
+ 'test multi-line attrs': function(assert){
+ assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', render('a(foo="bar"\n bar="baz"\n checked) foo'));
+ assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', render('a(foo="bar"\nbar="baz"\nchecked) foo'));
+ assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', render('a(foo="bar"\n,bar="baz"\n,checked) foo'));
+ assert.equal('<a foo="bar" bar="baz" checked="checked">foo</a>', render('a(foo="bar",\nbar="baz",\nchecked) foo'));
+ },
+
+ 'test attrs': function(assert){
+ assert.equal('<img src="<script>"/>', render('img(src="<script>")'), 'Test attr escaping');
+
+ assert.equal('<a data-attr="bar"></a>', render('a(data-attr="bar")'));
+ assert.equal('<a data-attr="bar" data-attr-2="baz"></a>', render('a(data-attr="bar", data-attr-2="baz")'));
+
+ assert.equal('<a title="foo,bar"></a>', render('a(title= "foo,bar")'));
+ assert.equal('<a title="foo,bar" href="#"></a>', render('a(title= "foo,bar", href="#")'));
+
+ assert.equal('<p class="foo"></p>', render("p(class='foo')"), 'Test single quoted attrs');
+ assert.equal('<input type="checkbox" checked="checked"/>', render('input( type="checkbox", checked )'));
+ assert.equal('<input type="checkbox" checked="checked"/>', render('input( type="checkbox", checked = true )'));
+ assert.equal('<input type="checkbox"/>', render('input(type="checkbox", checked= false)'));
+ assert.equal('<input type="checkbox"/>', render('input(type="checkbox", checked= null)'));
+ assert.equal('<input type="checkbox"/>', render('input(type="checkbox", checked= undefined)'));
+
+ assert.equal('<img src="/foo.png"/>', render('img(src="/foo.png")'), 'Test attr =');
+ assert.equal('<img src="/foo.png"/>', render('img(src = "/foo.png")'), 'Test attr = whitespace');
+ assert.equal('<img src="/foo.png"/>', render('img(src="/foo.png")'), 'Test attr :');
+ assert.equal('<img src="/foo.png"/>', render('img(src = "/foo.png")'), 'Test attr : whitespace');
+
+ assert.equal('<img src="/foo.png" alt="just some foo"/>', render('img(src="/foo.png", alt="just some foo")'));
+ assert.equal('<img src="/foo.png" alt="just some foo"/>', render('img(src = "/foo.png", alt = "just some foo")'));
+
+ assert.equal('<p class="foo,bar,baz"></p>', render('p(class="foo,bar,baz")'));
+ assert.equal('<a href="http://google.com" title="Some : weird = title"></a>', render('a(href= "http://google.com", title= "Some : weird = title")'));
+ assert.equal('<label for="name"></label>', render('label(for="name")'));
+ assert.equal('<meta name="viewport" content="width=device-width"/>', render("meta(name= 'viewport', content='width=device-width')"), 'Test attrs that contain attr separators');
+ assert.equal('<div style="color= white"></div>', render("div(style='color= white')"));
+ assert.equal('<div style="color: white"></div>', render("div(style='color: white')"));
+ assert.equal('<p class="foo"></p>', render("p('class'='foo')"), 'Test keys with single quotes');
+ assert.equal('<p class="foo"></p>', render("p(\"class\"= 'foo')"), 'Test keys with double quotes');
+
+ assert.equal('<p data-lang="en"></p>', render('p(data-lang = "en")'));
+ assert.equal('<p data-dynamic="true"></p>', render('p("data-dynamic"= "true")'));
+ assert.equal('<p data-dynamic="true" class="name"></p>', render('p("class"= "name", "data-dynamic"= "true")'));
+ assert.equal('<p data-dynamic="true"></p>', render('p(\'data-dynamic\'= "true")'));
+ assert.equal('<p data-dynamic="true" class="name"></p>', render('p(\'class\'= "name", \'data-dynamic\'= "true")'));
+ assert.equal('<p data-dynamic="true" yay="yay" class="name"></p>', render('p(\'class\'= "name", \'data-dynamic\'= "true", yay)'));
+
+ assert.equal('<input checked="checked" type="checkbox"/>', render('input(checked, type="checkbox")'));
+
+ assert.equal('<a data-foo="{ foo: \'bar\', bar= \'baz\' }"></a>', render('a(data-foo = "{ foo: \'bar\', bar= \'baz\' }")'));
+
+ assert.equal('<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>', render('meta(http-equiv="X-UA-Compatible", content="IE=edge,chrome=1")'));
+
+ assert.equal('<div style="background: url(/images/test.png)">Foo</div>', render("div(style= 'background: url(/images/test.png)') Foo"));
+ assert.equal('<div style="background = url(/images/test.png)">Foo</div>', render("div(style= 'background = url(/images/test.png)') Foo"));
+ assert.equal('<div style="foo">Foo</div>', render("div(style= ['foo', 'bar'][0]) Foo"));
+ assert.equal('<div style="bar">Foo</div>', render("div(style= { foo: 'bar', baz: 'raz' }['foo']) Foo"));
+ assert.equal('<a href="def">Foo</a>', render("a(href='abcdefg'.substr(3,3)) Foo"));
+ assert.equal('<a href="def">Foo</a>', render("a(href={test: 'abcdefg'}.test.substr(3,3)) Foo"));
+ assert.equal('<a href="def">Foo</a>', render("a(href={test: 'abcdefg'}.test.substr(3,[0,3][1])) Foo"));
+
+ assert.equal('<rss xmlns:atom="atom"></rss>', render("rss(xmlns:atom=\"atom\")"));
+ assert.equal('<rss xmlns:atom="atom"></rss>', render("rss('xmlns:atom'=\"atom\")"));
+ assert.equal('<rss xmlns:atom="atom"></rss>', render("rss(\"xmlns:atom\"='atom')"));
+ assert.equal('<rss xmlns:atom="atom" foo="bar"></rss>', render("rss('xmlns:atom'=\"atom\", 'foo'= 'bar')"));
+ assert.equal('<a data-obj="{ foo: \'bar\' }"></a>', render("a(data-obj= \"{ foo: 'bar' }\")"));
+
+ assert.equal('<meta content="what\'s up? \'weee\'"/>', render('meta(content="what\'s up? \'weee\'")'));
+ },
+
+ 'test colons option': function(assert){
+ assert.equal('<a href="/bar"></a>', render('a(href:"/bar")', { colons: true }));
+ },
+
+ 'test class attr array': function(assert){
+ assert.equal('<body class="foo bar baz"></body>', render('body(class=["foo", "bar", "baz"])'));
+ },
+
+ 'test attr interpolation': function(assert){
+ // Test single quote interpolation
+ assert.equal('<a href="/user/12">tj</a>'
+ , render("a(href='/user/#{id}') #{name}", { name: 'tj', id: 12 }));
+
+ assert.equal('<a href="/user/12-tj">tj</a>'
+ , render("a(href='/user/#{id}-#{name}') #{name}", { name: 'tj', id: 12 }));
+
+ assert.equal('<a href="/user/<script>">tj</a>'
+ , render("a(href='/user/#{id}') #{name}", { name: 'tj', id: '<script>' }));
+
+ // Test double quote interpolation
+ assert.equal('<a href="/user/13">ds</a>'
+ , render('a(href="/user/#{id}") #{name}', { name: 'ds', id: 13 }));
+
+ assert.equal('<a href="/user/13-ds">ds</a>'
+ , render('a(href="/user/#{id}-#{name}") #{name}', { name: 'ds', id: 13 }));
+
+ assert.equal('<a href="/user/<script>">ds</a>'
+ , render('a(href="/user/#{id}") #{name}', { name: 'ds', id: '<script>' }));
+ },
+
+ 'test attr parens': function(assert){
+ assert.equal('<p foo="bar">baz</p>', render('p(foo=((("bar"))))= ((("baz")))'));
+ },
+
+ 'test code attrs': function(assert){
+ assert.equal('<p></p>', render('p(id= name)', { name: undefined }));
+ assert.equal('<p></p>', render('p(id= name)', { name: null }));
+ assert.equal('<p></p>', render('p(id= name)', { name: false }));
+ assert.equal('<p id=""></p>', render('p(id= name)', { name: '' }));
+ assert.equal('<p id="tj"></p>', render('p(id= name)', { name: 'tj' }));
+ assert.equal('<p id="default"></p>', render('p(id= name || "default")', { name: null }));
+ assert.equal('<p id="something"></p>', render("p(id= 'something')", { name: null }));
+ assert.equal('<p id="something"></p>', render("p(id = 'something')", { name: null }));
+ assert.equal('<p id="foo"></p>', render("p(id= (true ? 'foo' : 'bar'))"));
+ assert.equal('<option value="">Foo</option>', render("option(value='') Foo"));
+ },
+
+ 'test code attrs class': function(assert){
+ assert.equal('<p class="tj"></p>', render('p(class= name)', { name: 'tj' }));
+ assert.equal('<p class="tj"></p>', render('p( class= name )', { name: 'tj' }));
+ assert.equal('<p class="default"></p>', render('p(class= name || "default")', { name: null }));
+ assert.equal('<p class="foo default"></p>', render('p.foo(class= name || "default")', { name: null }));
+ assert.equal('<p class="default foo"></p>', render('p(class= name || "default").foo', { name: null }));
+ assert.equal('<p id="default"></p>', render('p(id = name || "default")', { name: null }));
+ assert.equal('<p id="user-1"></p>', render('p(id = "user-" + 1)'));
+ assert.equal('<p class="user-1"></p>', render('p(class = "user-" + 1)'));
+ },
+
+ 'test code buffering': function(assert){
+ assert.equal('<p></p>', render('p= null'));
+ assert.equal('<p></p>', render('p= undefined'));
+ assert.equal('<p>0</p>', render('p= 0'));
+ assert.equal('<p>false</p>', render('p= false'));
+ },
+
+ 'test script text': function(assert){
+ var str = [
+ 'script',
+ ' p foo',
+ '',
+ 'script(type="text/template")',
+ ' p foo',
+ '',
+ 'script(type="text/template").',
+ ' p foo'
+ ].join('\n');
+
+ var html = [
+ '<script>p foo\n\n</script>',
+ '<script type="text/template"><p>foo</p></script>',
+ '<script type="text/template">p foo\n</script>'
+ ].join('');
+
+ assert.equal(html, render(str));
+ },
+
+ 'test comments': function(assert){
+ // Regular
+ var str = [
+ '//foo',
+ 'p bar'
+ ].join('\n');
+
+ var html = [
+ '<!--foo-->',
+ '<p>bar</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ // Arbitrary indentation
+
+ var str = [
+ ' //foo',
+ 'p bar'
+ ].join('\n');
+
+ var html = [
+ '<!--foo-->',
+ '<p>bar</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ // Between tags
+
+ var str = [
+ 'p foo',
+ '// bar ',
+ 'p baz'
+ ].join('\n');
+
+ var html = [
+ '<p>foo</p>',
+ '<!-- bar -->',
+ '<p>baz</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ // Quotes
+
+ var str = "<!-- script(src: '/js/validate.js') -->",
+ js = "// script(src: '/js/validate.js') ";
+ assert.equal(str, render(js));
+ },
+
+ 'test unbuffered comments': function(assert){
+ var str = [
+ '//- foo',
+ 'p bar'
+ ].join('\n');
+
+ var html = [
+ '<p>bar</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ 'p foo',
+ '//- bar ',
+ 'p baz'
+ ].join('\n');
+
+ var html = [
+ '<p>foo</p>',
+ '<p>baz</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+ },
+
+ 'test literal html': function(assert){
+ assert.equal('<!--[if IE lt 9]>weeee<![endif]-->\n', render('<!--[if IE lt 9]>weeee<![endif]-->'));
+ },
+
+ 'test code': function(assert){
+ assert.equal('test', render('!= "test"'));
+ assert.equal('test', render('= "test"'));
+ assert.equal('test', render('- var foo = "test"\n=foo'));
+ assert.equal('foo\n<em>test</em>bar\n', render('- var foo = "test"\n| foo\nem= foo\n| bar'));
+ assert.equal('test<h2>something</h2>', render('!= "test"\nh2 something'));
+
+ var str = [
+ '- var foo = "<script>";',
+ '= foo',
+ '!= foo'
+ ].join('\n');
+
+ var html = [
+ '<script>',
+ '<script>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ '- var foo = "<script>";',
+ '- if (foo)',
+ ' p= foo'
+ ].join('\n');
+
+ var html = [
+ '<p><script></p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ '- var foo = "<script>";',
+ '- if (foo)',
+ ' p!= foo'
+ ].join('\n');
+
+ var html = [
+ '<p><script></p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ '- var foo;',
+ '- if (foo)',
+ ' p.hasFoo= foo',
+ '- else',
+ ' p.noFoo no foo'
+ ].join('\n');
+
+ var html = [
+ '<p class="noFoo">no foo</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ '- var foo;',
+ '- if (foo)',
+ ' p.hasFoo= foo',
+ '- else if (true)',
+ ' p kinda foo',
+ '- else',
+ ' p.noFoo no foo'
+ ].join('\n');
+
+ var html = [
+ '<p>kinda foo</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ 'p foo',
+ '= "bar"',
+ ].join('\n');
+
+ var html = [
+ '<p>foo</p>bar'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ 'title foo',
+ '- if (true)',
+ ' p something',
+ ].join('\n');
+
+ var html = [
+ '<title>foo</title><p>something</p>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ 'foo',
+ ' bar= "bar"',
+ ' baz= "baz"',
+ ].join('\n');
+
+ var html = [
+ '<foo>',
+ '<bar>bar',
+ '<baz>baz</baz>',
+ '</bar>',
+ '</foo>'
+ ].join('');
+
+ assert.equal(html, render(str));
+ },
+
+ 'test - each': function(assert){
+ // Array
+ var str = [
+ '- var items = ["one", "two", "three"];',
+ '- each item in items',
+ ' li= item'
+ ].join('\n');
+
+ var html = [
+ '<li>one</li>',
+ '<li>two</li>',
+ '<li>three</li>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ // Any enumerable (length property)
+ var str = [
+ '- var jQuery = { length: 3, 0: 1, 1: 2, 2: 3 };',
+ '- each item in jQuery',
+ ' li= item'
+ ].join('\n');
+
+ var html = [
+ '<li>1</li>',
+ '<li>2</li>',
+ '<li>3</li>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ // Empty array
+ var str = [
+ '- var items = [];',
+ '- each item in items',
+ ' li= item'
+ ].join('\n');
+
+ assert.equal('', render(str));
+
+ // Object
+ var str = [
+ '- var obj = { foo: "bar", baz: "raz" };',
+ '- each val in obj',
+ ' li= val'
+ ].join('\n');
+
+ var html = [
+ '<li>bar</li>',
+ '<li>raz</li>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ // Complex
+ var str = [
+ '- var obj = { foo: "bar", baz: "raz" };',
+ '- each key in Object.keys(obj)',
+ ' li= key'
+ ].join('\n');
+
+ var html = [
+ '<li>foo</li>',
+ '<li>baz</li>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ // Keys
+ var str = [
+ '- var obj = { foo: "bar", baz: "raz" };',
+ '- each val, key in obj',
+ ' li #{key}: #{val}'
+ ].join('\n');
+
+ var html = [
+ '<li>foo: bar</li>',
+ '<li>baz: raz</li>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ // Nested
+ var str = [
+ '- var users = [{ name: "tj" }]',
+ '- each user in users',
+ ' - each val, key in user',
+ ' li #{key} #{val}',
+ ].join('\n');
+
+ var html = [
+ '<li>name tj</li>'
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ '- var users = ["tobi", "loki", "jane"]',
+ 'each user in users',
+ ' li= user',
+ ].join('\n');
+
+ var html = [
+ '<li>tobi</li>',
+ '<li>loki</li>',
+ '<li>jane</li>',
+ ].join('');
+
+ assert.equal(html, render(str));
+
+ var str = [
+ '- var users = ["tobi", "loki", "jane"]',
+ 'for user in users',
+ ' li= user',
+ ].join('\n');
+
+ var html = [
+ '<li>tobi</li>',
+ '<li>loki</li>',
+ '<li>jane</li>',
+ ].join('');
+
+ assert.equal(html, render(str));
+ },
+
+ 'test if': function(assert){
+ var str = [
+ '- var users = ["tobi", "loki", "jane"]',
+ 'if users.length',
+ ' p users: #{users.length}',
+ ].join('\n');
+
+ assert.equal('<p>users: 3</p>', render(str));
+
+ assert.equal('<iframe foo="bar"></iframe>', render('iframe(foo="bar")'));
+ },
+
+ 'test unless': function(assert){
+ var str = [
+ '- var users = ["tobi", "loki", "jane"]',
+ 'unless users.length',
+ ' p no users',
+ ].join('\n');
+
+ assert.equal('', render(str));
+
+ var str = [
+ '- var users = []',
+ 'unless users.length',
+ ' p no users',
+ ].join('\n');
+
+ assert.equal('<p>no users</p>', render(str));
+ },
+
+ 'test else': function(assert){
+ var str = [
+ '- var users = []',
+ 'if users.length',
+ ' p users: #{users.length}',
+ 'else',
+ ' p users: none',
+ ].join('\n');
+
+ assert.equal('<p>users: none</p>', render(str));
+ },
+
+ 'test else if': function(assert){
+ var str = [
+ '- var users = ["tobi", "jane", "loki"]',
+ 'for user in users',
+ ' if user == "tobi"',
+ ' p awesome #{user}',
+ ' else if user == "jane"',
+ ' p lame #{user}',
+ ' else',
+ ' p #{user}',
+ ].join('\n');
+
+ assert.equal('<p>awesome tobi</p><p>lame jane</p><p>loki</p>', render(str));
+ },
+
+ 'test .compile()': function(assert){
+ var fn = jade.compile('p foo');
+ assert.equal('<p>foo</p>', fn());
+ },
+
+ 'test .compile() locals': function(assert){
+ var fn = jade.compile('p= foo');
+ assert.equal('<p>bar</p>', fn({ foo: 'bar' }));
+ },
+
+ 'test .compile() no debug': function(assert){
+ var fn = jade.compile('p foo\np #{bar}', {compileDebug: false});
+ assert.equal('<p>foo</p><p>baz</p>', fn({bar: 'baz'}));
+ },
+
+ 'test .compile() no debug and global helpers': function(assert){
+ var fn = jade.compile('p foo\np #{bar}', {compileDebug: false, helpers: 'global'});
+ assert.equal('<p>foo</p><p>baz</p>', fn({bar: 'baz'}));
+ },
+
+ 'test null attrs on tag': function(assert){
+ var tag = new jade.nodes.Tag('a'),
+ name = 'href',
+ val = '"/"';
+ tag.setAttribute(name, val)
+ assert.equal(tag.getAttribute(name), val)
+ tag.removeAttribute(name)
+ assert.isUndefined(tag.getAttribute(name))
+ }
};
url = require('url'),
path = require('path');
-this.version = [0, 5, 6];
+this.version = [0, 5, 9];
var mime = require('./node-static/mime');
var util = require('./node-static/util');
this.Server = function (root, options) {
if (root && (typeof(root) === 'object')) { options = root, root = null }
- this.root = path.normalize(root || '.');
+ this.root = path.resolve(root || '.');
this.options = options || {};
this.cache = 3600;
var that = this;
var promise = new(events.EventEmitter);
- pathname = this.normalize(pathname);
+ pathname = this.resolve(pathname);
fs.stat(pathname, function (e, stat) {
if (e) {
var that = this,
promise = new(events.EventEmitter);
- pathname = this.normalize(pathname);
+ pathname = this.resolve(pathname);
// Only allow GET and HEAD requests
if (req.method !== 'GET' && req.method !== 'HEAD') {
// Make sure we're not trying to access a
// file outside of the root.
- if (new(RegExp)('^' + that.root).test(pathname)) {
+ if (pathname.indexOf(that.root) === 0) {
fs.stat(pathname, function (e, stat) {
if (e) {
finish(404, {});
}
return promise;
};
-this.Server.prototype.normalize = function (pathname) {
- return path.normalize(path.join(this.root, pathname));
+this.Server.prototype.resolve = function (pathname) {
+ return path.resolve(path.join(this.root, pathname));
};
this.Server.prototype.serve = function (req, res, callback) {
var that = this,
promise = new(events.EventEmitter);
-
- var pathname = url.parse(req.url).pathname;
+
+ var pathname = decodeURI(url.parse(req.url).pathname);
var finish = function (status, headers) {
that.finish(status, headers, req, res, promise, callback);
headers['Last-Modified'] = new(Date)(stat.mtime).toUTCString();
// Conditional GET
- // If both the "If-Modified-Since" and "If-None-Match" headers
+ // If the "If-Modified-Since" or "If-None-Match" headers
// match the conditions, send a 304 Not Modified.
- if (req.headers['if-none-match'] === headers['Etag'] &&
+ if (req.headers['if-none-match'] === headers['Etag'] ||
Date.parse(req.headers['if-modified-since']) >= mtime) {
finish(304, headers);
} else if (req.method === 'HEAD') {
headers['Content-Type'] = mime.contentTypes[path.extname(files[0]).slice(1)] ||
'application/octet-stream';
- for (var k in headers) { _headers[k] = headers[k] }
+ for (var k in _headers) { headers[k] = _headers[k] }
- res.writeHead(status, _headers);
+ res.writeHead(status, headers);
// If the file was cached and it's not older
// than what's on disk, serve the cached version.
if (this.cache && (key in exports.store) &&
exports.store[key].stat.mtime >= stat.mtime) {
res.end(exports.store[key].buffer);
- finish(status, _headers);
+ finish(status, headers);
} else {
this.stream(pathname, files, new(buffer.Buffer)(stat.size), res, function (e, buffer) {
if (e) { return finish(500, {}) }
buffer: buffer,
timestamp: Date.now()
};
- finish(status, _headers);
+ finish(status, headers);
});
}
}
"dependencies" : [],
"lib" : "lib",
"main" : "./lib/node-static",
- "version" : "0.5.6",
+ "version" : "0.5.9",
"directories" : { "test": "./test" },
"engines" : { "node": ">= 0.4.1" }
}
+0.8.3 / 2011-09-03
+==================
+
+ * Fixed `\n` parsing for non-JSON packets.
+ * Fixed; make Socket.IO XHTML doctype compatible (fixes #460 from server)
+ * Fixed support for Node.JS running `socket.io-client`.
+ * Updated repository name in `package.json`.
+ * Added support for different policy file ports without having to port
+ forward 843 on the server side [3rd-Eden]
+
+0.8.2 / 2011-08-29
+==================
+
+ * Fixed flashsocket detection.
+
+0.8.1 / 2011-08-29
+==================
+
+ * Bump version.
+
+0.8.0 / 2011-08-28
+==================
+
+ * Added MozWebSocket support (hybi-10 doesn't require API changes) [einaros].
+
+0.7.11 / 2011-08-27
+===================
+
+ * Corrected previous release (missing build).
+
+0.7.10 / 2011-08-27
+===================
+
+ * Fix for failing fallback in websockets
+
+0.7.9 / 2011-08-12
+==================
+
+ * Added check on `Socket#onConnect` to prevent double `connect` events on the main manager.
+ * Fixed socket namespace connect test. Remove broken alternative namespace connect test.
+ * Removed test handler for removed test.
+ * Bumped version to match `socket.io` server.
+
+0.7.5 / 2011-08-08
+==================
+
+ * Added querystring support for `connect` [3rd-Eden]
+ * Added partial Node.JS transports support [3rd-Eden, josephg]
+ * Fixed builder test.
+ * Changed `util.inherit` to replicate Object.create / __proto__.
+ * Changed and cleaned up some acceptance tests.
+ * Fixed race condition with a test that could not be run multiple times.
+ * Added test for encoding a payload.
+ * Added the ability to override the transport to use in acceptance test [3rd-Eden]
+ * Fixed multiple connect packets [DanielBaulig]
+ * Fixed jsonp-polling over-buffering [3rd-Eden]
+ * Fixed ascii preservation in minified socket.io client [3rd-Eden]
+ * Fixed socket.io in situations where the page is not served through utf8.
+ * Fixed namespaces not reconnecting after disconnect [3rd-Eden]
+ * Fixed default port for secure connections.
+
0.7.4 / 2011-07-12
==================
ALL_TESTS = $(shell find test/ -name '*.test.js')
run-tests:
- @npm link --local > /dev/null
@./node_modules/.bin/expresso \
-I lib \
-I support \
@$(MAKE) TESTS="$(ALL_TESTS)" run-tests
test-acceptance:
- @npm link --local > /dev/null
- @node support/test-runner/app
+ @node support/test-runner/app $(TRANSPORT)
build:
@node ./bin/builder.js
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
-
+
/**
* Module dependencies.
*/
/**
* License headers.
+ *
* @api private
*/
-
+
var template = '/*! Socket.IO.%ext% build:' + socket.version + ', %type%. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */\n'
, development = template.replace('%type%', 'development').replace('%ext%', 'js')
, production = template.replace('%type%', 'production').replace('%ext%', 'min.js');
/**
- * If statements, these allows you to create serveride & client side compatible code
- * using specially designed `if` statements that remove serverside designed code from
- * the source files
+ * If statements, these allows you to create serveride & client side compatible
+ * code using specially designed `if` statements that remove serverside
+ * designed code from the source files
*
* @api private
*/
+
var starttagIF = '// if node'
, endtagIF = '// end node';
* @type {Array}
* @api private
*/
-
+
var base = [
'io.js'
, 'util.js'
* @type {Object}
* @api public
*/
-
- var baseTransports = {
+
+var baseTransports = {
'websocket': ['transports/websocket.js']
, 'flashsocket': [
- 'transports/websocket.js'
- , 'transports/flashsocket.js'
- , 'vendor/web-socket-js/swfobject.js'
- , 'vendor/web-socket-js/web_socket.js'
+ 'transports/websocket.js'
+ , 'transports/flashsocket.js'
+ , 'vendor/web-socket-js/swfobject.js'
+ , 'vendor/web-socket-js/web_socket.js'
]
, 'htmlfile': ['transports/xhr.js', 'transports/htmlfile.js']
/* FIXME: re-enable me once we have multi-part support
, 'xhr-multipart': ['transports/xhr.js', 'transports/xhr-multipart.js'] */
, 'xhr-polling': ['transports/xhr.js', 'transports/xhr-polling.js']
- , 'jsonp-polling': ['transports/xhr.js', 'transports/jsonp-polling.js']
- };
+ , 'jsonp-polling': [
+ 'transports/xhr.js'
+ , 'transports/xhr-polling.js'
+ , 'transports/jsonp-polling.js'
+ ]
+};
/**
- * Builds a custom Socket.IO distribution based on the transports that you need. You
- * can configure the build to create development build or production build (minified).
+ * Builds a custom Socket.IO distribution based on the transports that you
+ * need. You can configure the build to create development build or production
+ * build (minified).
*
* @param {Array} transports The transports that needs to be bundled.
* @param {Object} [options] Options to configure the building process.
- * @param {Function} callback The argument is always the callback, because the options are.. optional:D.
- * @callback {String|Boolean} err An optional argument, if it exists than an error occurred during the build process.
+ * @param {Function} callback Last argument should always be the callback
+ * @callback {String|Boolean} err An optional argument, if it exists than an error
+ * occurred during the build process.
* @callback {String} result The result of the build process.
* @api public
*/
-
-var builder = module.exports = function(){
+
+var builder = module.exports = function () {
var transports, options, callback, error = null
- , args = Array.prototype.slice.call(arguments,0)
+ , args = Array.prototype.slice.call(arguments, 0)
, settings = {
minify: true
, node: false
, custom: []
};
-
- // Fancy pancy argument support
- // this makes any pattern possible mainly because we require only one of each type
- args.forEach(function(arg){
- switch(Object.prototype.toString.call(arg).replace(/\[object\s(\w+)\]/gi , '$1' ).toLowerCase()){
+
+ // Fancy pancy argument support this makes any pattern possible mainly
+ // because we require only one of each type
+ args.forEach(function (arg) {
+ var type = Object.prototype.toString.call(arg)
+ .replace(/\[object\s(\w+)\]/gi , '$1' ).toLowerCase();
+
+ switch (type) {
case 'array':
return transports = arg;
case 'object':
return callback = arg;
}
});
-
+
// Add defaults
options = options || {};
transports = transports || Object.keys(baseTransports);
-
+
// Merge the data
- for(var option in options) settings[option] = options[option];
-
- // Start creating a dependencies chain with all the required files for the custom Socket.IO bundle.
+ for(var option in options) {
+ settings[option] = options[option];
+ }
+
+ // Start creating a dependencies chain with all the required files for the
+ // custom Socket.IO bundle.
var files = [];
- base.forEach(function(file){
+ base.forEach(function (file) {
files.push(__dirname + '/../lib/' + file);
});
-
- transports.forEach(function(transport){
+
+ transports.forEach(function (transport) {
var dependencies = baseTransports[transport];
- if (!dependencies) return error = 'Unsupported transport `' + transport + '` supplied as argument.';
-
+ if (!dependencies) {
+ error = 'Unsupported transport `' + transport + '` supplied as argument.';
+ return;
+ }
+
// Add the files to the files list, but only if they are not added before
- dependencies.forEach(function(file){
+ dependencies.forEach(function (file) {
var path = __dirname + '/../lib/' + file;
if (!~files.indexOf(path)) files.push(path);
})
});
-
+
// check to see if the files tree compilation generated any errors.
if (error) return callback(error);
-
+
var results = {};
- files.forEach(function(file){
- fs.readFile(file, function(err, content){
+ files.forEach(function (file) {
+ fs.readFile(file, function (err, content) {
if (err) error = err;
results[file] = content;
-
+
// check if we are done yet, or not.. Just by checking the size of the result
// object.
if (Object.keys(results).length !== files.length) return;
-
+
// we are done, did we error?
if (error) return callback(error);
-
+
// concatinate the file contents in order
var code = development
, ignore = 0;
-
- files.forEach(function(file){
+
+ files.forEach(function (file) {
code += results[file];
});
-
+
// check if we need to add custom code
- if (settings.custom.length){
- settings.custom.forEach(function(content){
+ if (settings.custom.length) {
+ settings.custom.forEach(function (content) {
code += content;
- })
+ });
}
-
- // Search for conditional code blocks that need to be removed as they where designed for
- // a server side env. but only if we don't want to make this build node compatible
- if (!settings.node){
- code = code.split('\n').filter(function(line){
+
+ // Search for conditional code blocks that need to be removed as they
+ // where designed for a server side env. but only if we don't want to
+ // make this build node compatible.
+ if (!settings.node) {
+ code = code.split('\n').filter(function (line) {
// check if there are tags in here
var start = line.indexOf(starttagIF) >= 0
, end = line.indexOf(endtagIF) >= 0
, ret = ignore;
-
+
// ignore the current line
- if (start) ignore++,ret = ignore;
-
+ if (start) {
+ ignore++;
+ ret = ignore;
+ }
+
// stop ignoring the next line
- if (end) ignore--;
-
+ if (end) {
+ ignore--;
+ }
+
return ret == 0;
- }).join('\n')
+ }).join('\n');
}
-
+
// check if we need to process it any further
- if (settings.minify){
+ if (settings.minify) {
+ // uglify hate unicode chars...
+ var separator = '@@OMGYUCHANGEME@@@';
+ code = code.replace(/(\\ufffd)/g, separator);
+
var ast = uglify.parser.parse(code);
ast = uglify.uglify.ast_mangle(ast);
ast = uglify.uglify.ast_squeeze(ast);
-
+
code = production + uglify.uglify.gen_code(ast);
+
+ // restore the code
+ code = code.replace(new RegExp('('+ separator + ')', 'g'), '\\ufffd');
}
-
+
callback(error, code);
})
})
*/
if (!module.parent){
- var args = process.argv.slice(2); // the first 2 are `node` and the path to this file, we don't need them
-
+ // the first 2 are `node` and the path to this file, we don't need them
+ var args = process.argv.slice(2);
+
// build a development build
- builder(args.length ? args : false, {minify:false},function(err, content){
+ builder(args.length ? args : false, { minify:false }, function (err, content) {
if (err) return console.error(err);
-
- fs.write(fs.openSync(__dirname + '/../dist/socket.io.js', 'w'), content, 0, 'utf8');
+
+ fs.write(
+ fs.openSync(__dirname + '/../dist/socket.io.js', 'w')
+ , content
+ , 0
+ , 'utf8'
+ );
console.log('Successfully generated the development build: socket.io.js');
});
-
+
// and build a production build
- builder(args.length ? args : false, function(err, content){
+ builder(args.length ? args : false, function (err, content) {
if (err) return console.error(err);
-
- fs.write(fs.openSync(__dirname + '/../dist/socket.io.min.js', 'w'), content, 0, 'utf8');
+
+ fs.write(
+ fs.openSync(__dirname + '/../dist/socket.io.min.js', 'w')
+ , content
+ , 0
+ , 'utf8'
+ );
console.log('Successfully generated the production build: socket.io.min.js');
});
}
-/*! Socket.IO.js build:0.7.4, development. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */
+/*! Socket.IO.js build:0.8.4, development. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */
/**
* socket.io
* @api public
*/
- io.version = '0.7.4';
+ io.version = '0.8.4';
/**
* Protocol implemented.
var options = {
host: uri.host
- , secure: uri.protocol == 'https'
- , port: uri.port || 80
+ , secure: 'https' == uri.protocol
+ , port: uri.port || ('https' == uri.protocol ? 443 : 80)
+ , query: uri.query || ''
};
+
io.util.merge(options, details);
if (options['force new connection'] || !io.sockets[uuri]) {
* MIT Licensed
*/
-(function (exports) {
+(function (exports, global) {
/**
* Utilities namespace.
, host = uri.host
, port = uri.port;
- if ('undefined' != typeof document) {
+ if ('document' in global) {
host = host || document.domain;
port = port || (protocol == 'https'
&& document.location.protocol !== 'https:' ? 443 : document.location.port);
return (protocol || 'http') + '://' + host + ':' + (port || 80);
};
+ /**
+ * Mergest 2 query strings in to once unique query string
+ *
+ * @param {String} base
+ * @param {String} addition
+ * @api public
+ */
+
+ util.query = function (base, addition) {
+ var query = util.chunkQuery(base || '')
+ , components = [];
+
+ util.merge(query, util.chunkQuery(addition || ''));
+ for (var part in query) {
+ if (query.hasOwnProperty(part)) {
+ components.push(part + '=' + query[part]);
+ }
+ }
+
+ return components.length ? '?' + components.join('&') : '';
+ };
+
+ /**
+ * Transforms a querystring in to an object
+ *
+ * @param {String} qs
+ * @api public
+ */
+
+ util.chunkQuery = function (qs) {
+ var query = {}
+ , params = qs.split('&')
+ , i = 0
+ , l = params.length
+ , kv;
+
+ for (; i < l; ++i) {
+ kv = params[i].split('=');
+ if (kv[0]) {
+ query[kv[0]] = decodeURIComponent(kv[1]);
+ }
+ }
+
+ return query;
+ };
+
/**
* Executes the given function when the page is loaded.
*
var pageLoaded = false;
util.load = function (fn) {
- if (document.readyState === 'complete' || pageLoaded) {
+ if ('document' in global && document.readyState === 'complete' || pageLoaded) {
return fn();
}
- util.on(window, 'load', fn, false);
+ util.on(global, 'load', fn, false);
};
/**
util.on = function (element, event, fn, capture) {
if (element.attachEvent) {
element.attachEvent('on' + event, fn);
- } else {
+ } else if (element.addEventListener) {
element.addEventListener(event, fn, capture);
}
};
*/
util.request = function (xdomain) {
+
if ('undefined' != typeof window) {
if (xdomain && window.XDomainRequest) {
return new XDomainRequest();
- };
+ }
if (window.XMLHttpRequest && (!xdomain || util.ua.hasCORS)) {
return new XMLHttpRequest();
- };
+ }
if (!xdomain) {
try {
*/
util.inherit = function (ctor, ctor2) {
- ctor.prototype = new ctor2;
- util.merge(ctor, ctor2);
+ function f() {};
+ f.prototype = ctor2.prototype;
+ ctor.prototype = new f;
};
/**
util.intersect = function (arr, arr2) {
var ret = []
, longest = arr.length > arr2.length ? arr : arr2
- , shortest = arr.length > arr2.length ? arr2 : arr
+ , shortest = arr.length > arr2.length ? arr2 : arr;
for (var i = 0, l = shortest.length; i < l; i++) {
if (~util.indexOf(longest, shortest[i]))
return Array.prototype.indexOf.call(arr, o, i);
}
- for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0
- ; i < j && arr[i] !== o; i++);
+ for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0;
+ i < j && arr[i] !== o; i++);
return j <= i ? -1 : i;
};
util.ua.webkit = 'undefined' != typeof navigator
&& /webkit/i.test(navigator.userAgent);
-})('undefined' != typeof window ? io : module.exports);
+})(
+ 'undefined' != typeof window ? io : module.exports
+ , this
+);
/**
* socket.io
switch (packet.type) {
case 'error':
var reason = packet.reason ? indexOf(reasons, packet.reason) : ''
- , adv = packet.advice ? indexOf(advice, packet.advice) : ''
+ , adv = packet.advice ? indexOf(advice, packet.advice) : '';
if (reason !== '' || adv !== '')
- data = reason + (adv !== '' ? ('+' + adv) : '')
+ data = reason + (adv !== '' ? ('+' + adv) : '');
break;
for (var i = 0, l = packets.length; i < l; i++) {
var packet = packets[i];
- decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]
+ decoded += '\ufffd' + packet.length + '\ufffd' + packets[i];
}
return decoded;
* @api private
*/
- var regexp = /^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;
+ var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
parser.decodePacket = function (data) {
var pieces = data.match(regexp);
+ options.resource + '/' + io.protocol
+ '/' + this.name + '/' + this.sessid;
};
+
+ /**
+ * Checks if the transport is ready to start a connection.
+ *
+ * @param {Socket} socket The socket instance that needs a transport
+ * @param {Function} fn The callback
+ * @api private
+ */
+
+ Transport.prototype.ready = function (socket, fn) {
+ fn.call(this);
+ };
})(
'undefined' != typeof io ? io : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
* MIT Licensed
*/
-(function (exports, io) {
+(function (exports, io, global) {
/**
* Expose constructor.
exports.Socket = Socket;
/**
- * Create a new `Socket.IO client` which can establish a persisent
+ * Create a new `Socket.IO client` which can establish a persistent
* connection with a Socket.IO enabled server.
*
* @api public
this.options = {
port: 80
, secure: false
- , document: document
+ , document: 'document' in global ? document : false
, resource: 'socket.io'
, transports: io.transports
, 'connect timeout': 10000
, 'try multiple transports': true
, 'reconnect': true
, 'reconnection delay': 500
+ , 'reconnection limit': Infinity
, 'reopen delay': 3000
, 'max reconnection attempts': 10
, 'sync disconnect on unload': true
, 'auto connect': true
+ , 'flash policy port': 10843
};
io.util.merge(this.options, options);
(!this.isXDomain() || io.util.ua.hasCORS)) {
var self = this;
- io.util.on(window, 'beforeunload', function () {
+ io.util.on(global, 'beforeunload', function () {
self.disconnectSync();
}, false);
}
, options.host + ':' + options.port
, this.options.resource
, io.protocol
- , '?t=' + + new Date
+ , io.util.query(this.options.query, 't=' + +new Date)
].join('/');
if (this.isXDomain()) {
var insertAt = document.getElementsByTagName('script')[0]
- , script = document.createElement('SCRIPT');
+ , script = document.createElement('script');
script.src = url + '&jsonp=' + io.j.length;
insertAt.parentNode.insertBefore(script, insertAt);
} else {
var xhr = io.util.request();
- xhr.open('GET', url);
+ xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
xhr.onreadystatechange = empty;
);
function connect (transports){
+ if (self.transport) self.transport.clearTimeouts();
+
self.transport = self.getTransport(transports);
if (!self.transport) return self.publish('connect_failed');
- self.connecting = true;
- self.publish('connecting', self.transport.name);
- self.transport.open();
+ // once the transport is ready
+ self.transport.ready(self, function () {
+ self.connecting = true;
+ self.publish('connecting', self.transport.name);
+ self.transport.open();
- if (self.options['connect timeout']) {
- self.connectTimeoutTimer = setTimeout(function () {
- if (!self.connected) {
- self.connecting = false;
+ if (self.options['connect timeout']) {
+ self.connectTimeoutTimer = setTimeout(function () {
+ if (!self.connected) {
+ self.connecting = false;
- if (self.options['try multiple transports']) {
- if (!self.remainingTransports) {
- self.remainingTransports = self.transports.slice(0);
- }
+ if (self.options['try multiple transports']) {
+ if (!self.remainingTransports) {
+ self.remainingTransports = self.transports.slice(0);
+ }
- var remaining = self.remainingTransports;
+ var remaining = self.remainingTransports;
- while (remaining.length > 0 && remaining.splice(0,1)[0] !=
- self.transport.name) {}
+ while (remaining.length > 0 && remaining.splice(0,1)[0] !=
+ self.transport.name) {}
- if (remaining.length){
- connect(remaining);
- } else {
- self.publish('connect_failed');
+ if (remaining.length){
+ connect(remaining);
+ } else {
+ self.publish('connect_failed');
+ }
}
}
- }
- }, self.options['connect timeout']);
- }
+ }, self.options['connect timeout']);
+ }
+ });
}
connect();
*/
Socket.prototype.isXDomain = function () {
- var locPort = window.location.port || 80;
- return this.options.host !== document.domain || this.options.port != locPort;
+
+ var port = window.location.port ||
+ ('https:' == window.location.protocol ? 443 : 80);
+
+ return this.options.host !== document.domain || this.options.port != port;
};
/**
*/
Socket.prototype.onConnect = function () {
- this.connected = true;
- this.connecting = false;
- if (!this.doBuffer) {
- // make sure to flush the buffer
- this.setBuffer(false);
+ if (!this.connected) {
+ this.connected = true;
+ this.connecting = false;
+ if (!this.doBuffer) {
+ // make sure to flush the buffer
+ this.setBuffer(false);
+ }
+ this.emit('connect');
}
- this.emit('connect');
};
/**
var self = this
, maxAttempts = this.options['max reconnection attempts']
, tryMultiple = this.options['try multiple transports']
+ , limit = this.options['reconnection limit'];
function reset () {
if (self.connected) {
+ for (var i in self.namespaces) {
+ if (self.namespaces.hasOwnProperty(i) && '' !== i) {
+ self.namespaces[i].packet({ type: 'connect' });
+ }
+ }
self.publish('reconnect', self.transport.name, self.reconnectionAttempts);
}
reset();
}
} else {
- self.reconnectionDelay *= 2; // exponential back off
+ if (self.reconnectionDelay < limit) {
+ self.reconnectionDelay *= 2; // exponential back off
+ }
+
self.connect();
self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts);
self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay);
})(
'undefined' != typeof io ? io : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
+ , this
);
/**
* socket.io
*/
WS.prototype.open = function () {
- this.websocket = new WebSocket(this.prepareUrl());
+ var query = io.util.query(this.socket.options.query)
+ , self = this
+ , Socket
+
+
+ if (!Socket) {
+ Socket = window.MozWebSocket || window.WebSocket;
+ }
+
+ this.websocket = new Socket(this.prepareUrl() + query);
- var self = this;
this.websocket.onopen = function () {
self.onOpen();
self.socket.setBuffer(false);
*/
WS.check = function () {
- return 'WebSocket' in window && !('__addTask' in WebSocket);
+ return ('WebSocket' in window && !('__addTask' in WebSocket))
+ || 'MozWebSocket' in window;
};
/**
exports.flashsocket = Flashsocket;
/**
- * The Flashsocket transport. This is a API wrapper for the HTML5 WebSocket
+ * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket
* specification. It uses a .swf file to communicate with the server. If you want
* to serve the .swf file from a other server than where the Socket.IO script is
* coming from you need to use the insecure version of the .swf. More information
Flashsocket.prototype.name = 'flashsocket';
/**
- *Disconnect the established `Flashsocket` connection. This is done by adding a
- * new task to the Flashsocket. The rest will be handled off by the `WebSocket`
+ * Disconnect the established `FlashSocket` connection. This is done by adding a
+ * new task to the FlashSocket. The rest will be handled off by the `WebSocket`
* transport.
*
* @returns {Transport}
*/
Flashsocket.prototype.open = function () {
- var self = this, args = arguments;
+ var self = this
+ , args = arguments;
+
WebSocket.__addTask(function () {
io.Transport.websocket.prototype.open.apply(self, args);
});
/**
* Sends a message to the Socket.IO server. This is done by adding a new
- * task to the Flashsocket. The rest will be handled off by the `WebSocket`
+ * task to the FlashSocket. The rest will be handled off by the `WebSocket`
* transport.
*
* @returns {Transport}
};
/**
- * Disconnects the established `Flashsocket` connection.
+ * Disconnects the established `FlashSocket` connection.
*
* @returns {Transport}
* @api public
};
/**
- * Check if the Flashsocket transport is supported as it requires that the Adobe
- * Flash Player plugin version `10.0.0` or greater is installed. And also check if
+ * The WebSocket fall back needs to append the flash container to the body
+ * element, so we need to make sure we have access to it. Or defer the call
+ * until we are sure there is a body element.
+ *
+ * @param {Socket} socket The socket instance that needs a transport
+ * @param {Function} fn The callback
+ * @api private
+ */
+
+ Flashsocket.prototype.ready = function (socket, fn) {
+ function init () {
+ var options = socket.options
+ , port = options['flash policy port']
+ , path = [
+ 'http' + (options.secure ? 's' : '') + ':/'
+ , options.host + ':' + options.port
+ , options.resource
+ , 'static/flashsocket'
+ , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf'
+ ];
+
+ // Only start downloading the swf file when the checked that this browser
+ // actually supports it
+ if (!Flashsocket.loaded) {
+ if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') {
+ // Set the correct file based on the XDomain settings
+ WEB_SOCKET_SWF_LOCATION = path.join('/');
+ }
+
+ if (port !== 843) {
+ WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port);
+ }
+
+ WebSocket.__initialize();
+ Flashsocket.loaded = true;
+ }
+
+ fn.call(self);
+ }
+
+ var self = this;
+ if (document.body) return init();
+
+ io.util.load(init);
+ };
+
+ /**
+ * Check if the FlashSocket transport is supported as it requires that the Adobe
+ * Flash Player plug-in version `10.0.0` or greater is installed. And also check if
* the polyfill is correctly loaded.
*
* @returns {Boolean}
* @api public
*/
- Flashsocket.check = function (socket) {
+ Flashsocket.check = function () {
if (
typeof WebSocket == 'undefined'
|| !('__initialize' in WebSocket) || !swfobject
) return false;
- var supported = swfobject.getFlashPlayerVersion().major >= 10
- , options = socket.options
- , path = [
- 'http' + (options.secure ? 's' : '') + ':/'
- , options.host + ':' + options.port
- , options.resource
- , 'static/flashsocket'
- , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf'
- ];
-
- // Only start downloading the swf file when the checked that this browser
- // actually supports it
- if (supported && !Flashsocket.loaded) {
- if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') {
- // Set the correct file based on the XDomain settings
- WEB_SOCKET_SWF_LOCATION = path.join('/');
- }
-
- WebSocket.__initialize();
- Flashsocket.loaded = true;
- }
-
- return supported;
+ return swfobject.getFlashPlayerVersion().major >= 10;
};
/**
- * Check if the Flashsocket transport can be used as cross domain / cross origin
+ * Check if the FlashSocket transport can be used as cross domain / cross origin
* transport. Because we can't see which type (secure or insecure) of .swf is used
* we will just return true.
*
* MIT Licensed
*/
-(function (exports, io) {
+(function (exports, io, global) {
/**
* Expose constructor.
this.sendXHR = this.request('POST');
- if (window.XDomainRequest && this.sendXHR instanceof XDomainRequest) {
+ if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) {
this.sendXHR.onload = this.sendXHR.onerror = onload;
} else {
this.sendXHR.onreadystatechange = stateChange;
*/
XHR.prototype.request = function (method) {
- var req = io.util.request(this.socket.isXDomain());
- req.open(method || 'GET', this.prepareUrl() + '?t' + (+ new Date));
+ var req = io.util.request(this.socket.isXDomain())
+ , query = io.util.query(this.socket.options.query, 't=' + +new Date);
+
+ req.open(method || 'GET', this.prepareUrl() + query, true);
if (method == 'POST') {
try {
return false;
};
-
+
/**
* Check if the XHR transport supports corss domain requests.
*
})(
'undefined' != typeof io ? io.Transport : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
+ , this
);
/**
iframeC.appendChild(this.iframe);
- this.iframe.src = this.prepareUrl() + '/?t=' + (+ new Date);
+ var self = this
+ , query = io.util.query(this.socket.options.query, 't='+ +new Date);
- var self = this;
+ this.iframe.src = this.prepareUrl() + query;
io.util.on(window, 'unload', function () {
self.destroy();
* MIT Licensed
*/
-(function (exports, io) {
+(function (exports, io, global) {
/**
* Expose constructor.
io.util.inherit(XHRPolling, io.Transport.XHR);
+ /**
+ * Merge the properties from XHR transport
+ */
+
+ io.util.merge(XHRPolling, io.Transport.XHR);
+
/**
* Transport name
*
XHRPolling.prototype.open = function () {
var self = this;
- io.util.defer(function () {
- io.Transport.XHR.prototype.open.call(self);
- });
-
+ io.Transport.XHR.prototype.open.call(self);
return false;
};
this.xhr = this.request();
- if (window.XDomainRequest && this.xhr instanceof XDomainRequest) {
+ if (global.XDomainRequest && this.xhr instanceof XDomainRequest) {
this.xhr.onload = this.xhr.onerror = onload;
} else {
this.xhr.onreadystatechange = stateChange;
}
};
+ /**
+ * Webkit based browsers show a infinit spinner when you start a XHR request
+ * before the browsers onload event is called so we need to defer opening of
+ * the transport until the onload event is called. Wrapping the cb in our
+ * defer method solve this.
+ *
+ * @param {Socket} socket The socket instance that needs a transport
+ * @param {Function} fn The callback
+ * @api private
+ */
+
+ XHRPolling.prototype.ready = function (socket, fn) {
+ var self = this;
+
+ io.util.defer(function () {
+ fn.call(self);
+ });
+ };
+
/**
* Add the transport to your public io.transports array.
*
})(
'undefined' != typeof io ? io.Transport : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
+ , this
);
/**
*/
JSONPPolling.prototype.post = function (data) {
- var self = this;
+ var self = this
+ , query = io.util.query(
+ this.socket.options.query
+ , 't='+ (+new Date) + '&i=' + this.index
+ );
if (!this.form) {
- var form = document.createElement('FORM')
- , area = document.createElement('TEXTAREA')
+ var form = document.createElement('form')
+ , area = document.createElement('textarea')
, id = this.iframeId = 'socketio_iframe_' + this.index
, iframe;
form.style.left = '-1000px';
form.target = id;
form.method = 'POST';
+ form.setAttribute('accept-charset', 'utf-8');
area.name = 'd';
form.appendChild(area);
document.body.appendChild(form);
this.area = area;
}
- this.form.action = this.prepareUrl() + '?t=' + (+new Date) + '&i=' + this.index;
+ this.form.action = this.prepareUrl() + query;
function complete () {
initIframe();
} else {
this.iframe.onload = complete;
}
+
+ this.socket.setBuffer(true);
};
/**
JSONPPolling.prototype.get = function () {
var self = this
- , script = document.createElement('SCRIPT');
+ , script = document.createElement('script')
+ , query = io.util.query(
+ this.socket.options.query
+ , 't='+ (+new Date) + '&i=' + this.index
+ );
if (this.script) {
this.script.parentNode.removeChild(this.script);
}
script.async = true;
- script.src = this.prepareUrl() + '/?t=' + (+new Date) + '&i=' + this.index;
+ script.src = this.prepareUrl() + query;
script.onerror = function () {
self.onClose();
};
-/*! Socket.IO.min.js build:0.7.4, production. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */
-(function(a){var b=a;b.version="0.7.4",b.protocol=1,b.transports=[],b.j=[],b.sockets={},b.connect=function(a,c){var d=b.util.parseUri(a),e,f;"undefined"!=typeof document&&(d.protocol=d.protocol||document.location.protocol.slice(0,-1),d.host=d.host||document.domain,d.port=d.port||document.location.port),e=b.util.uniqueUri(d);var g={host:d.host,secure:d.protocol=="https",port:d.port||80};b.util.merge(g,c);if(g["force new connection"]||!b.sockets[e])f=new b.Socket(g);!g["force new connection"]&&f&&(b.sockets[e]=f),f=f||b.sockets[e];return f.of(d.path.length>1?d.path:"")}})("object"==typeof module?module.exports:window.io={}),function(a){var b=a.util={},c=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,d=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];b.parseUri=function(a){var b=c.exec(a||""),e={},f=14;while(f--)e[d[f]]=b[f]||"";return e},b.uniqueUri=function(a){var b=a.protocol,c=a.host,d=a.port;"undefined"!=typeof document?(c=c||document.domain,d=d||(b=="https"&&document.location.protocol!=="https:"?443:document.location.port)):(c=c||"localhost",!d&&b=="https"&&(d=443));return(b||"http")+"://"+c+":"+(d||80)};var e=!1;b.load=function(a){if(document.readyState==="complete"||e)return a();b.on(window,"load",a,!1)},b.on=function(a,b,c,d){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener(b,c,d)},b.request=function(a){if("undefined"!=typeof window){if(a&&window.XDomainRequest)return new XDomainRequest;if(window.XMLHttpRequest&&(!a||b.ua.hasCORS))return new XMLHttpRequest;if(!a)try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(c){}}return null},"undefined"!=typeof window&&b.load(function(){e=!0}),b.defer=function(a){if(!b.ua.webkit)return a();b.load(function(){setTimeout(a,100)})},b.merge=function f(c,d,e,f){var g=f||[],h=typeof e=="undefined"?2:e,i;for(i in d)d.hasOwnProperty(i)&&b.indexOf(g,i)<0&&(typeof c[i]!="object"||!h?(c[i]=d[i],g.push(d[i])):b.merge(c[i],d[i],h-1,g));return c},b.mixin=function(a,c){b.merge(a.prototype,c.prototype)},b.inherit=function(a,c){a.prototype=new c,b.merge(a,c)},b.isArray=Array.isArray||function(a){return Object.prototype.toString.call(a)==="[object Array]"},b.intersect=function(a,c){var d=[],e=a.length>c.length?a:c,f=a.length>c.length?c:a;for(var g=0,h=f.length;g<h;g++)~b.indexOf(e,f[g])&&d.push(f[g]);return d},b.indexOf=function(a,b,c){if(Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b,c);for(var d=a.length,c=c<0?c+d<0?0:c+d:c||0;c<d&&a[c]!==b;c++);return d<=c?-1:c},b.toArray=function(a){var b=[];for(var c=0,d=a.length;c<d;c++)b.push(a[c]);return b},b.ua={},b.ua.hasCORS="undefined"!=typeof window&&window.XMLHttpRequest&&function(){try{var a=new XMLHttpRequest}catch(b){return!1}return a.withCredentials!=undefined}(),b.ua.webkit="undefined"!=typeof navigator&&/webkit/i.test(navigator.userAgent)}("undefined"!=typeof window?io:module.exports),function(a,b){function c(){}a.EventEmitter=c,c.prototype.on=function(a,c){this.$events||(this.$events={}),this.$events[a]?b.util.isArray(this.$events[a])?this.$events[a].push(c):this.$events[a]=[this.$events[a],c]:this.$events[a]=c;return this},c.prototype.addListener=c.prototype.on,c.prototype.once=function(a,b){function d(){c.removeListener(a,d),b.apply(this,arguments)}var c=this;d.listener=b,this.on(a,d);return this},c.prototype.removeListener=function(a,c){if(this.$events&&this.$events[a]){var d=this.$events[a];if(b.util.isArray(d)){var e=-1;for(var f=0,g=d.length;f<g;f++)if(d[f]===c||d[f].listener&&d[f].listener===c){e=f;break}if(e<0)return this;d.splice(e,1),d.length||delete this.$events[a]}else(d===c||d.listener&&d.listener===c)&&delete this.$events[a]}return this},c.prototype.removeAllListeners=function(a){this.$events&&this.$events[a]&&(this.$events[a]=null);return this},c.prototype.listeners=function(a){this.$events||(this.$events={}),this.$events[a]||(this.$events[a]=[]),b.util.isArray(this.$events[a])||(this.$events[a]=[this.$events[a]]);return this.$events[a]},c.prototype.emit=function(a){if(!this.$events)return!1;var c=this.$events[a];if(!c)return!1;var d=Array.prototype.slice.call(arguments,1);if("function"==typeof c)c.apply(this,d);else{if(!b.util.isArray(c))return!1;var e=c.slice();for(var f=0,g=e.length;f<g;f++)e[f].apply(this,d)}return!0}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(exports,nativeJSON){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i instanceof Date&&(i=date(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function date(a,b){return isFinite(a.valueOf())?a.getUTCFullYear()+"-"+f(a.getUTCMonth()+1)+"-"+f(a.getUTCDate())+"T"+f(a.getUTCHours())+":"+f(a.getUTCMinutes())+":"+f(a.getUTCSeconds())+"Z":null}function f(a){return a<10?"0"+a:a}"use strict";if(nativeJSON&&nativeJSON.parse)return exports.JSON={parse:nativeJSON.parse,stringify:nativeJSON.stringify};var JSON=exports.JSON={},cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(b&&typeof b!="function"&&(typeof b!="object"||typeof b.length!="number"))throw new Error("JSON.stringify");return str("",{"":a})},JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}("undefined"!=typeof io?io:module.exports,typeof JSON!="undefined"?JSON:undefined),function(a,b){var c=a.parser={},d=c.packets=["disconnect","connect","heartbeat","message","json","event","ack","error","noop"],e=c.reasons=["transport not supported","client not handshaken","unauthorized"],f=c.advice=["reconnect"],g=b.JSON,h=b.util.indexOf;c.encodePacket=function(a){var b=h(d,a.type),c=a.id||"",i=a.endpoint||"",j=a.ack,k=null;switch(a.type){case"error":var l=a.reason?h(e,a.reason):"",m=a.advice?h(f,a.advice):"";if(l!==""||m!=="")k=l+(m!==""?"+"+m:"");break;case"message":a.data!==""&&(k=a.data);break;case"event":var n={name:a.name};a.args&&a.args.length&&(n.args=a.args),k=g.stringify(n);break;case"json":k=g.stringify(a.data);break;case"connect":a.qs&&(k=a.qs);break;case"ack":k=a.ackId+(a.args&&a.args.length?"+"+g.stringify(a.args):"")}var o=[b,c+(j=="data"?"+":""),i];k!==null&&k!==undefined&&o.push(k);return o.join(":")},c.encodePayload=function(a){var b="";if(a.length==1)return a[0];for(var c=0,d=a.length;c<d;c++){var e=a[c];b+="�"+e.length+"�"+a[c]}return b};var i=/^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;c.decodePacket=function(a){var b=a.match(i);if(!b)return{};var c=b[2]||"",a=b[5]||"",h={type:d[b[1]],endpoint:b[4]||""};c&&(h.id=c,b[3]?h.ack="data":h.ack=!0);switch(h.type){case"error":var b=a.split("+");h.reason=e[b[0]]||"",h.advice=f[b[1]]||"";break;case"message":h.data=a||"";break;case"event":try{var j=g.parse(a);h.name=j.name,h.args=j.args}catch(k){}h.args=h.args||[];break;case"json":try{h.data=g.parse(a)}catch(k){}break;case"connect":h.qs=a||"";break;case"ack":var b=a.match(/^([0-9]+)(\+)?(.*)/);if(b){h.ackId=b[1],h.args=[];if(b[3])try{h.args=b[3]?g.parse(b[3]):[]}catch(k){}}break;case"disconnect":case"heartbeat":}return h},c.decodePayload=function(a){if(a.charAt(0)=="�"){var b=[];for(var d=1,e="";d<a.length;d++)a.charAt(d)=="�"?(b.push(c.decodePacket(a.substr(d+1).substr(0,e))),d+=Number(e)+1,e=""):e+=a.charAt(d);return b}return[c.decodePacket(a)]}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a,b){this.socket=a,this.sessid=b}a.Transport=c,b.util.mixin(c,b.EventEmitter),c.prototype.onData=function(a){this.clearCloseTimeout(),this.setCloseTimeout();if(a!==""){var c=b.parser.decodePayload(a);if(c&&c.length)for(var d=0,e=c.length;d<e;d++)this.onPacket(c[d])}return this},c.prototype.onPacket=function(a){if(a.type=="heartbeat")return this.onHeartbeat();a.type=="connect"&&a.endpoint==""&&this.onConnect(),this.socket.onPacket(a);return this},c.prototype.setCloseTimeout=function(){if(!this.closeTimeout){var a=this;this.closeTimeout=setTimeout(function(){a.onDisconnect()},this.socket.closeTimeout)}},c.prototype.onDisconnect=function(){this.close&&this.close(),this.clearTimeouts(),this.socket.onDisconnect();return this},c.prototype.onConnect=function(){this.socket.onConnect();return this},c.prototype.clearCloseTimeout=function(){this.closeTimeout&&(clearTimeout(this.closeTimeout),this.closeTimeout=null)},c.prototype.clearTimeouts=function(){this.clearCloseTimeout(),this.reopenTimeout&&clearTimeout(this.reopenTimeout)},c.prototype.packet=function(a){this.send(b.parser.encodePacket(a))},c.prototype.onHeartbeat=function(a){this.packet({type:"heartbeat"})},c.prototype.onOpen=function(){this.open=!0,this.clearCloseTimeout(),this.socket.onOpen()},c.prototype.onClose=function(){var a=this;this.open=!1,this.setCloseTimeout(),this.socket.onClose()},c.prototype.prepareUrl=function(){var a=this.socket.options;return this.scheme()+"://"+a.host+":"+a.port+"/"+a.resource+"/"+b.protocol+"/"+this.name+"/"+this.sessid}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function d(){}function c(a){this.options={port:80,secure:!1,document:document,resource:"socket.io",transports:b.transports,"connect timeout":1e4,"try multiple transports":!0,reconnect:!0,"reconnection delay":500,"reopen delay":3e3,"max reconnection attempts":10,"sync disconnect on unload":!0,"auto connect":!0},b.util.merge(this.options,a),this.connected=!1,this.open=!1,this.connecting=!1,this.reconnecting=!1,this.namespaces={},this.buffer=[],this.doBuffer=!1;if(this.options["sync disconnect on unload"]&&(!this.isXDomain()||b.util.ua.hasCORS)){var c=this;b.util.on(window,"beforeunload",function(){c.disconnectSync()},!1)}this.options["auto connect"]&&this.connect()}a.Socket=c,b.util.mixin(c,b.EventEmitter),c.prototype.of=function(a){this.namespaces[a]||(this.namespaces[a]=new b.SocketNamespace(this,a),a!==""&&this.namespaces[a].packet({type:"connect"}));return this.namespaces[a]},c.prototype.publish=function(){this.emit.apply(this,arguments);var a;for(var b in this.namespaces)this.namespaces.hasOwnProperty(b)&&(a=this.of(b),a.$emit.apply(a,arguments))},c.prototype.handshake=function(a){function f(b){b instanceof Error?c.onError(b.message):a.apply(null,b.split(":"))}var c=this,e=this.options,g=["http"+(e.secure?"s":"")+":/",e.host+":"+e.port,this.options.resource,b.protocol,"?t="+ +(new Date)].join("/");if(this.isXDomain()){var h=document.getElementsByTagName("script")[0],i=document.createElement("SCRIPT");i.src=g+"&jsonp="+b.j.length,h.parentNode.insertBefore(i,h),b.j.push(function(a){f(a),i.parentNode.removeChild(i)})}else{var j=b.util.request();j.open("GET",g),j.onreadystatechange=function(){j.readyState==4&&(j.onreadystatechange=d,j.status==200?f(j.responseText):!c.reconnecting&&c.onError(j.responseText))},j.send(null)}},c.prototype.getTransport=function(a){var c=a||this.transports,d;for(var e=0,f;f=c[e];e++)if(b.Transport[f]&&b.Transport[f].check(this)&&(!this.isXDomain()||b.Transport[f].xdomainCheck()))return new b.Transport[f](this,this.sessionid);return null},c.prototype.connect=function(a){if(this.connecting)return this;var c=this;this.handshake(function(d,e,f,g){function h(a){c.transport=c.getTransport(a);if(!c.transport)return c.publish("connect_failed");c.connecting=!0,c.publish("connecting",c.transport.name),c.transport.open(),c.options["connect timeout"]&&(c.connectTimeoutTimer=setTimeout(function(){if(!c.connected){c.connecting=!1;if(c.options["try multiple transports"]){c.remainingTransports||(c.remainingTransports=c.transports.slice(0));var a=c.remainingTransports;while(a.length>0&&a.splice(0,1)[0]!=c.transport.name);a.length?h(a):c.publish("connect_failed")}}},c.options["connect timeout"]))}c.sessionid=d,c.closeTimeout=f*1e3,c.heartbeatTimeout=e*1e3,c.transports=b.util.intersect(g.split(","),c.options.transports),h(),c.once("connect",function(){clearTimeout(c.connectTimeoutTimer),a&&typeof a=="function"&&a()})});return this},c.prototype.packet=function(a){this.connected&&!this.doBuffer?this.transport.packet(a):this.buffer.push(a);return this},c.prototype.setBuffer=function(a){this.doBuffer=a,!a&&this.connected&&this.buffer.length&&(this.transport.payload(this.buffer),this.buffer=[])},c.prototype.disconnect=function(){this.connected&&(this.open&&this.of("").packet({type:"disconnect"}),this.onDisconnect("booted"));return this},c.prototype.disconnectSync=function(){var a=b.util.request(),c=this.resource+"/"+b.protocol+"/"+this.sessionid;a.open("GET",c,!0),this.onDisconnect("booted")},c.prototype.isXDomain=function(){var a=window.location.port||80;return this.options.host!==document.domain||this.options.port!=a},c.prototype.onConnect=function(){this.connected=!0,this.connecting=!1,this.doBuffer||this.setBuffer(!1),this.emit("connect")},c.prototype.onOpen=function(){this.open=!0},c.prototype.onClose=function(){this.open=!1},c.prototype.onPacket=function(a){this.of(a.endpoint).onPacket(a)},c.prototype.onError=function(a){a&&a.advice&&a.advice==="reconnect"&&this.connected&&(this.disconnect(),this.reconnect()),this.publish("error",a&&a.reason?a.reason:a)},c.prototype.onDisconnect=function(a){var b=this.connected;this.connected=!1,this.connecting=!1,this.open=!1,b&&(this.transport.close(),this.transport.clearTimeouts(),this.publish("disconnect",a),"booted"!=a&&this.options.reconnect&&!this.reconnecting&&this.reconnect())},c.prototype.reconnect=function(){function e(){if(!!a.reconnecting){if(a.connected)return d();if(a.connecting&&a.reconnecting)return a.reconnectionTimer=setTimeout(e,1e3);a.reconnectionAttempts++>=b?a.redoTransports?(a.publish("reconnect_failed"),d()):(a.on("connect_failed",e),a.options["try multiple transports"]=!0,a.transport=a.getTransport(),a.redoTransports=!0,a.connect()):(a.reconnectionDelay*=2,a.connect(),a.publish("reconnecting",a.reconnectionDelay,a.reconnectionAttempts),a.reconnectionTimer=setTimeout(e,a.reconnectionDelay))}}function d(){a.connected&&a.publish("reconnect",a.transport.name,a.reconnectionAttempts),a.removeListener("connect_failed",e),a.removeListener("connect",e),a.reconnecting=!1,delete a.reconnectionAttempts,delete a.reconnectionDelay,delete a.reconnectionTimer,delete a.redoTransports,a.options["try multiple transports"]=c}this.reconnecting=!0,this.reconnectionAttempts=0,this.reconnectionDelay=this.options["reconnection delay"];var a=this,b=this.options["max reconnection attempts"],c=this.options["try multiple transports"];this.options["try multiple transports"]=!1,this.reconnectionTimer=setTimeout(e,this.reconnectionDelay),this.on("connect",e)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function d(a,b){this.namespace=a,this.name=b}function c(a,b){this.socket=a,this.name=b||"",this.flags={},this.json=new d(this,"json"),this.ackPackets=0,this.acks={}}a.SocketNamespace=c,b.util.mixin(c,b.EventEmitter),c.prototype.$emit=b.EventEmitter.prototype.emit,c.prototype.of=function(){return this.socket.of.apply(this.socket,arguments)},c.prototype.packet=function(a){a.endpoint=this.name,this.socket.packet(a),this.flags={};return this},c.prototype.send=function(a,b){var c={type:this.flags.json?"json":"message",data:a};"function"==typeof b&&(c.id=++this.ackPackets,c.ack=!0,this.acks[c.id]=b);return this.packet(c)},c.prototype.emit=function(a){var b=Array.prototype.slice.call(arguments,1),c=b[b.length-1],d={type:"event",name:a};"function"==typeof c&&(d.id=++this.ackPackets,d.ack="data",this.acks[d.id]=c,b=b.slice(0,b.length-1)),d.args=b;return this.packet(d)},c.prototype.disconnect=function(){this.name===""?this.socket.disconnect():(this.packet({type:"disconnect"}),this.$emit("disconnect"));return this},c.prototype.onPacket=function(a){function d(){c.packet({type:"ack",args:b.util.toArray(arguments),ackId:a.id})}var c=this;switch(a.type){case"connect":this.$emit("connect");break;case"disconnect":this.name===""?this.socket.onDisconnect(a.reason||"booted"):this.$emit("disconnect",a.reason);break;case"message":case"json":var e=["message",a.data];a.ack=="data"?e.push(d):a.ack&&this.packet({type:"ack",ackId:a.id}),this.$emit.apply(this,e);break;case"event":var e=[a.name].concat(a.args);a.ack=="data"&&e.push(d),this.$emit.apply(this,e);break;case"ack":this.acks[a.ackId]&&(this.acks[a.ackId].apply(this,a.args),delete this.acks[a.ackId]);break;case"error":a.advice?this.socket.onError(a):a.reason=="unauthorized"?this.$emit("connect_failed",a.reason):this.$emit("error",a.reason)}},d.prototype.send=function(){this.namespace.flags[this.name]=!0,this.namespace.send.apply(this.namespace,arguments)},d.prototype.emit=function(){this.namespace.flags[this.name]=!0,this.namespace.emit.apply(this.namespace,arguments)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a){b.Transport.apply(this,arguments)}a.websocket=c,b.util.inherit(c,b.Transport),c.prototype.name="websocket",c.prototype.open=function(){this.websocket=new WebSocket(this.prepareUrl());var a=this;this.websocket.onopen=function(){a.onOpen(),a.socket.setBuffer(!1)},this.websocket.onmessage=function(b){a.onData(b.data)},this.websocket.onclose=function(){a.onClose(),a.socket.setBuffer(!0)},this.websocket.onerror=function(b){a.onError(b)};return this},c.prototype.send=function(a){this.websocket.send(a);return this},c.prototype.payload=function(a){for(var b=0,c=a.length;b<c;b++)this.packet(a[b]);return this},c.prototype.close=function(){this.websocket.close();return this},c.prototype.onError=function(a){this.socket.onError(a)},c.prototype.scheme=function(){return this.socket.options.secure?"wss":"ws"},c.check=function(){return"WebSocket"in window&&!("__addTask"in WebSocket)},c.xdomainCheck=function(){return!0},b.transports.push("websocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(){b.Transport.websocket.apply(this,arguments)}a.flashsocket=c,b.util.inherit(c,b.Transport.websocket),c.prototype.name="flashsocket",c.prototype.open=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.open.apply(a,c)});return this},c.prototype.send=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.send.apply(a,c)});return this},c.prototype.close=function(){WebSocket.__tasks.length=0,b.Transport.websocket.prototype.close.call(this);return this},c.check=function(a){if(typeof WebSocket=="undefined"||!("__initialize"in WebSocket)||!swfobject)return!1;var b=swfobject.getFlashPlayerVersion().major>=10,d=a.options,e=["http"+(d.secure?"s":"")+":/",d.host+":"+d.port,d.resource,"static/flashsocket","WebSocketMain"+(a.isXDomain()?"Insecure":"")+".swf"];b&&!c.loaded&&(typeof WEB_SOCKET_SWF_LOCATION=="undefined"&&(WEB_SOCKET_SWF_LOCATION=e.join("/")),WebSocket.__initialize(),c.loaded=!0);return b},c.xdomainCheck=function(){return!0},typeof window!="undefined"&&(WEB_SOCKET_DISABLE_AUTO_INITIALIZATION=!0),b.transports.push("flashsocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports);var swfobject=function(){function V(b){var c=/[\\\"<>\.;]/,d=c.exec(b)!=null;return d&&typeof encodeURIComponent!=a?encodeURIComponent(b):b}function U(a,b){if(!!x){var c=b?"visible":"hidden";t&&P(a)?P(a).style.visibility=c:T("#"+a,"visibility:"+c)}}function T(c,d,e,f){if(!y.ie||!y.mac){var g=i.getElementsByTagName("head")[0];if(!g)return;var h=e&&typeof e=="string"?e:"screen";f&&(v=null,w=null);if(!v||w!=h){var j=Q("style");j.setAttribute("type","text/css"),j.setAttribute("media",h),v=g.appendChild(j),y.ie&&y.win&&typeof i.styleSheets!=a&&i.styleSheets.length>0&&(v=i.styleSheets[i.styleSheets.length-1]),w=h}y.ie&&y.win?v&&typeof v.addRule==b&&v.addRule(c,d):v&&typeof i.createTextNode!=a&&v.appendChild(i.createTextNode(c+" {"+d+"}"))}}function S(a){var b=y.pv,c=a.split(".");c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0;return b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function R(a,b,c){a.attachEvent(b,c),o[o.length]=[a,b,c]}function Q(a){return i.createElement(a)}function P(a){var b=null;try{b=i.getElementById(a)}catch(c){}return b}function O(a){var b=P(a);if(b){for(var c in b)typeof b[c]=="function"&&(b[c]=null);b.parentNode.removeChild(b)}}function N(a){var b=P(a);b&&b.nodeName=="OBJECT"&&(y.ie&&y.win?(b.style.display="none",function(){b.readyState==4?O(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function M(a,b,c){var d=Q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function L(c,d,f){var g,h=P(f);if(y.wk&&y.wk<312)return g;if(h){typeof c.id==a&&(c.id=f);if(y.ie&&y.win){var i="";for(var j in c)c[j]!=Object.prototype[j]&&(j.toLowerCase()=="data"?d.movie=c[j]:j.toLowerCase()=="styleclass"?i+=' class="'+c[j]+'"':j.toLowerCase()!="classid"&&(i+=" "+j+'="'+c[j]+'"'));var k="";for(var l in d)d[l]!=Object.prototype[l]&&(k+='<param name="'+l+'" value="'+d[l]+'" />');h.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+i+">"+k+"</object>",n[n.length]=c.id,g=P(c.id)}else{var m=Q(b);m.setAttribute("type",e);for(var o in c)c[o]!=Object.prototype[o]&&(o.toLowerCase()=="styleclass"?m.setAttribute("class",c[o]):o.toLowerCase()!="classid"&&m.setAttribute(o,c[o]));for(var p in d)d[p]!=Object.prototype[p]&&p.toLowerCase()!="movie"&&M(m,p,d[p]);h.parentNode.replaceChild(m,h),g=m}}return g}function K(a){var c=Q("div");if(y.win&&y.ie)c.innerHTML=a.innerHTML;else{var d=a.getElementsByTagName(b)[0];if(d){var e=d.childNodes;if(e){var f=e.length;for(var g=0;g<f;g++)(e[g].nodeType!=1||e[g].nodeName!="PARAM")&&e[g].nodeType!=8&&c.appendChild(e[g].cloneNode(!0))}}}return c}function J(a){if(y.ie&&y.win&&a.readyState!=4){var b=Q("div");a.parentNode.insertBefore(b,a),b.parentNode.replaceChild(K(a),b),a.style.display="none",function(){a.readyState==4?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)}()}else a.parentNode.replaceChild(K(a),a)}function I(b,c,d,e){u=!0,r=e||null,s={success:!1,id:d};var g=P(d);if(g){g.nodeName=="OBJECT"?(p=K(g),q=null):(p=g,q=d),b.id=f;if(typeof b.width==a||!/%$/.test(b.width)&&parseInt(b.width,10)<310)b.width="310";if(typeof b.height==a||!/%$/.test(b.height)&&parseInt(b.height,10)<137)b.height="137";i.title=i.title.slice(0,47)+" - Flash Player Installation";var j=y.ie&&y.win?"ActiveX":"PlugIn",k="MMredirectURL="+h.location.toString().replace(/&/g,"%26")+"&MMplayerType="+j+"&MMdoctitle="+i.title;typeof c.flashvars!=a?c.flashvars+="&"+k:c.flashvars=k;if(y.ie&&y.win&&g.readyState!=4){var l=Q("div");d+="SWFObjectNew",l.setAttribute("id",d),g.parentNode.insertBefore(l,g),g.style.display="none",function(){g.readyState==4?g.parentNode.removeChild(g):setTimeout(arguments.callee,10)}()}L(b,c,d)}}function H(){return!u&&S("6.0.65")&&(y.win||y.mac)&&!(y.wk&&y.wk<312)}function G(c){var d=null,e=P(c);if(e&&e.nodeName=="OBJECT")if(typeof e.SetVariable!=a)d=e;else{var f=e.getElementsByTagName(b)[0];f&&(d=f)}return d}function F(){var b=m.length;if(b>0)for(var c=0;c<b;c++){var d=m[c].id,e=m[c].callbackFn,f={success:!1,id:d};if(y.pv[0]>0){var g=P(d);if(g)if(S(m[c].swfVersion)&&!(y.wk&&y.wk<312))U(d,!0),e&&(f.success=!0,f.ref=G(d),e(f));else if(m[c].expressInstall&&H()){var h={};h.data=m[c].expressInstall,h.width=g.getAttribute("width")||"0",h.height=g.getAttribute("height")||"0",g.getAttribute("class")&&(h.styleclass=g.getAttribute("class")),g.getAttribute("align")&&(h.align=g.getAttribute("align"));var i={},j=g.getElementsByTagName("param"),k=j.length;for(var l=0;l<k;l++)j[l].getAttribute("name").toLowerCase()!="movie"&&(i[j[l].getAttribute("name")]=j[l].getAttribute("value"));I(h,i,d,e)}else J(g),e&&e(f)}else{U(d,!0);if(e){var n=G(d);n&&typeof n.SetVariable!=a&&(f.success=!0,f.ref=n),e(f)}}}}function E(){var c=i.getElementsByTagName("body")[0],d=Q(b);d.setAttribute("type",e);var f=c.appendChild(d);if(f){var g=0;(function(){if(typeof f.GetVariable!=a){var b=f.GetVariable("$version");b&&(b=b.split(" ")[1].split(","),y.pv=[parseInt(b[0],10),parseInt(b[1],10),parseInt(b[2],10)])}else if(g<10){g++,setTimeout(arguments.callee,10);return}c.removeChild(d),f=null,F()})()}else F()}function D(){k?E():F()}function C(b){if(typeof h.addEventListener!=a)h.addEventListener("load",b,!1);else if(typeof i.addEventListener!=a)i.addEventListener("load",b,!1);else if(typeof h.attachEvent!=a)R(h,"onload",b);else if(typeof h.onload=="function"){var c=h.onload;h.onload=function(){c(),b()}}else h.onload=b}function B(a){t?a():l[l.length]=a}function A(){if(!t){try{var a=i.getElementsByTagName("body")[0].appendChild(Q("span"));a.parentNode.removeChild(a)}catch(b){return}t=!0;var c=l.length;for(var d=0;d<c;d++)l[d]()}}var a="undefined",b="object",c="Shockwave Flash",d="ShockwaveFlash.ShockwaveFlash",e="application/x-shockwave-flash",f="SWFObjectExprInst",g="onreadystatechange",h=window,i=document,j=navigator,k=!1,l=[D],m=[],n=[],o=[],p,q,r,s,t=!1,u=!1,v,w,x=!0,y=function(){var f=typeof i.getElementById!=a&&typeof i.getElementsByTagName!=a&&typeof i.createElement!=a,g=j.userAgent.toLowerCase(),l=j.platform.toLowerCase(),m=l?/win/.test(l):/win/.test(g),n=l?/mac/.test(l):/mac/.test(g),o=/webkit/.test(g)?parseFloat(g.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,p=!1,q=[0,0,0],r=null;if(typeof j.plugins!=a&&typeof j.plugins[c]==b)r=j.plugins[c].description,r&&(typeof j.mimeTypes==a||!j.mimeTypes[e]||!!j.mimeTypes[e].enabledPlugin)&&(k=!0,p=!1,r=r.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),q[0]=parseInt(r.replace(/^(.*)\..*$/,"$1"),10),q[1]=parseInt(r.replace(/^.*\.(.*)\s.*$/,"$1"),10),q[2]=/[a-zA-Z]/.test(r)?parseInt(r.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof h.ActiveXObject!=a)try{var s=new ActiveXObject(d);s&&(r=s.GetVariable("$version"),r&&(p=!0,r=r.split(" ")[1].split(","),q=[parseInt(r[0],10),parseInt(r[1],10),parseInt(r[2],10)]))}catch(t){}return{w3:f,pv:q,wk:o,ie:p,win:m,mac:n}}(),z=function(){!y.w3||((typeof i.readyState!=a&&i.readyState=="complete"||typeof i.readyState==a&&(i.getElementsByTagName("body")[0]||i.body))&&A(),t||(typeof i.addEventListener!=a&&i.addEventListener("DOMContentLoaded",A,!1),y.ie&&y.win&&(i.attachEvent(g,function(){i.readyState=="complete"&&(i.detachEvent(g,arguments.callee),A())}),h==top&&function(){if(!t){try{i.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee,0);return}A()}}()),y.wk&&function(){if(!t){if(!/loaded|complete/.test(i.readyState)){setTimeout(arguments.callee,0);return}A()}}(),C(A)))}(),W=function(){y.ie&&y.win&&window.attachEvent("onunload",function(){var a=o.length;for(var b=0;b<a;b++)o[b][0].detachEvent(o[b][1],o[b][2]);var c=n.length;for(var d=0;d<c;d++)N(n[d]);for(var e in y)y[e]=null;y=null;for(var f in swfobject)swfobject[f]=null;swfobject=null})}();return{registerObject:function(a,b,c,d){if(y.w3&&a&&b){var e={};e.id=a,e.swfVersion=b,e.expressInstall=c,e.callbackFn=d,m[m.length]=e,U(a,!1)}else d&&d({success:!1,id:a})},getObjectById:function(a){if(y.w3)return G(a)},embedSWF:function(c,d,e,f,g,h,i,j,k,l){var m={success:!1,id:d};y.w3&&!(y.wk&&y.wk<312)&&c&&d&&e&&f&&g?(U(d,!1),B(function(){e+="",f+="";var n={};if(k&&typeof k===b)for(var o in k)n[o]=k[o];n.data=c,n.width=e,n.height=f;var p={};if(j&&typeof j===b)for(var q in j)p[q]=j[q];if(i&&typeof i===b)for(var r in i)typeof p.flashvars!=a?p.flashvars+="&"+r+"="+i[r]:p.flashvars=r+"="+i[r];if(S(g)){var s=L(n,p,d);n.id==d&&U(d,!0),m.success=!0,m.ref=s}else{if(h&&H()){n.data=h,I(n,p,d,l);return}U(d,!0)}l&&l(m)})):l&&l(m)},switchOffAutoHideShow:function(){x=!1},ua:y,getFlashPlayerVersion:function(){return{major:y.pv[0],minor:y.pv[1],release:y.pv[2]}},hasFlashPlayerVersion:S,createSWF:function(a,b,c){return y.w3?L(a,b,c):undefined},showExpressInstall:function(a,b,c,d){y.w3&&H()&&I(a,b,c,d)},removeSWF:function(a){y.w3&&N(a)},createCSS:function(a,b,c,d){y.w3&&T(a,b,c,d)},addDomLoadEvent:B,addLoadEvent:C,getQueryParamValue:function(a){var b=i.location.search||i.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(a==null)return V(b);var c=b.split("&");for(var d=0;d<c.length;d++)if(c[d].substring(0,c[d].indexOf("="))==a)return V(c[d].substring(c[d].indexOf("=")+1))}return""},expressInstallCallback:function(){if(u){var a=P(f);a&&p&&(a.parentNode.replaceChild(p,a),q&&(U(q,!0),y.ie&&y.win&&(p.style.display="block")),r&&r(s)),u=!1}}}}();(function(){if(!window.WebSocket){var a=window.console;if(!a||!a.log||!a.error)a={log:function(){},error:function(){}};if(!swfobject.hasFlashPlayerVersion("10.0.0")){a.error("Flash Player >= 10.0.0 is required.");return}location.protocol=="file:"&&a.error("WARNING: web-socket-js doesn't work in file:///... URL unless you set Flash Security Settings properly. Open the page via Web server i.e. http://..."),WebSocket=function(a,b,c,d,e){var f=this;f.__id=WebSocket.__nextId++,WebSocket.__instances[f.__id]=f,f.readyState=WebSocket.CONNECTING,f.bufferedAmount=0,f.__events={},b?typeof b=="string"&&(b=[b]):b=[],setTimeout(function(){WebSocket.__addTask(function(){WebSocket.__flash.create(f.__id,a,b,c||null,d||0,e||null)})},0)},WebSocket.prototype.send=function(a){if(this.readyState==WebSocket.CONNECTING)throw"INVALID_STATE_ERR: Web Socket connection has not been established";var b=WebSocket.__flash.send(this.__id,encodeURIComponent(a));if(b<0)return!0;this.bufferedAmount+=b;return!1},WebSocket.prototype.close=function(){this.readyState!=WebSocket.CLOSED&&this.readyState!=WebSocket.CLOSING&&(this.readyState=WebSocket.CLOSING,WebSocket.__flash.close(this.__id))},WebSocket.prototype.addEventListener=function(a,b,c){a in this.__events||(this.__events[a]=[]),this.__events[a].push(b)},WebSocket.prototype.removeEventListener=function(a,b,c){if(a in this.__events){var d=this.__events[a];for(var e=d.length-1;e>=0;--e)if(d[e]===b){d.splice(e,1);break}}},WebSocket.prototype.dispatchEvent=function(a){var b=this.__events[a.type]||[];for(var c=0;c<b.length;++c)b[c](a);var d=this["on"+a.type];d&&d(a)},WebSocket.prototype.__handleEvent=function(a){"readyState"in a&&(this.readyState=a.readyState),"protocol"in a&&(this.protocol=a.protocol);var b;if(a.type=="open"||a.type=="error")b=this.__createSimpleEvent(a.type);else if(a.type=="close")b=this.__createSimpleEvent("close");else{if(a.type!="message")throw"unknown event type: "+a.type;var c=decodeURIComponent(a.message);b=this.__createMessageEvent("message",c)}this.dispatchEvent(b)},WebSocket.prototype.__createSimpleEvent=function(a){if(document.createEvent&&window.Event){var b=document.createEvent("Event");b.initEvent(a,!1,!1);return b}return{type:a,bubbles:!1,cancelable:!1}},WebSocket.prototype.__createMessageEvent=function(a,b){if(document.createEvent&&window.MessageEvent&&!window.opera){var c=document.createEvent("MessageEvent");c.initMessageEvent("message",!1,!1,b,null,null,window,null);return c}return{type:a,data:b,bubbles:!1,cancelable:!1}},WebSocket.CONNECTING=0,WebSocket.OPEN=1,WebSocket.CLOSING=2,WebSocket.CLOSED=3,WebSocket.__flash=null,WebSocket.__instances={},WebSocket.__tasks=[],WebSocket.__nextId=0,WebSocket.loadFlashPolicyFile=function(a){WebSocket.__addTask(function(){WebSocket.__flash.loadManualPolicyFile(a)})},WebSocket.__initialize=function(){if(!WebSocket.__flash){WebSocket.__swfLocation&&(window.WEB_SOCKET_SWF_LOCATION=WebSocket.__swfLocation);if(!window.WEB_SOCKET_SWF_LOCATION){a.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");return}var b=document.createElement("div");b.id="webSocketContainer",b.style.position="absolute",WebSocket.__isFlashLite()?(b.style.left="0px",b.style.top="0px"):(b.style.left="-100px",b.style.top="-100px");var c=document.createElement("div");c.id="webSocketFlash",b.appendChild(c),document.body.appendChild(b),swfobject.embedSWF(WEB_SOCKET_SWF_LOCATION,"webSocketFlash","1","1","10.0.0",null,null,{hasPriority:!0,swliveconnect:!0,allowScriptAccess:"always"},null,function(b){b.success||a.error("[WebSocket] swfobject.embedSWF failed")})}},WebSocket.__onFlashInitialized=function(){setTimeout(function(){WebSocket.__flash=document.getElementById("webSocketFlash"),WebSocket.__flash.setCallerUrl(location.href),WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);for(var a=0;a<WebSocket.__tasks.length;++a)WebSocket.__tasks[a]();WebSocket.__tasks=[]},0)},WebSocket.__onFlashEvent=function(){setTimeout(function(){try{var b=WebSocket.__flash.receiveEvents();for(var c=0;c<b.length;++c)WebSocket.__instances[b[c].webSocketId].__handleEvent(b[c])}catch(d){a.error(d)}},0);return!0},WebSocket.__log=function(b){a.log(decodeURIComponent(b))},WebSocket.__error=function(b){a.error(decodeURIComponent(b))},WebSocket.__addTask=function(a){WebSocket.__flash?a():WebSocket.__tasks.push(a)},WebSocket.__isFlashLite=function(){if(!window.navigator||!window.navigator.mimeTypes)return!1;var a=window.navigator.mimeTypes["application/x-shockwave-flash"];if(!a||!a.enabledPlugin||!a.enabledPlugin.filename)return!1;return a.enabledPlugin.filename.match(/flashlite/i)?!0:!1},window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION||(window.addEventListener?window.addEventListener("load",function(){WebSocket.__initialize()},!1):window.attachEvent("onload",function(){WebSocket.__initialize()}))}})(),function(a,b){function d(){}function c(a){!a||(b.Transport.apply(this,arguments),this.sendBuffer=[])}a.XHR=c,b.util.inherit(c,b.Transport),c.prototype.open=function(){this.socket.setBuffer(!1),this.onOpen(),this.get(),this.setCloseTimeout();return this},c.prototype.payload=function(a){var c=[];for(var d=0,e=a.length;d<e;d++)c.push(b.parser.encodePacket(a[d]));this.send(b.parser.encodePayload(c))},c.prototype.send=function(a){this.post(a);return this},c.prototype.post=function(a){function e(){this.onload=d,b.socket.setBuffer(!1)}function c(){this.readyState==4&&(this.onreadystatechange=d,b.posting=!1,this.status==200?b.socket.setBuffer(!1):b.onClose())}var b=this;this.socket.setBuffer(!0),this.sendXHR=this.request("POST"),window.XDomainRequest&&this.sendXHR instanceof XDomainRequest?this.sendXHR.onload=this.sendXHR.onerror=e:this.sendXHR.onreadystatechange=c,this.sendXHR.send(a)},c.prototype.close=function(){this.onClose();return this},c.prototype.request=function(a){var c=b.util.request(this.socket.isXDomain());c.open(a||"GET",this.prepareUrl()+"?t"+ +(new Date));if(a=="POST")try{c.setRequestHeader?c.setRequestHeader("Content-type","text/plain;charset=UTF-8"):c.contentType="text/plain"}catch(d){}return c},c.prototype.scheme=function(){return this.socket.options.secure?"https":"http"},c.check=function(a,c){try{if(b.util.request(c))return!0}catch(d){}return!1},c.xdomainCheck=function(){return c.check(null,!0)}}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a){b.Transport.XHR.apply(this,arguments)}a.htmlfile=c,b.util.inherit(c,b.Transport.XHR),c.prototype.name="htmlfile",c.prototype.get=function(){this.doc=new ActiveXObject("htmlfile"),this.doc.open(),this.doc.write("<html></html>"),this.doc.close(),this.doc.parentWindow.s=this;var a=this.doc.createElement("div");a.className="socketio",this.doc.body.appendChild(a),this.iframe=this.doc.createElement("iframe"),a.appendChild(this.iframe),this.iframe.src=this.prepareUrl()+"/?t="+ +(new Date);var c=this;b.util.on(window,"unload",function(){c.destroy()})},c.prototype._=function(a,b){this.onData(a);try{var c=b.getElementsByTagName("script")[0];c.parentNode.removeChild(c)}catch(d){}},c.prototype.destroy=function(){if(this.iframe){try{this.iframe.src="about:blank"}catch(a){}this.doc=null,this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,CollectGarbage()}},c.prototype.close=function(){this.destroy();return b.Transport.XHR.prototype.close.call(this)},c.check=function(){if("ActiveXObject"in window)try{var a=new ActiveXObject("htmlfile");return a&&b.Transport.XHR.check()}catch(c){}return!1},c.xdomainCheck=function(){return!1},b.transports.push("htmlfile")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function d(){}function c(){b.Transport.XHR.apply(this,arguments)}a["xhr-polling"]=c,b.util.inherit(c,b.Transport.XHR),c.prototype.name="xhr-polling",c.prototype.open=function(){var a=this;b.util.defer(function(){b.Transport.XHR.prototype.open.call(a)});return!1},c.prototype.get=function(){function c(){this.onload=d,a.onData(this.responseText),a.get()}function b(){this.readyState==4&&(this.onreadystatechange=d,this.status==200?(a.onData(this.responseText),a.get()):a.onClose())}if(!!this.open){var a=this;this.xhr=this.request(),window.XDomainRequest&&this.xhr instanceof XDomainRequest?this.xhr.onload=this.xhr.onerror=c:this.xhr.onreadystatechange=b,this.xhr.send(null)}},c.prototype.onClose=function(){b.Transport.XHR.prototype.onClose.call(this);if(this.xhr){this.xhr.onreadystatechange=this.xhr.onload=d;try{this.xhr.abort()}catch(a){}this.xhr=null}},b.transports.push("xhr-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a){b.Transport["xhr-polling"].apply(this,arguments),this.index=b.j.length;var c=this;b.j.push(function(a){c._(a)})}a["jsonp-polling"]=c,b.util.inherit(c,b.Transport["xhr-polling"]),c.prototype.name="jsonp-polling",c.prototype.post=function(a){function h(){b.iframe&&b.form.removeChild(b.iframe);try{f=document.createElement('<iframe name="'+b.iframeId+'">')}catch(a){f=document.createElement("iframe"),f.name=b.iframeId}f.id=b.iframeId,b.form.appendChild(f),b.iframe=f}function g(){h(),b.socket.setBuffer(!1)}var b=this;if(!this.form){var c=document.createElement("FORM"),d=document.createElement("TEXTAREA"),e=this.iframeId="socketio_iframe_"+this.index,f;c.className="socketio",c.style.position="absolute",c.style.top="-1000px",c.style.left="-1000px",c.target=e,c.method="POST",d.name="d",c.appendChild(d),document.body.appendChild(c),this.form=c,this.area=d}this.form.action=this.prepareUrl()+"?t="+ +(new Date)+"&i="+this.index,h(),this.area.value=a;try{this.form.submit()}catch(i){}this.iframe.attachEvent?f.onreadystatechange=function(){b.iframe.readyState=="complete"&&g()}:this.iframe.onload=g},c.prototype.get=function(){var a=this,b=document.createElement("SCRIPT");this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),b.async=!0,b.src=this.prepareUrl()+"/?t="+ +(new Date)+"&i="+this.index,b.onerror=function(){a.onClose()};var c=document.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c),this.script=b},c.prototype._=function(a){this.onData(a),this.open&&this.get();return this},c.check=function(){return!0},c.xdomainCheck=function(){return!0},b.transports.push("jsonp-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports)
\ No newline at end of file
+/*! Socket.IO.min.js build:0.8.4, production. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */
+(function(a){var b=a;b.version="0.8.4",b.protocol=1,b.transports=[],b.j=[],b.sockets={},b.connect=function(a,c){var d=b.util.parseUri(a),e,f;"undefined"!=typeof document&&(d.protocol=d.protocol||document.location.protocol.slice(0,-1),d.host=d.host||document.domain,d.port=d.port||document.location.port),e=b.util.uniqueUri(d);var g={host:d.host,secure:"https"==d.protocol,port:d.port||("https"==d.protocol?443:80),query:d.query||""};b.util.merge(g,c);if(g["force new connection"]||!b.sockets[e])f=new b.Socket(g);!g["force new connection"]&&f&&(b.sockets[e]=f),f=f||b.sockets[e];return f.of(d.path.length>1?d.path:"")}})("object"==typeof module?module.exports:window.io={}),function(a,b){var c=a.util={},d=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,e=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];c.parseUri=function(a){var b=d.exec(a||""),c={},f=14;while(f--)c[e[f]]=b[f]||"";return c},c.uniqueUri=function(a){var c=a.protocol,d=a.host,e=a.port;"document"in b?(d=d||document.domain,e=e||(c=="https"&&document.location.protocol!=="https:"?443:document.location.port)):(d=d||"localhost",!e&&c=="https"&&(e=443));return(c||"http")+"://"+d+":"+(e||80)},c.query=function(a,b){var d=c.chunkQuery(a||""),e=[];c.merge(d,c.chunkQuery(b||""));for(var f in d)d.hasOwnProperty(f)&&e.push(f+"="+d[f]);return e.length?"?"+e.join("&"):""},c.chunkQuery=function(a){var b={},c=a.split("&"),d=0,e=c.length,f;for(;d<e;++d)f=c[d].split("="),f[0]&&(b[f[0]]=decodeURIComponent(f[1]));return b};var f=!1;c.load=function(a){if("document"in b&&document.readyState==="complete"||f)return a();c.on(b,"load",a,!1)},c.on=function(a,b,c,d){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener&&a.addEventListener(b,c,d)},c.request=function(a){if("undefined"!=typeof window){if(a&&window.XDomainRequest)return new XDomainRequest;if(window.XMLHttpRequest&&(!a||c.ua.hasCORS))return new XMLHttpRequest;if(!a)try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}return null},"undefined"!=typeof window&&c.load(function(){f=!0}),c.defer=function(a){if(!c.ua.webkit)return a();c.load(function(){setTimeout(a,100)})},c.merge=function g(a,b,d,e){var f=e||[],g=typeof d=="undefined"?2:d,h;for(h in b)b.hasOwnProperty(h)&&c.indexOf(f,h)<0&&(typeof a[h]!="object"||!g?(a[h]=b[h],f.push(b[h])):c.merge(a[h],b[h],g-1,f));return a},c.mixin=function(a,b){c.merge(a.prototype,b.prototype)},c.inherit=function(a,b){function c(){}c.prototype=b.prototype,a.prototype=new c},c.isArray=Array.isArray||function(a){return Object.prototype.toString.call(a)==="[object Array]"},c.intersect=function(a,b){var d=[],e=a.length>b.length?a:b,f=a.length>b.length?b:a;for(var g=0,h=f.length;g<h;g++)~c.indexOf(e,f[g])&&d.push(f[g]);return d},c.indexOf=function(a,b,c){if(Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b,c);for(var d=a.length,c=c<0?c+d<0?0:c+d:c||0;c<d&&a[c]!==b;c++);return d<=c?-1:c},c.toArray=function(a){var b=[];for(var c=0,d=a.length;c<d;c++)b.push(a[c]);return b},c.ua={},c.ua.hasCORS="undefined"!=typeof window&&window.XMLHttpRequest&&function(){try{var a=new XMLHttpRequest}catch(b){return!1}return a.withCredentials!=undefined}(),c.ua.webkit="undefined"!=typeof navigator&&/webkit/i.test(navigator.userAgent)}("undefined"!=typeof window?io:module.exports,this),function(a,b){function c(){}a.EventEmitter=c,c.prototype.on=function(a,c){this.$events||(this.$events={}),this.$events[a]?b.util.isArray(this.$events[a])?this.$events[a].push(c):this.$events[a]=[this.$events[a],c]:this.$events[a]=c;return this},c.prototype.addListener=c.prototype.on,c.prototype.once=function(a,b){function d(){c.removeListener(a,d),b.apply(this,arguments)}var c=this;d.listener=b,this.on(a,d);return this},c.prototype.removeListener=function(a,c){if(this.$events&&this.$events[a]){var d=this.$events[a];if(b.util.isArray(d)){var e=-1;for(var f=0,g=d.length;f<g;f++)if(d[f]===c||d[f].listener&&d[f].listener===c){e=f;break}if(e<0)return this;d.splice(e,1),d.length||delete this.$events[a]}else(d===c||d.listener&&d.listener===c)&&delete this.$events[a]}return this},c.prototype.removeAllListeners=function(a){this.$events&&this.$events[a]&&(this.$events[a]=null);return this},c.prototype.listeners=function(a){this.$events||(this.$events={}),this.$events[a]||(this.$events[a]=[]),b.util.isArray(this.$events[a])||(this.$events[a]=[this.$events[a]]);return this.$events[a]},c.prototype.emit=function(a){if(!this.$events)return!1;var c=this.$events[a];if(!c)return!1;var d=Array.prototype.slice.call(arguments,1);if("function"==typeof c)c.apply(this,d);else{if(!b.util.isArray(c))return!1;var e=c.slice();for(var f=0,g=e.length;f<g;f++)e[f].apply(this,d)}return!0}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(exports,nativeJSON){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i instanceof Date&&(i=date(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function date(a,b){return isFinite(a.valueOf())?a.getUTCFullYear()+"-"+f(a.getUTCMonth()+1)+"-"+f(a.getUTCDate())+"T"+f(a.getUTCHours())+":"+f(a.getUTCMinutes())+":"+f(a.getUTCSeconds())+"Z":null}function f(a){return a<10?"0"+a:a}"use strict";if(nativeJSON&&nativeJSON.parse)return exports.JSON={parse:nativeJSON.parse,stringify:nativeJSON.stringify};var JSON=exports.JSON={},cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")},JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}("undefined"!=typeof io?io:module.exports,typeof JSON!="undefined"?JSON:undefined),function(a,b){var c=a.parser={},d=c.packets=["disconnect","connect","heartbeat","message","json","event","ack","error","noop"],e=c.reasons=["transport not supported","client not handshaken","unauthorized"],f=c.advice=["reconnect"],g=b.JSON,h=b.util.indexOf;c.encodePacket=function(a){var b=h(d,a.type),c=a.id||"",i=a.endpoint||"",j=a.ack,k=null;switch(a.type){case"error":var l=a.reason?h(e,a.reason):"",m=a.advice?h(f,a.advice):"";if(l!==""||m!=="")k=l+(m!==""?"+"+m:"");break;case"message":a.data!==""&&(k=a.data);break;case"event":var n={name:a.name};a.args&&a.args.length&&(n.args=a.args),k=g.stringify(n);break;case"json":k=g.stringify(a.data);break;case"connect":a.qs&&(k=a.qs);break;case"ack":k=a.ackId+(a.args&&a.args.length?"+"+g.stringify(a.args):"")}var o=[b,c+(j=="data"?"+":""),i];k!==null&&k!==undefined&&o.push(k);return o.join(":")},c.encodePayload=function(a){var b="";if(a.length==1)return a[0];for(var c=0,d=a.length;c<d;c++){var e=a[c];b+="\ufffd"+e.length+"\ufffd"+a[c]}return b};var i=/([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;c.decodePacket=function(a){var b=a.match(i);if(!b)return{};var c=b[2]||"",a=b[5]||"",h={type:d[b[1]],endpoint:b[4]||""};c&&(h.id=c,b[3]?h.ack="data":h.ack=!0);switch(h.type){case"error":var b=a.split("+");h.reason=e[b[0]]||"",h.advice=f[b[1]]||"";break;case"message":h.data=a||"";break;case"event":try{var j=g.parse(a);h.name=j.name,h.args=j.args}catch(k){}h.args=h.args||[];break;case"json":try{h.data=g.parse(a)}catch(k){}break;case"connect":h.qs=a||"";break;case"ack":var b=a.match(/^([0-9]+)(\+)?(.*)/);if(b){h.ackId=b[1],h.args=[];if(b[3])try{h.args=b[3]?g.parse(b[3]):[]}catch(k){}}break;case"disconnect":case"heartbeat":}return h},c.decodePayload=function(a){if(a.charAt(0)=="\ufffd"){var b=[];for(var d=1,e="";d<a.length;d++)a.charAt(d)=="\ufffd"?(b.push(c.decodePacket(a.substr(d+1).substr(0,e))),d+=Number(e)+1,e=""):e+=a.charAt(d);return b}return[c.decodePacket(a)]}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a,b){this.socket=a,this.sessid=b}a.Transport=c,b.util.mixin(c,b.EventEmitter),c.prototype.onData=function(a){this.clearCloseTimeout(),this.setCloseTimeout();if(a!==""){var c=b.parser.decodePayload(a);if(c&&c.length)for(var d=0,e=c.length;d<e;d++)this.onPacket(c[d])}return this},c.prototype.onPacket=function(a){if(a.type=="heartbeat")return this.onHeartbeat();a.type=="connect"&&a.endpoint==""&&this.onConnect(),this.socket.onPacket(a);return this},c.prototype.setCloseTimeout=function(){if(!this.closeTimeout){var a=this;this.closeTimeout=setTimeout(function(){a.onDisconnect()},this.socket.closeTimeout)}},c.prototype.onDisconnect=function(){this.close&&this.close(),this.clearTimeouts(),this.socket.onDisconnect();return this},c.prototype.onConnect=function(){this.socket.onConnect();return this},c.prototype.clearCloseTimeout=function(){this.closeTimeout&&(clearTimeout(this.closeTimeout),this.closeTimeout=null)},c.prototype.clearTimeouts=function(){this.clearCloseTimeout(),this.reopenTimeout&&clearTimeout(this.reopenTimeout)},c.prototype.packet=function(a){this.send(b.parser.encodePacket(a))},c.prototype.onHeartbeat=function(a){this.packet({type:"heartbeat"})},c.prototype.onOpen=function(){this.open=!0,this.clearCloseTimeout(),this.socket.onOpen()},c.prototype.onClose=function(){var a=this;this.open=!1,this.setCloseTimeout(),this.socket.onClose()},c.prototype.prepareUrl=function(){var a=this.socket.options;return this.scheme()+"://"+a.host+":"+a.port+"/"+a.resource+"/"+b.protocol+"/"+this.name+"/"+this.sessid},c.prototype.ready=function(a,b){b.call(this)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function e(){}function d(a){this.options={port:80,secure:!1,document:"document"in c?document:!1,resource:"socket.io",transports:b.transports,"connect timeout":1e4,"try multiple transports":!0,reconnect:!0,"reconnection delay":500,"reconnection limit":Infinity,"reopen delay":3e3,"max reconnection attempts":10,"sync disconnect on unload":!0,"auto connect":!0,"flash policy port":10843},b.util.merge(this.options,a),this.connected=!1,this.open=!1,this.connecting=!1,this.reconnecting=!1,this.namespaces={},this.buffer=[],this.doBuffer=!1;if(this.options["sync disconnect on unload"]&&(!this.isXDomain()||b.util.ua.hasCORS)){var d=this;b.util.on(c,"beforeunload",function(){d.disconnectSync()},!1)}this.options["auto connect"]&&this.connect()}a.Socket=d,b.util.mixin(d,b.EventEmitter),d.prototype.of=function(a){this.namespaces[a]||(this.namespaces[a]=new b.SocketNamespace(this,a),a!==""&&this.namespaces[a].packet({type:"connect"}));return this.namespaces[a]},d.prototype.publish=function(){this.emit.apply(this,arguments);var a;for(var b in this.namespaces)this.namespaces.hasOwnProperty(b)&&(a=this.of(b),a.$emit.apply(a,arguments))},d.prototype.handshake=function(a){function f(b){b instanceof Error?c.onError(b.message):a.apply(null,b.split(":"))}var c=this,d=this.options,g=["http"+(d.secure?"s":"")+":/",d.host+":"+d.port,this.options.resource,b.protocol,b.util.query(this.options.query,"t="+ +(new Date))].join("/");if(this.isXDomain()){var h=document.getElementsByTagName("script")[0],i=document.createElement("script");i.src=g+"&jsonp="+b.j.length,h.parentNode.insertBefore(i,h),b.j.push(function(a){f(a),i.parentNode.removeChild(i)})}else{var j=b.util.request();j.open("GET",g,!0),j.onreadystatechange=function(){j.readyState==4&&(j.onreadystatechange=e,j.status==200?f(j.responseText):!c.reconnecting&&c.onError(j.responseText))},j.send(null)}},d.prototype.getTransport=function(a){var c=a||this.transports,d;for(var e=0,f;f=c[e];e++)if(b.Transport[f]&&b.Transport[f].check(this)&&(!this.isXDomain()||b.Transport[f].xdomainCheck()))return new b.Transport[f](this,this.sessionid);return null},d.prototype.connect=function(a){if(this.connecting)return this;var c=this;this.handshake(function(d,e,f,g){function h(a){c.transport&&c.transport.clearTimeouts(),c.transport=c.getTransport(a);if(!c.transport)return c.publish("connect_failed");c.transport.ready(c,function(){c.connecting=!0,c.publish("connecting",c.transport.name),c.transport.open(),c.options["connect timeout"]&&(c.connectTimeoutTimer=setTimeout(function(){if(!c.connected){c.connecting=!1;if(c.options["try multiple transports"]){c.remainingTransports||(c.remainingTransports=c.transports.slice(0));var a=c.remainingTransports;while(a.length>0&&a.splice(0,1)[0]!=c.transport.name);a.length?h(a):c.publish("connect_failed")}}},c.options["connect timeout"]))})}c.sessionid=d,c.closeTimeout=f*1e3,c.heartbeatTimeout=e*1e3,c.transports=b.util.intersect(g.split(","),c.options.transports),h(),c.once("connect",function(){clearTimeout(c.connectTimeoutTimer),a&&typeof a=="function"&&a()})});return this},d.prototype.packet=function(a){this.connected&&!this.doBuffer?this.transport.packet(a):this.buffer.push(a);return this},d.prototype.setBuffer=function(a){this.doBuffer=a,!a&&this.connected&&this.buffer.length&&(this.transport.payload(this.buffer),this.buffer=[])},d.prototype.disconnect=function(){this.connected&&(this.open&&this.of("").packet({type:"disconnect"}),this.onDisconnect("booted"));return this},d.prototype.disconnectSync=function(){var a=b.util.request(),c=this.resource+"/"+b.protocol+"/"+this.sessionid;a.open("GET",c,!0),this.onDisconnect("booted")},d.prototype.isXDomain=function(){var a=window.location.port||("https:"==window.location.protocol?443:80);return this.options.host!==document.domain||this.options.port!=a},d.prototype.onConnect=function(){this.connected||(this.connected=!0,this.connecting=!1,this.doBuffer||this.setBuffer(!1),this.emit("connect"))},d.prototype.onOpen=function(){this.open=!0},d.prototype.onClose=function(){this.open=!1},d.prototype.onPacket=function(a){this.of(a.endpoint).onPacket(a)},d.prototype.onError=function(a){a&&a.advice&&a.advice==="reconnect"&&this.connected&&(this.disconnect(),this.reconnect()),this.publish("error",a&&a.reason?a.reason:a)},d.prototype.onDisconnect=function(a){var b=this.connected;this.connected=!1,this.connecting=!1,this.open=!1,b&&(this.transport.close(),this.transport.clearTimeouts(),this.publish("disconnect",a),"booted"!=a&&this.options.reconnect&&!this.reconnecting&&this.reconnect())},d.prototype.reconnect=function(){function f(){if(!!a.reconnecting){if(a.connected)return e();if(a.connecting&&a.reconnecting)return a.reconnectionTimer=setTimeout(f,1e3);a.reconnectionAttempts++>=b?a.redoTransports?(a.publish("reconnect_failed"),e()):(a.on("connect_failed",f),a.options["try multiple transports"]=!0,a.transport=a.getTransport(),a.redoTransports=!0,a.connect()):(a.reconnectionDelay<d&&(a.reconnectionDelay*=2),a.connect(),a.publish("reconnecting",a.reconnectionDelay,a.reconnectionAttempts),a.reconnectionTimer=setTimeout(f,a.reconnectionDelay))}}function e(){if(a.connected){for(var b in a.namespaces)a.namespaces.hasOwnProperty(b)&&""!==b&&a.namespaces[b].packet({type:"connect"});a.publish("reconnect",a.transport.name,a.reconnectionAttempts)}a.removeListener("connect_failed",f),a.removeListener("connect",f),a.reconnecting=!1,delete a.reconnectionAttempts,delete a.reconnectionDelay,delete a.reconnectionTimer,delete a.redoTransports,a.options["try multiple transports"]=c}this.reconnecting=!0,this.reconnectionAttempts=0,this.reconnectionDelay=this.options["reconnection delay"];var a=this,b=this.options["max reconnection attempts"],c=this.options["try multiple transports"],d=this.options["reconnection limit"];this.options["try multiple transports"]=!1,this.reconnectionTimer=setTimeout(f,this.reconnectionDelay),this.on("connect",f)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function d(a,b){this.namespace=a,this.name=b}function c(a,b){this.socket=a,this.name=b||"",this.flags={},this.json=new d(this,"json"),this.ackPackets=0,this.acks={}}a.SocketNamespace=c,b.util.mixin(c,b.EventEmitter),c.prototype.$emit=b.EventEmitter.prototype.emit,c.prototype.of=function(){return this.socket.of.apply(this.socket,arguments)},c.prototype.packet=function(a){a.endpoint=this.name,this.socket.packet(a),this.flags={};return this},c.prototype.send=function(a,b){var c={type:this.flags.json?"json":"message",data:a};"function"==typeof b&&(c.id=++this.ackPackets,c.ack=!0,this.acks[c.id]=b);return this.packet(c)},c.prototype.emit=function(a){var b=Array.prototype.slice.call(arguments,1),c=b[b.length-1],d={type:"event",name:a};"function"==typeof c&&(d.id=++this.ackPackets,d.ack="data",this.acks[d.id]=c,b=b.slice(0,b.length-1)),d.args=b;return this.packet(d)},c.prototype.disconnect=function(){this.name===""?this.socket.disconnect():(this.packet({type:"disconnect"}),this.$emit("disconnect"));return this},c.prototype.onPacket=function(a){function d(){c.packet({type:"ack",args:b.util.toArray(arguments),ackId:a.id})}var c=this;switch(a.type){case"connect":this.$emit("connect");break;case"disconnect":this.name===""?this.socket.onDisconnect(a.reason||"booted"):this.$emit("disconnect",a.reason);break;case"message":case"json":var e=["message",a.data];a.ack=="data"?e.push(d):a.ack&&this.packet({type:"ack",ackId:a.id}),this.$emit.apply(this,e);break;case"event":var e=[a.name].concat(a.args);a.ack=="data"&&e.push(d),this.$emit.apply(this,e);break;case"ack":this.acks[a.ackId]&&(this.acks[a.ackId].apply(this,a.args),delete this.acks[a.ackId]);break;case"error":a.advice?this.socket.onError(a):a.reason=="unauthorized"?this.$emit("connect_failed",a.reason):this.$emit("error",a.reason)}},d.prototype.send=function(){this.namespace.flags[this.name]=!0,this.namespace.send.apply(this.namespace,arguments)},d.prototype.emit=function(){this.namespace.flags[this.name]=!0,this.namespace.emit.apply(this.namespace,arguments)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a){b.Transport.apply(this,arguments)}a.websocket=c,b.util.inherit(c,b.Transport),c.prototype.name="websocket",c.prototype.open=function(){var a=b.util.query(this.socket.options.query),c=this,d;d||(d=window.MozWebSocket||window.WebSocket),this.websocket=new d(this.prepareUrl()+a),this.websocket.onopen=function(){c.onOpen(),c.socket.setBuffer(!1)},this.websocket.onmessage=function(a){c.onData(a.data)},this.websocket.onclose=function(){c.onClose(),c.socket.setBuffer(!0)},this.websocket.onerror=function(a){c.onError(a)};return this},c.prototype.send=function(a){this.websocket.send(a);return this},c.prototype.payload=function(a){for(var b=0,c=a.length;b<c;b++)this.packet(a[b]);return this},c.prototype.close=function(){this.websocket.close();return this},c.prototype.onError=function(a){this.socket.onError(a)},c.prototype.scheme=function(){return this.socket.options.secure?"wss":"ws"},c.check=function(){return"WebSocket"in window&&!("__addTask"in WebSocket)||"MozWebSocket"in window},c.xdomainCheck=function(){return!0},b.transports.push("websocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(){b.Transport.websocket.apply(this,arguments)}a.flashsocket=c,b.util.inherit(c,b.Transport.websocket),c.prototype.name="flashsocket",c.prototype.open=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.open.apply(a,c)});return this},c.prototype.send=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.send.apply(a,c)});return this},c.prototype.close=function(){WebSocket.__tasks.length=0,b.Transport.websocket.prototype.close.call(this);return this},c.prototype.ready=function(a,d){function e(){var b=a.options,e=b["flash policy port"],g=["http"+(b.secure?"s":"")+":/",b.host+":"+b.port,b.resource,"static/flashsocket","WebSocketMain"+(a.isXDomain()?"Insecure":"")+".swf"];c.loaded||(typeof WEB_SOCKET_SWF_LOCATION=="undefined"&&(WEB_SOCKET_SWF_LOCATION=g.join("/")),e!==843&&WebSocket.loadFlashPolicyFile("xmlsocket://"+b.host+":"+e),WebSocket.__initialize(),c.loaded=!0),d.call(f)}var f=this;if(document.body)return e();b.util.load(e)},c.check=function(){return typeof WebSocket!="undefined"&&"__initialize"in WebSocket&&!!swfobject?swfobject.getFlashPlayerVersion().major>=10:!1},c.xdomainCheck=function(){return!0},typeof window!="undefined"&&(WEB_SOCKET_DISABLE_AUTO_INITIALIZATION=!0),b.transports.push("flashsocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports);var swfobject=function(){function V(b){var c=/[\\\"<>\.;]/,d=c.exec(b)!=null;return d&&typeof encodeURIComponent!=a?encodeURIComponent(b):b}function U(a,b){if(!!x){var c=b?"visible":"hidden";t&&P(a)?P(a).style.visibility=c:T("#"+a,"visibility:"+c)}}function T(c,d,e,f){if(!y.ie||!y.mac){var g=i.getElementsByTagName("head")[0];if(!g)return;var h=e&&typeof e=="string"?e:"screen";f&&(v=null,w=null);if(!v||w!=h){var j=Q("style");j.setAttribute("type","text/css"),j.setAttribute("media",h),v=g.appendChild(j),y.ie&&y.win&&typeof i.styleSheets!=a&&i.styleSheets.length>0&&(v=i.styleSheets[i.styleSheets.length-1]),w=h}y.ie&&y.win?v&&typeof v.addRule==b&&v.addRule(c,d):v&&typeof i.createTextNode!=a&&v.appendChild(i.createTextNode(c+" {"+d+"}"))}}function S(a){var b=y.pv,c=a.split(".");c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0;return b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function R(a,b,c){a.attachEvent(b,c),o[o.length]=[a,b,c]}function Q(a){return i.createElement(a)}function P(a){var b=null;try{b=i.getElementById(a)}catch(c){}return b}function O(a){var b=P(a);if(b){for(var c in b)typeof b[c]=="function"&&(b[c]=null);b.parentNode.removeChild(b)}}function N(a){var b=P(a);b&&b.nodeName=="OBJECT"&&(y.ie&&y.win?(b.style.display="none",function(){b.readyState==4?O(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function M(a,b,c){var d=Q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function L(c,d,f){var g,h=P(f);if(y.wk&&y.wk<312)return g;if(h){typeof c.id==a&&(c.id=f);if(y.ie&&y.win){var i="";for(var j in c)c[j]!=Object.prototype[j]&&(j.toLowerCase()=="data"?d.movie=c[j]:j.toLowerCase()=="styleclass"?i+=' class="'+c[j]+'"':j.toLowerCase()!="classid"&&(i+=" "+j+'="'+c[j]+'"'));var k="";for(var l in d)d[l]!=Object.prototype[l]&&(k+='<param name="'+l+'" value="'+d[l]+'" />');h.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+i+">"+k+"</object>",n[n.length]=c.id,g=P(c.id)}else{var m=Q(b);m.setAttribute("type",e);for(var o in c)c[o]!=Object.prototype[o]&&(o.toLowerCase()=="styleclass"?m.setAttribute("class",c[o]):o.toLowerCase()!="classid"&&m.setAttribute(o,c[o]));for(var p in d)d[p]!=Object.prototype[p]&&p.toLowerCase()!="movie"&&M(m,p,d[p]);h.parentNode.replaceChild(m,h),g=m}}return g}function K(a){var c=Q("div");if(y.win&&y.ie)c.innerHTML=a.innerHTML;else{var d=a.getElementsByTagName(b)[0];if(d){var e=d.childNodes;if(e){var f=e.length;for(var g=0;g<f;g++)(e[g].nodeType!=1||e[g].nodeName!="PARAM")&&e[g].nodeType!=8&&c.appendChild(e[g].cloneNode(!0))}}}return c}function J(a){if(y.ie&&y.win&&a.readyState!=4){var b=Q("div");a.parentNode.insertBefore(b,a),b.parentNode.replaceChild(K(a),b),a.style.display="none",function(){a.readyState==4?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)}()}else a.parentNode.replaceChild(K(a),a)}function I(b,c,d,e){u=!0,r=e||null,s={success:!1,id:d};var g=P(d);if(g){g.nodeName=="OBJECT"?(p=K(g),q=null):(p=g,q=d),b.id=f;if(typeof b.width==a||!/%$/.test(b.width)&&parseInt(b.width,10)<310)b.width="310";if(typeof b.height==a||!/%$/.test(b.height)&&parseInt(b.height,10)<137)b.height="137";i.title=i.title.slice(0,47)+" - Flash Player Installation";var j=y.ie&&y.win?"ActiveX":"PlugIn",k="MMredirectURL="+h.location.toString().replace(/&/g,"%26")+"&MMplayerType="+j+"&MMdoctitle="+i.title;typeof c.flashvars!=a?c.flashvars+="&"+k:c.flashvars=k;if(y.ie&&y.win&&g.readyState!=4){var l=Q("div");d+="SWFObjectNew",l.setAttribute("id",d),g.parentNode.insertBefore(l,g),g.style.display="none",function(){g.readyState==4?g.parentNode.removeChild(g):setTimeout(arguments.callee,10)}()}L(b,c,d)}}function H(){return!u&&S("6.0.65")&&(y.win||y.mac)&&!(y.wk&&y.wk<312)}function G(c){var d=null,e=P(c);if(e&&e.nodeName=="OBJECT")if(typeof e.SetVariable!=a)d=e;else{var f=e.getElementsByTagName(b)[0];f&&(d=f)}return d}function F(){var b=m.length;if(b>0)for(var c=0;c<b;c++){var d=m[c].id,e=m[c].callbackFn,f={success:!1,id:d};if(y.pv[0]>0){var g=P(d);if(g)if(S(m[c].swfVersion)&&!(y.wk&&y.wk<312))U(d,!0),e&&(f.success=!0,f.ref=G(d),e(f));else if(m[c].expressInstall&&H()){var h={};h.data=m[c].expressInstall,h.width=g.getAttribute("width")||"0",h.height=g.getAttribute("height")||"0",g.getAttribute("class")&&(h.styleclass=g.getAttribute("class")),g.getAttribute("align")&&(h.align=g.getAttribute("align"));var i={},j=g.getElementsByTagName("param"),k=j.length;for(var l=0;l<k;l++)j[l].getAttribute("name").toLowerCase()!="movie"&&(i[j[l].getAttribute("name")]=j[l].getAttribute("value"));I(h,i,d,e)}else J(g),e&&e(f)}else{U(d,!0);if(e){var n=G(d);n&&typeof n.SetVariable!=a&&(f.success=!0,f.ref=n),e(f)}}}}function E(){var c=i.getElementsByTagName("body")[0],d=Q(b);d.setAttribute("type",e);var f=c.appendChild(d);if(f){var g=0;(function(){if(typeof f.GetVariable!=a){var b=f.GetVariable("$version");b&&(b=b.split(" ")[1].split(","),y.pv=[parseInt(b[0],10),parseInt(b[1],10),parseInt(b[2],10)])}else if(g<10){g++,setTimeout(arguments.callee,10);return}c.removeChild(d),f=null,F()})()}else F()}function D(){k?E():F()}function C(b){if(typeof h.addEventListener!=a)h.addEventListener("load",b,!1);else if(typeof i.addEventListener!=a)i.addEventListener("load",b,!1);else if(typeof h.attachEvent!=a)R(h,"onload",b);else if(typeof h.onload=="function"){var c=h.onload;h.onload=function(){c(),b()}}else h.onload=b}function B(a){t?a():l[l.length]=a}function A(){if(!t){try{var a=i.getElementsByTagName("body")[0].appendChild(Q("span"));a.parentNode.removeChild(a)}catch(b){return}t=!0;var c=l.length;for(var d=0;d<c;d++)l[d]()}}var a="undefined",b="object",c="Shockwave Flash",d="ShockwaveFlash.ShockwaveFlash",e="application/x-shockwave-flash",f="SWFObjectExprInst",g="onreadystatechange",h=window,i=document,j=navigator,k=!1,l=[D],m=[],n=[],o=[],p,q,r,s,t=!1,u=!1,v,w,x=!0,y=function(){var f=typeof i.getElementById!=a&&typeof i.getElementsByTagName!=a&&typeof i.createElement!=a,g=j.userAgent.toLowerCase(),l=j.platform.toLowerCase(),m=l?/win/.test(l):/win/.test(g),n=l?/mac/.test(l):/mac/.test(g),o=/webkit/.test(g)?parseFloat(g.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,p=!1,q=[0,0,0],r=null;if(typeof j.plugins!=a&&typeof j.plugins[c]==b)r=j.plugins[c].description,r&&(typeof j.mimeTypes==a||!j.mimeTypes[e]||!!j.mimeTypes[e].enabledPlugin)&&(k=!0,p=!1,r=r.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),q[0]=parseInt(r.replace(/^(.*)\..*$/,"$1"),10),q[1]=parseInt(r.replace(/^.*\.(.*)\s.*$/,"$1"),10),q[2]=/[a-zA-Z]/.test(r)?parseInt(r.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof h.ActiveXObject!=a)try{var s=new ActiveXObject(d);s&&(r=s.GetVariable("$version"),r&&(p=!0,r=r.split(" ")[1].split(","),q=[parseInt(r[0],10),parseInt(r[1],10),parseInt(r[2],10)]))}catch(t){}return{w3:f,pv:q,wk:o,ie:p,win:m,mac:n}}(),z=function(){!y.w3||((typeof i.readyState!=a&&i.readyState=="complete"||typeof i.readyState==a&&(i.getElementsByTagName("body")[0]||i.body))&&A(),t||(typeof i.addEventListener!=a&&i.addEventListener("DOMContentLoaded",A,!1),y.ie&&y.win&&(i.attachEvent(g,function(){i.readyState=="complete"&&(i.detachEvent(g,arguments.callee),A())}),h==top&&function(){if(!t){try{i.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee,0);return}A()}}()),y.wk&&function(){if(!t){if(!/loaded|complete/.test(i.readyState)){setTimeout(arguments.callee,0);return}A()}}(),C(A)))}(),W=function(){y.ie&&y.win&&window.attachEvent("onunload",function(){var a=o.length;for(var b=0;b<a;b++)o[b][0].detachEvent(o[b][1],o[b][2]);var c=n.length;for(var d=0;d<c;d++)N(n[d]);for(var e in y)y[e]=null;y=null;for(var f in swfobject)swfobject[f]=null;swfobject=null})}();return{registerObject:function(a,b,c,d){if(y.w3&&a&&b){var e={};e.id=a,e.swfVersion=b,e.expressInstall=c,e.callbackFn=d,m[m.length]=e,U(a,!1)}else d&&d({success:!1,id:a})},getObjectById:function(a){if(y.w3)return G(a)},embedSWF:function(c,d,e,f,g,h,i,j,k,l){var m={success:!1,id:d};y.w3&&!(y.wk&&y.wk<312)&&c&&d&&e&&f&&g?(U(d,!1),B(function(){e+="",f+="";var n={};if(k&&typeof k===b)for(var o in k)n[o]=k[o];n.data=c,n.width=e,n.height=f;var p={};if(j&&typeof j===b)for(var q in j)p[q]=j[q];if(i&&typeof i===b)for(var r in i)typeof p.flashvars!=a?p.flashvars+="&"+r+"="+i[r]:p.flashvars=r+"="+i[r];if(S(g)){var s=L(n,p,d);n.id==d&&U(d,!0),m.success=!0,m.ref=s}else{if(h&&H()){n.data=h,I(n,p,d,l);return}U(d,!0)}l&&l(m)})):l&&l(m)},switchOffAutoHideShow:function(){x=!1},ua:y,getFlashPlayerVersion:function(){return{major:y.pv[0],minor:y.pv[1],release:y.pv[2]}},hasFlashPlayerVersion:S,createSWF:function(a,b,c){return y.w3?L(a,b,c):undefined},showExpressInstall:function(a,b,c,d){y.w3&&H()&&I(a,b,c,d)},removeSWF:function(a){y.w3&&N(a)},createCSS:function(a,b,c,d){y.w3&&T(a,b,c,d)},addDomLoadEvent:B,addLoadEvent:C,getQueryParamValue:function(a){var b=i.location.search||i.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(a==null)return V(b);var c=b.split("&");for(var d=0;d<c.length;d++)if(c[d].substring(0,c[d].indexOf("="))==a)return V(c[d].substring(c[d].indexOf("=")+1))}return""},expressInstallCallback:function(){if(u){var a=P(f);a&&p&&(a.parentNode.replaceChild(p,a),q&&(U(q,!0),y.ie&&y.win&&(p.style.display="block")),r&&r(s)),u=!1}}}}();(function(){if(!window.WebSocket){var a=window.console;if(!a||!a.log||!a.error)a={log:function(){},error:function(){}};if(!swfobject.hasFlashPlayerVersion("10.0.0")){a.error("Flash Player >= 10.0.0 is required.");return}location.protocol=="file:"&&a.error("WARNING: web-socket-js doesn't work in file:///... URL unless you set Flash Security Settings properly. Open the page via Web server i.e. http://..."),WebSocket=function(a,b,c,d,e){var f=this;f.__id=WebSocket.__nextId++,WebSocket.__instances[f.__id]=f,f.readyState=WebSocket.CONNECTING,f.bufferedAmount=0,f.__events={},b?typeof b=="string"&&(b=[b]):b=[],setTimeout(function(){WebSocket.__addTask(function(){WebSocket.__flash.create(f.__id,a,b,c||null,d||0,e||null)})},0)},WebSocket.prototype.send=function(a){if(this.readyState==WebSocket.CONNECTING)throw"INVALID_STATE_ERR: Web Socket connection has not been established";var b=WebSocket.__flash.send(this.__id,encodeURIComponent(a));if(b<0)return!0;this.bufferedAmount+=b;return!1},WebSocket.prototype.close=function(){this.readyState!=WebSocket.CLOSED&&this.readyState!=WebSocket.CLOSING&&(this.readyState=WebSocket.CLOSING,WebSocket.__flash.close(this.__id))},WebSocket.prototype.addEventListener=function(a,b,c){a in this.__events||(this.__events[a]=[]),this.__events[a].push(b)},WebSocket.prototype.removeEventListener=function(a,b,c){if(a in this.__events){var d=this.__events[a];for(var e=d.length-1;e>=0;--e)if(d[e]===b){d.splice(e,1);break}}},WebSocket.prototype.dispatchEvent=function(a){var b=this.__events[a.type]||[];for(var c=0;c<b.length;++c)b[c](a);var d=this["on"+a.type];d&&d(a)},WebSocket.prototype.__handleEvent=function(a){"readyState"in a&&(this.readyState=a.readyState),"protocol"in a&&(this.protocol=a.protocol);var b;if(a.type=="open"||a.type=="error")b=this.__createSimpleEvent(a.type);else if(a.type=="close")b=this.__createSimpleEvent("close");else{if(a.type!="message")throw"unknown event type: "+a.type;var c=decodeURIComponent(a.message);b=this.__createMessageEvent("message",c)}this.dispatchEvent(b)},WebSocket.prototype.__createSimpleEvent=function(a){if(document.createEvent&&window.Event){var b=document.createEvent("Event");b.initEvent(a,!1,!1);return b}return{type:a,bubbles:!1,cancelable:!1}},WebSocket.prototype.__createMessageEvent=function(a,b){if(document.createEvent&&window.MessageEvent&&!window.opera){var c=document.createEvent("MessageEvent");c.initMessageEvent("message",!1,!1,b,null,null,window,null);return c}return{type:a,data:b,bubbles:!1,cancelable:!1}},WebSocket.CONNECTING=0,WebSocket.OPEN=1,WebSocket.CLOSING=2,WebSocket.CLOSED=3,WebSocket.__flash=null,WebSocket.__instances={},WebSocket.__tasks=[],WebSocket.__nextId=0,WebSocket.loadFlashPolicyFile=function(a){WebSocket.__addTask(function(){WebSocket.__flash.loadManualPolicyFile(a)})},WebSocket.__initialize=function(){if(!WebSocket.__flash){WebSocket.__swfLocation&&(window.WEB_SOCKET_SWF_LOCATION=WebSocket.__swfLocation);if(!window.WEB_SOCKET_SWF_LOCATION){a.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");return}var b=document.createElement("div");b.id="webSocketContainer",b.style.position="absolute",WebSocket.__isFlashLite()?(b.style.left="0px",b.style.top="0px"):(b.style.left="-100px",b.style.top="-100px");var c=document.createElement("div");c.id="webSocketFlash",b.appendChild(c),document.body.appendChild(b),swfobject.embedSWF(WEB_SOCKET_SWF_LOCATION,"webSocketFlash","1","1","10.0.0",null,null,{hasPriority:!0,swliveconnect:!0,allowScriptAccess:"always"},null,function(b){b.success||a.error("[WebSocket] swfobject.embedSWF failed")})}},WebSocket.__onFlashInitialized=function(){setTimeout(function(){WebSocket.__flash=document.getElementById("webSocketFlash"),WebSocket.__flash.setCallerUrl(location.href),WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);for(var a=0;a<WebSocket.__tasks.length;++a)WebSocket.__tasks[a]();WebSocket.__tasks=[]},0)},WebSocket.__onFlashEvent=function(){setTimeout(function(){try{var b=WebSocket.__flash.receiveEvents();for(var c=0;c<b.length;++c)WebSocket.__instances[b[c].webSocketId].__handleEvent(b[c])}catch(d){a.error(d)}},0);return!0},WebSocket.__log=function(b){a.log(decodeURIComponent(b))},WebSocket.__error=function(b){a.error(decodeURIComponent(b))},WebSocket.__addTask=function(a){WebSocket.__flash?a():WebSocket.__tasks.push(a)},WebSocket.__isFlashLite=function(){if(!window.navigator||!window.navigator.mimeTypes)return!1;var a=window.navigator.mimeTypes["application/x-shockwave-flash"];return!a||!a.enabledPlugin||!a.enabledPlugin.filename?!1:a.enabledPlugin.filename.match(/flashlite/i)?!0:!1},window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION||(window.addEventListener?window.addEventListener("load",function(){WebSocket.__initialize()},!1):window.attachEvent("onload",function(){WebSocket.__initialize()}))}})(),function(a,b,c){function e(){}function d(a){!a||(b.Transport.apply(this,arguments),this.sendBuffer=[])}a.XHR=d,b.util.inherit(d,b.Transport),d.prototype.open=function(){this.socket.setBuffer(!1),this.onOpen(),this.get(),this.setCloseTimeout();return this},d.prototype.payload=function(a){var c=[];for(var d=0,e=a.length;d<e;d++)c.push(b.parser.encodePacket(a[d]));this.send(b.parser.encodePayload(c))},d.prototype.send=function(a){this.post(a);return this},d.prototype.post=function(a){function f(){this.onload=e,b.socket.setBuffer(!1)}function d(){this.readyState==4&&(this.onreadystatechange=e,b.posting=!1,this.status==200?b.socket.setBuffer(!1):b.onClose())}var b=this;this.socket.setBuffer(!0),this.sendXHR=this.request("POST"),c.XDomainRequest&&this.sendXHR instanceof XDomainRequest?this.sendXHR.onload=this.sendXHR.onerror=f:this.sendXHR.onreadystatechange=d,this.sendXHR.send(a)},d.prototype.close=function(){this.onClose();return this},d.prototype.request=function(a){var c=b.util.request(this.socket.isXDomain()),d=b.util.query(this.socket.options.query,"t="+ +(new Date));c.open(a||"GET",this.prepareUrl()+d,!0);if(a=="POST")try{c.setRequestHeader?c.setRequestHeader("Content-type","text/plain;charset=UTF-8"):c.contentType="text/plain"}catch(e){}return c},d.prototype.scheme=function(){return this.socket.options.secure?"https":"http"},d.check=function(a,c){try{if(b.util.request(c))return!0}catch(d){}return!1},d.xdomainCheck=function(){return d.check(null,!0)}}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function c(a){b.Transport.XHR.apply(this,arguments)}a.htmlfile=c,b.util.inherit(c,b.Transport.XHR),c.prototype.name="htmlfile",c.prototype.get=function(){this.doc=new ActiveXObject("htmlfile"),this.doc.open(),this.doc.write("<html></html>"),this.doc.close(),this.doc.parentWindow.s=this;var a=this.doc.createElement("div");a.className="socketio",this.doc.body.appendChild(a),this.iframe=this.doc.createElement("iframe"),a.appendChild(this.iframe);var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date));this.iframe.src=this.prepareUrl()+d,b.util.on(window,"unload",function(){c.destroy()})},c.prototype._=function(a,b){this.onData(a);try{var c=b.getElementsByTagName("script")[0];c.parentNode.removeChild(c)}catch(d){}},c.prototype.destroy=function(){if(this.iframe){try{this.iframe.src="about:blank"}catch(a){}this.doc=null,this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,CollectGarbage()}},c.prototype.close=function(){this.destroy();return b.Transport.XHR.prototype.close.call(this)},c.check=function(){if("ActiveXObject"in window)try{var a=new ActiveXObject("htmlfile");return a&&b.Transport.XHR.check()}catch(c){}return!1},c.xdomainCheck=function(){return!1},b.transports.push("htmlfile")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function e(){}function d(){b.Transport.XHR.apply(this,arguments)}a["xhr-polling"]=d,b.util.inherit(d,b.Transport.XHR),b.util.merge(d,b.Transport.XHR),d.prototype.name="xhr-polling",d.prototype.open=function(){var a=this;b.Transport.XHR.prototype.open.call(a);return!1},d.prototype.get=function(){function d(){this.onload=e,a.onData(this.responseText),a.get()}function b(){this.readyState==4&&(this.onreadystatechange=e,this.status==200?(a.onData(this.responseText),a.get()):a.onClose())}if(!!this.open){var a=this;this.xhr=this.request(),c.XDomainRequest&&this.xhr instanceof XDomainRequest?this.xhr.onload=this.xhr.onerror=d:this.xhr.onreadystatechange=b,this.xhr.send(null)}},d.prototype.onClose=function(){b.Transport.XHR.prototype.onClose.call(this);if(this.xhr){this.xhr.onreadystatechange=this.xhr.onload=e;try{this.xhr.abort()}catch(a){}this.xhr=null}},d.prototype.ready=function(a,c){var d=this;b.util.defer(function(){c.call(d)})},b.transports.push("xhr-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function c(a){b.Transport["xhr-polling"].apply(this,arguments),this.index=b.j.length;var c=this;b.j.push(function(a){c._(a)})}a["jsonp-polling"]=c,b.util.inherit(c,b.Transport["xhr-polling"]),c.prototype.name="jsonp-polling",c.prototype.post=function(a){function j(){c.iframe&&c.form.removeChild(c.iframe);try{h=document.createElement('<iframe name="'+c.iframeId+'">')}catch(a){h=document.createElement("iframe"),h.name=c.iframeId}h.id=c.iframeId,c.form.appendChild(h),c.iframe=h}function i(){j(),c.socket.setBuffer(!1)}var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date)+"&i="+this.index);if(!this.form){var e=document.createElement("form"),f=document.createElement("textarea"),g=this.iframeId="socketio_iframe_"+this.index,h;e.className="socketio",e.style.position="absolute",e.style.top="-1000px",e.style.left="-1000px",e.target=g,e.method="POST",e.setAttribute("accept-charset","utf-8"),f.name="d",e.appendChild(f),document.body.appendChild(e),this.form=e,this.area=f}this.form.action=this.prepareUrl()+d,j(),this.area.value=a;try{this.form.submit()}catch(k){}this.iframe.attachEvent?h.onreadystatechange=function(){c.iframe.readyState=="complete"&&i()}:this.iframe.onload=i,this.socket.setBuffer(!0)},c.prototype.get=function(){var a=this,c=document.createElement("script"),d=b.util.query(this.socket.options.query,"t="+ +(new Date)+"&i="+this.index);this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),c.async=!0,c.src=this.prepareUrl()+d,c.onerror=function(){a.onClose()};var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(c,e),this.script=c},c.prototype._=function(a){this.onData(a),this.open&&this.get();return this},c.check=function(){return!0},c.xdomainCheck=function(){return!0},b.transports.push("jsonp-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports)
\ No newline at end of file
* @api public
*/
- io.version = '0.7.4';
+ io.version = '0.8.4';
/**
* Protocol implemented.
io.EventEmitter = process.EventEmitter;
+ /**
+ * Expose SocketNamespace
+ *
+ * @api private
+ */
+
+ io.SocketNamespace = require('./namespace').SocketNamespace;
+
/**
* Expose Transport
*
io.Transport = require('./transport').Transport;
+ /**
+ * Default enabled transports
+ *
+ * @api public
+ */
+
+ io.transports = ['websocket', 'xhr-polling'];
+
/**
* Expose all transports
+ *
+ * @api public
*/
+ io.Transport.XHR = require('./transports/xhr').XHR;
+
io.transports.forEach(function (t) {
- //io.Transport[t] = require('./transports/node/' + t);
+ io.Transport[t] = require('./transports/' + t)[t];
});
/**
var options = {
host: uri.host
- , secure: uri.protocol == 'https'
- , port: uri.port || 80
+ , secure: 'https' == uri.protocol
+ , port: uri.port || ('https' == uri.protocol ? 443 : 80)
+ , query: uri.query || ''
};
+
io.util.merge(options, details);
if (options['force new connection'] || !io.sockets[uuri]) {
switch (packet.type) {
case 'error':
var reason = packet.reason ? indexOf(reasons, packet.reason) : ''
- , adv = packet.advice ? indexOf(advice, packet.advice) : ''
+ , adv = packet.advice ? indexOf(advice, packet.advice) : '';
if (reason !== '' || adv !== '')
- data = reason + (adv !== '' ? ('+' + adv) : '')
+ data = reason + (adv !== '' ? ('+' + adv) : '');
break;
for (var i = 0, l = packets.length; i < l; i++) {
var packet = packets[i];
- decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]
+ decoded += '\ufffd' + packet.length + '\ufffd' + packets[i];
}
return decoded;
* @api private
*/
- var regexp = /^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;
+ var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
parser.decodePacket = function (data) {
var pieces = data.match(regexp);
* MIT Licensed
*/
-(function (exports, io) {
+(function (exports, io, global) {
/**
* Expose constructor.
exports.Socket = Socket;
/**
- * Create a new `Socket.IO client` which can establish a persisent
+ * Create a new `Socket.IO client` which can establish a persistent
* connection with a Socket.IO enabled server.
*
* @api public
this.options = {
port: 80
, secure: false
- , document: document
+ , document: 'document' in global ? document : false
, resource: 'socket.io'
, transports: io.transports
, 'connect timeout': 10000
, 'try multiple transports': true
, 'reconnect': true
, 'reconnection delay': 500
+ , 'reconnection limit': Infinity
, 'reopen delay': 3000
, 'max reconnection attempts': 10
, 'sync disconnect on unload': true
, 'auto connect': true
+ , 'flash policy port': 10843
};
io.util.merge(this.options, options);
(!this.isXDomain() || io.util.ua.hasCORS)) {
var self = this;
- io.util.on(window, 'beforeunload', function () {
+ io.util.on(global, 'beforeunload', function () {
self.disconnectSync();
}, false);
}
, options.host + ':' + options.port
, this.options.resource
, io.protocol
- , '?t=' + + new Date
+ , io.util.query(this.options.query, 't=' + +new Date)
].join('/');
if (this.isXDomain()) {
var insertAt = document.getElementsByTagName('script')[0]
- , script = document.createElement('SCRIPT');
+ , script = document.createElement('script');
script.src = url + '&jsonp=' + io.j.length;
insertAt.parentNode.insertBefore(script, insertAt);
} else {
var xhr = io.util.request();
- xhr.open('GET', url);
+ xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
xhr.onreadystatechange = empty;
);
function connect (transports){
+ if (self.transport) self.transport.clearTimeouts();
+
self.transport = self.getTransport(transports);
if (!self.transport) return self.publish('connect_failed');
- self.connecting = true;
- self.publish('connecting', self.transport.name);
- self.transport.open();
+ // once the transport is ready
+ self.transport.ready(self, function () {
+ self.connecting = true;
+ self.publish('connecting', self.transport.name);
+ self.transport.open();
- if (self.options['connect timeout']) {
- self.connectTimeoutTimer = setTimeout(function () {
- if (!self.connected) {
- self.connecting = false;
+ if (self.options['connect timeout']) {
+ self.connectTimeoutTimer = setTimeout(function () {
+ if (!self.connected) {
+ self.connecting = false;
- if (self.options['try multiple transports']) {
- if (!self.remainingTransports) {
- self.remainingTransports = self.transports.slice(0);
- }
+ if (self.options['try multiple transports']) {
+ if (!self.remainingTransports) {
+ self.remainingTransports = self.transports.slice(0);
+ }
- var remaining = self.remainingTransports;
+ var remaining = self.remainingTransports;
- while (remaining.length > 0 && remaining.splice(0,1)[0] !=
- self.transport.name) {}
+ while (remaining.length > 0 && remaining.splice(0,1)[0] !=
+ self.transport.name) {}
- if (remaining.length){
- connect(remaining);
- } else {
- self.publish('connect_failed');
+ if (remaining.length){
+ connect(remaining);
+ } else {
+ self.publish('connect_failed');
+ }
}
}
- }
- }, self.options['connect timeout']);
- }
+ }, self.options['connect timeout']);
+ }
+ });
}
connect();
*/
Socket.prototype.isXDomain = function () {
- var locPort = window.location.port || 80;
- return this.options.host !== document.domain || this.options.port != locPort;
+ // if node
+ return false;
+ // end node
+
+ var port = window.location.port ||
+ ('https:' == window.location.protocol ? 443 : 80);
+
+ return this.options.host !== document.domain || this.options.port != port;
};
/**
*/
Socket.prototype.onConnect = function () {
- this.connected = true;
- this.connecting = false;
- if (!this.doBuffer) {
- // make sure to flush the buffer
- this.setBuffer(false);
+ if (!this.connected) {
+ this.connected = true;
+ this.connecting = false;
+ if (!this.doBuffer) {
+ // make sure to flush the buffer
+ this.setBuffer(false);
+ }
+ this.emit('connect');
}
- this.emit('connect');
};
/**
var self = this
, maxAttempts = this.options['max reconnection attempts']
, tryMultiple = this.options['try multiple transports']
+ , limit = this.options['reconnection limit'];
function reset () {
if (self.connected) {
+ for (var i in self.namespaces) {
+ if (self.namespaces.hasOwnProperty(i) && '' !== i) {
+ self.namespaces[i].packet({ type: 'connect' });
+ }
+ }
self.publish('reconnect', self.transport.name, self.reconnectionAttempts);
}
reset();
}
} else {
- self.reconnectionDelay *= 2; // exponential back off
+ if (self.reconnectionDelay < limit) {
+ self.reconnectionDelay *= 2; // exponential back off
+ }
+
self.connect();
self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts);
self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay);
})(
'undefined' != typeof io ? io : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
+ , this
);
+ options.resource + '/' + io.protocol
+ '/' + this.name + '/' + this.sessid;
};
+
+ /**
+ * Checks if the transport is ready to start a connection.
+ *
+ * @param {Socket} socket The socket instance that needs a transport
+ * @param {Function} fn The callback
+ * @api private
+ */
+
+ Transport.prototype.ready = function (socket, fn) {
+ fn.call(this);
+ };
})(
'undefined' != typeof io ? io : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
exports.flashsocket = Flashsocket;
/**
- * The Flashsocket transport. This is a API wrapper for the HTML5 WebSocket
+ * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket
* specification. It uses a .swf file to communicate with the server. If you want
* to serve the .swf file from a other server than where the Socket.IO script is
* coming from you need to use the insecure version of the .swf. More information
Flashsocket.prototype.name = 'flashsocket';
/**
- *Disconnect the established `Flashsocket` connection. This is done by adding a
- * new task to the Flashsocket. The rest will be handled off by the `WebSocket`
+ * Disconnect the established `FlashSocket` connection. This is done by adding a
+ * new task to the FlashSocket. The rest will be handled off by the `WebSocket`
* transport.
*
* @returns {Transport}
*/
Flashsocket.prototype.open = function () {
- var self = this, args = arguments;
+ var self = this
+ , args = arguments;
+
WebSocket.__addTask(function () {
io.Transport.websocket.prototype.open.apply(self, args);
});
/**
* Sends a message to the Socket.IO server. This is done by adding a new
- * task to the Flashsocket. The rest will be handled off by the `WebSocket`
+ * task to the FlashSocket. The rest will be handled off by the `WebSocket`
* transport.
*
* @returns {Transport}
};
/**
- * Disconnects the established `Flashsocket` connection.
+ * Disconnects the established `FlashSocket` connection.
*
* @returns {Transport}
* @api public
};
/**
- * Check if the Flashsocket transport is supported as it requires that the Adobe
- * Flash Player plugin version `10.0.0` or greater is installed. And also check if
+ * The WebSocket fall back needs to append the flash container to the body
+ * element, so we need to make sure we have access to it. Or defer the call
+ * until we are sure there is a body element.
+ *
+ * @param {Socket} socket The socket instance that needs a transport
+ * @param {Function} fn The callback
+ * @api private
+ */
+
+ Flashsocket.prototype.ready = function (socket, fn) {
+ function init () {
+ var options = socket.options
+ , port = options['flash policy port']
+ , path = [
+ 'http' + (options.secure ? 's' : '') + ':/'
+ , options.host + ':' + options.port
+ , options.resource
+ , 'static/flashsocket'
+ , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf'
+ ];
+
+ // Only start downloading the swf file when the checked that this browser
+ // actually supports it
+ if (!Flashsocket.loaded) {
+ if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') {
+ // Set the correct file based on the XDomain settings
+ WEB_SOCKET_SWF_LOCATION = path.join('/');
+ }
+
+ if (port !== 843) {
+ WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port);
+ }
+
+ WebSocket.__initialize();
+ Flashsocket.loaded = true;
+ }
+
+ fn.call(self);
+ }
+
+ var self = this;
+ if (document.body) return init();
+
+ io.util.load(init);
+ };
+
+ /**
+ * Check if the FlashSocket transport is supported as it requires that the Adobe
+ * Flash Player plug-in version `10.0.0` or greater is installed. And also check if
* the polyfill is correctly loaded.
*
* @returns {Boolean}
* @api public
*/
- Flashsocket.check = function (socket) {
+ Flashsocket.check = function () {
if (
typeof WebSocket == 'undefined'
|| !('__initialize' in WebSocket) || !swfobject
) return false;
- var supported = swfobject.getFlashPlayerVersion().major >= 10
- , options = socket.options
- , path = [
- 'http' + (options.secure ? 's' : '') + ':/'
- , options.host + ':' + options.port
- , options.resource
- , 'static/flashsocket'
- , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf'
- ];
-
- // Only start downloading the swf file when the checked that this browser
- // actually supports it
- if (supported && !Flashsocket.loaded) {
- if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') {
- // Set the correct file based on the XDomain settings
- WEB_SOCKET_SWF_LOCATION = path.join('/');
- }
-
- WebSocket.__initialize();
- Flashsocket.loaded = true;
- }
-
- return supported;
+ return swfobject.getFlashPlayerVersion().major >= 10;
};
/**
- * Check if the Flashsocket transport can be used as cross domain / cross origin
+ * Check if the FlashSocket transport can be used as cross domain / cross origin
* transport. Because we can't see which type (secure or insecure) of .swf is used
* we will just return true.
*
iframeC.appendChild(this.iframe);
- this.iframe.src = this.prepareUrl() + '/?t=' + (+ new Date);
+ var self = this
+ , query = io.util.query(this.socket.options.query, 't='+ +new Date);
- var self = this;
+ this.iframe.src = this.prepareUrl() + query;
io.util.on(window, 'unload', function () {
self.destroy();
*/
JSONPPolling.prototype.post = function (data) {
- var self = this;
+ var self = this
+ , query = io.util.query(
+ this.socket.options.query
+ , 't='+ (+new Date) + '&i=' + this.index
+ );
if (!this.form) {
- var form = document.createElement('FORM')
- , area = document.createElement('TEXTAREA')
+ var form = document.createElement('form')
+ , area = document.createElement('textarea')
, id = this.iframeId = 'socketio_iframe_' + this.index
, iframe;
form.style.left = '-1000px';
form.target = id;
form.method = 'POST';
+ form.setAttribute('accept-charset', 'utf-8');
area.name = 'd';
form.appendChild(area);
document.body.appendChild(form);
this.area = area;
}
- this.form.action = this.prepareUrl() + '?t=' + (+new Date) + '&i=' + this.index;
+ this.form.action = this.prepareUrl() + query;
function complete () {
initIframe();
} else {
this.iframe.onload = complete;
}
+
+ this.socket.setBuffer(true);
};
/**
JSONPPolling.prototype.get = function () {
var self = this
- , script = document.createElement('SCRIPT');
+ , script = document.createElement('script')
+ , query = io.util.query(
+ this.socket.options.query
+ , 't='+ (+new Date) + '&i=' + this.index
+ );
if (this.script) {
this.script.parentNode.removeChild(this.script);
}
script.async = true;
- script.src = this.prepareUrl() + '/?t=' + (+new Date) + '&i=' + this.index;
+ script.src = this.prepareUrl() + query;
script.onerror = function () {
self.onClose();
};
*/
WS.prototype.open = function () {
- this.websocket = new WebSocket(this.prepareUrl());
+ var query = io.util.query(this.socket.options.query)
+ , self = this
+ , Socket
+
+ // if node
+ Socket = require('websocket-client').WebSocket;
+ // end node
+
+ if (!Socket) {
+ Socket = window.MozWebSocket || window.WebSocket;
+ }
+
+ this.websocket = new Socket(this.prepareUrl() + query);
- var self = this;
this.websocket.onopen = function () {
self.onOpen();
self.socket.setBuffer(false);
*/
WS.check = function () {
- return 'WebSocket' in window && !('__addTask' in WebSocket);
+ // if node
+ return true;
+ // end node
+ return ('WebSocket' in window && !('__addTask' in WebSocket))
+ || 'MozWebSocket' in window;
};
/**
* MIT Licensed
*/
-(function (exports, io) {
+(function (exports, io, global) {
/**
* Expose constructor.
io.util.inherit(XHRPolling, io.Transport.XHR);
+ /**
+ * Merge the properties from XHR transport
+ */
+
+ io.util.merge(XHRPolling, io.Transport.XHR);
+
/**
* Transport name
*
XHRPolling.prototype.open = function () {
var self = this;
- io.util.defer(function () {
- io.Transport.XHR.prototype.open.call(self);
- });
-
+ io.Transport.XHR.prototype.open.call(self);
return false;
};
this.xhr = this.request();
- if (window.XDomainRequest && this.xhr instanceof XDomainRequest) {
+ if (global.XDomainRequest && this.xhr instanceof XDomainRequest) {
this.xhr.onload = this.xhr.onerror = onload;
} else {
this.xhr.onreadystatechange = stateChange;
}
};
+ /**
+ * Webkit based browsers show a infinit spinner when you start a XHR request
+ * before the browsers onload event is called so we need to defer opening of
+ * the transport until the onload event is called. Wrapping the cb in our
+ * defer method solve this.
+ *
+ * @param {Socket} socket The socket instance that needs a transport
+ * @param {Function} fn The callback
+ * @api private
+ */
+
+ XHRPolling.prototype.ready = function (socket, fn) {
+ var self = this;
+
+ io.util.defer(function () {
+ fn.call(self);
+ });
+ };
+
/**
* Add the transport to your public io.transports array.
*
})(
'undefined' != typeof io ? io.Transport : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
+ , this
);
* MIT Licensed
*/
-(function (exports, io) {
+(function (exports, io, global) {
/**
* Expose constructor.
this.sendXHR = this.request('POST');
- if (window.XDomainRequest && this.sendXHR instanceof XDomainRequest) {
+ if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) {
this.sendXHR.onload = this.sendXHR.onerror = onload;
} else {
this.sendXHR.onreadystatechange = stateChange;
*/
XHR.prototype.request = function (method) {
- var req = io.util.request(this.socket.isXDomain());
- req.open(method || 'GET', this.prepareUrl() + '?t' + (+ new Date));
+ var req = io.util.request(this.socket.isXDomain())
+ , query = io.util.query(this.socket.options.query, 't=' + +new Date);
+
+ req.open(method || 'GET', this.prepareUrl() + query, true);
if (method == 'POST') {
try {
return false;
};
-
+
/**
* Check if the XHR transport supports corss domain requests.
*
})(
'undefined' != typeof io ? io.Transport : module.exports
, 'undefined' != typeof io ? io : module.parent.exports
+ , this
);
* MIT Licensed
*/
-(function (exports) {
+(function (exports, global) {
/**
* Utilities namespace.
, host = uri.host
, port = uri.port;
- if ('undefined' != typeof document) {
+ if ('document' in global) {
host = host || document.domain;
port = port || (protocol == 'https'
&& document.location.protocol !== 'https:' ? 443 : document.location.port);
return (protocol || 'http') + '://' + host + ':' + (port || 80);
};
+ /**
+ * Mergest 2 query strings in to once unique query string
+ *
+ * @param {String} base
+ * @param {String} addition
+ * @api public
+ */
+
+ util.query = function (base, addition) {
+ var query = util.chunkQuery(base || '')
+ , components = [];
+
+ util.merge(query, util.chunkQuery(addition || ''));
+ for (var part in query) {
+ if (query.hasOwnProperty(part)) {
+ components.push(part + '=' + query[part]);
+ }
+ }
+
+ return components.length ? '?' + components.join('&') : '';
+ };
+
+ /**
+ * Transforms a querystring in to an object
+ *
+ * @param {String} qs
+ * @api public
+ */
+
+ util.chunkQuery = function (qs) {
+ var query = {}
+ , params = qs.split('&')
+ , i = 0
+ , l = params.length
+ , kv;
+
+ for (; i < l; ++i) {
+ kv = params[i].split('=');
+ if (kv[0]) {
+ query[kv[0]] = decodeURIComponent(kv[1]);
+ }
+ }
+
+ return query;
+ };
+
/**
* Executes the given function when the page is loaded.
*
var pageLoaded = false;
util.load = function (fn) {
- if (document.readyState === 'complete' || pageLoaded) {
+ if ('document' in global && document.readyState === 'complete' || pageLoaded) {
return fn();
}
- util.on(window, 'load', fn, false);
+ util.on(global, 'load', fn, false);
};
/**
util.on = function (element, event, fn, capture) {
if (element.attachEvent) {
element.attachEvent('on' + event, fn);
- } else {
+ } else if (element.addEventListener) {
element.addEventListener(event, fn, capture);
}
};
*/
util.request = function (xdomain) {
+ // if node
+ var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
+ return new XMLHttpRequest();
+ // end node
+
if ('undefined' != typeof window) {
if (xdomain && window.XDomainRequest) {
return new XDomainRequest();
- };
+ }
if (window.XMLHttpRequest && (!xdomain || util.ua.hasCORS)) {
return new XMLHttpRequest();
- };
+ }
if (!xdomain) {
try {
*/
util.inherit = function (ctor, ctor2) {
- ctor.prototype = new ctor2;
- util.merge(ctor, ctor2);
+ function f() {};
+ f.prototype = ctor2.prototype;
+ ctor.prototype = new f;
};
/**
util.intersect = function (arr, arr2) {
var ret = []
, longest = arr.length > arr2.length ? arr : arr2
- , shortest = arr.length > arr2.length ? arr2 : arr
+ , shortest = arr.length > arr2.length ? arr2 : arr;
for (var i = 0, l = shortest.length; i < l; i++) {
if (~util.indexOf(longest, shortest[i]))
return Array.prototype.indexOf.call(arr, o, i);
}
- for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0
- ; i < j && arr[i] !== o; i++);
+ for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0;
+ i < j && arr[i] !== o; i++);
return j <= i ? -1 : i;
};
util.ua.webkit = 'undefined' != typeof navigator
&& /webkit/i.test(navigator.userAgent);
-})('undefined' != typeof window ? io : module.exports);
+})(
+ 'undefined' != typeof window ? io : module.exports
+ , this
+);
{
"name": "socket.io-client"
, "description": "Socket.IO client for the browser and node.js"
- , "version": "0.7.4"
+ , "version": "0.8.4"
, "main" : "./lib/io.js"
, "browserify": "./dist/socket.io.js"
, "homepage": "http://socket.io"
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
+ , { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
]
, "repository": {
"type": "git"
- , "url": "https://github.com/LearnBoost/Socket.IO.git"
+ , "url": "https://github.com/LearnBoost/socket.io-client.git"
}
, "dependencies": {
- "uglify-js": "1.0.3"
+ "uglify-js": "1.0.6"
+ , "websocket-client": "1.0.0"
+ , "xmlhttprequest": "1.2.2"
}
, "devDependencies": {
"expresso": "0.7.7"
, "express": "2.3.11"
, "jade": "0.12.1"
, "stylus": "0.13.3"
- , "socket.io": "0.7.7"
- , "socket.io-client": "0.7.4"
+ , "socket.io": "0.8.4"
+ , "socket.io-client": "0.8.4"
}
, "engines": { "node": ">= 0.4.0" }
}
--- /dev/null
+
+/**
+ * should.js by TJ Holowaychuk (MIT), adapted to run in browser and node.
+ */
+
+(function (should) {
+
+ if ('undefined' != typeof exports) {
+ module.exports = exports = should = require('assert');
+ }
+
+ /**
+ * Expose constructor.
+ */
+
+ should.Assertion = Assertion;
+
+ /**
+ * Possible assertion flags.
+ */
+
+ var flags = {
+ not: ['be', 'have', 'include']
+ , an: ['instance']
+ , and: ['be', 'have', 'include', 'an']
+ , be: ['an']
+ , have: ['an', 'own']
+ , include: ['an']
+ , not: ['include', 'have', 'be']
+ , own: []
+ , instance: []
+ };
+
+ /**
+ * Extend Object.prototype.
+ */
+
+ if ('object' == typeof process) {
+ Object.defineProperty(
+ Object.prototype
+ , 'should'
+ , {
+ get: function () {
+ var self = this.valueOf()
+ , fn = function () {
+ return new Assertion(self);
+ };
+
+ if ('undefined' != typeof exports) {
+ fn.__proto__ = exports;
+ fn.exports = exports;
+ }
+
+ return fn;
+ }
+ , enumerable: false
+ }
+ );
+ } else {
+ Object.prototype.should = function () {
+ return new Assertion(this.valueOf());
+ };
+ }
+
+ /**
+ * Constructor
+ *
+ * @api private
+ */
+
+ function Assertion (obj) {
+ if (obj !== undefined) {
+ this.obj = obj;
+ this.flags = {};
+
+ var $flags = keys(flags);
+
+ for (var i = 0, l = $flags.length; i < l; i++) {
+ this[$flags[i]] = new FlaggedAssertion(this, $flags[i]);
+ }
+ }
+ };
+
+ /**
+ * Performs an assertion
+ *
+ * @api private
+ */
+
+ Assertion.prototype.assert = function (truth, msg, error) {
+ var msg = this.flags.not ? error : msg
+ , ok = this.flags.not ? !truth : truth;
+
+ if (!ok) {
+ throw new Error(msg);
+ }
+
+ this.flags = {};
+ };
+
+ /**
+ * Checks if the value is true
+ *
+ * @api public
+ */
+
+ Assertion.prototype.be_true = function () {
+ this.assert(
+ this.obj === true
+ , 'expected ' + i(this.obj) + ' to be true'
+ , 'expected ' + i(this.obj) + ' to not be true');
+ return this;
+ };
+
+ /**
+ * Checks if the value is true
+ *
+ * @api public
+ */
+
+ Assertion.prototype.be_false = function () {
+ this.assert(
+ this.obj === false
+ , 'expected ' + i(this.obj) + ' to be false'
+ , 'expected ' + i(this.obj) + ' to not be false'
+ );
+ return this;
+ };
+
+ /**
+ * Check if the value is truthy
+ *
+ * @api public
+ */
+
+ Assertion.prototype.ok = function () {
+ this.assert(
+ this.obj == true
+ , 'expected ' + i(this.obj) + ' to be true'
+ , 'expected ' + i(this.obj) + ' to not be true');
+ };
+
+ /**
+ * Checks if the array is empty.
+ *
+ * @api public
+ */
+
+ Assertion.prototype.empty = function () {
+ this.obj.should().have.property('length');
+ this.assert(
+ 0 === this.obj.length
+ , 'expected ' + i(this.obj) + ' to be empty'
+ , 'expected ' + i(this.obj) + ' to not be empty');
+ return this;
+ };
+
+ /**
+ * Checks if the obj is arguments.
+ *
+ * @api public
+ */
+
+ Assertion.prototype.arguments = function () {
+ this.assert(
+ '[object Arguments]' == Object.prototype.toString.call(this.obj)
+ , 'expected ' + i(this.obj) + ' to be arguments'
+ , 'expected ' + i(this.obj) + ' to not be arguments');
+ return this;
+ };
+
+ /**
+ * Checks if the obj exactly equals another.
+ *
+ * @api public
+ */
+
+ Assertion.prototype.equal = function (obj) {
+ this.assert(
+ obj === this.obj
+ , 'expected ' + i(this.obj) + ' to equal ' + i(obj)
+ , 'expected ' + i(this.obj) + ' to not equal ' + i(obj));
+ return this;
+ };
+
+ /**
+ * Checks if the obj sortof equals another.
+ *
+ * @api public
+ */
+
+ Assertion.prototype.eql = function (obj) {
+ this.assert(
+ should.eql(obj, this.obj)
+ , 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj)
+ , 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj));
+ return this;
+ };
+
+ /**
+ * Assert within start to finish (inclusive).
+ *
+ * @param {Number} start
+ * @param {Number} finish
+ * @api public
+ */
+
+ Assertion.prototype.within = function (start, finish) {
+ var range = start + '..' + finish;
+ this.assert(
+ this.obj >= start && this.obj <= finish
+ , 'expected ' + i(this.obj) + ' to be within ' + range
+ , 'expected ' + i(this.obj) + ' to not be within ' + range);
+ return this;
+ };
+
+ /**
+ * Assert typeof.
+ *
+ * @api public
+ */
+
+ Assertion.prototype.a = function (type) {
+ this.assert(
+ type == typeof this.obj
+ , 'expected ' + i(this.obj) + ' to be a ' + type
+ , 'expected ' + i(this.obj) + ' not to be a ' + type);
+ return this;
+ };
+
+ /**
+ * Assert instanceof.
+ *
+ * @api public
+ */
+
+ Assertion.prototype.of = function (constructor) {
+ var name = constructor.name;
+ this.assert(
+ this.obj instanceof constructor
+ , 'expected ' + i(this.obj) + ' to be an instance of ' + name
+ , 'expected ' + i(this.obj) + ' not to be an instance of ' + name);
+ return this;
+ };
+
+ /**
+ * Assert numeric value above _n_.
+ *
+ * @param {Number} n
+ * @api public
+ */
+
+ Assertion.prototype.greaterThan =
+ Assertion.prototype.above = function (n) {
+ this.assert(
+ this.obj > n
+ , 'expected ' + i(this.obj) + ' to be above ' + n
+ , 'expected ' + i(this.obj) + ' to be below ' + n);
+ return this;
+ };
+
+ /**
+ * Assert numeric value below _n_.
+ *
+ * @param {Number} n
+ * @api public
+ */
+
+ Assertion.prototype.lessThan =
+ Assertion.prototype.below = function (n) {
+ this.assert(
+ this.obj < n
+ , 'expected ' + i(this.obj) + ' to be below ' + n
+ , 'expected ' + i(this.obj) + ' to be above ' + n);
+ return this;
+ };
+
+ /**
+ * Assert string value matches _regexp_.
+ *
+ * @param {RegExp} regexp
+ * @api public
+ */
+
+ Assertion.prototype.match = function (regexp) {
+ this.assert(
+ regexp.exec(this.obj)
+ , 'expected ' + i(this.obj) + ' to match ' + regexp
+ , 'expected ' + i(this.obj) + ' not to match ' + regexp);
+ return this;
+ };
+
+ /**
+ * Assert property "length" exists and has value of _n_.
+ *
+ * @param {Number} n
+ * @api public
+ */
+
+ Assertion.prototype.length = function (n) {
+ this.obj.should().have.property('length');
+ var len = this.obj.length;
+ this.assert(
+ n == len
+ , 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len
+ , 'expected ' + i(this.obj) + ' to not have a length of ' + len);
+ return this;
+ };
+
+ /**
+ * Assert substring.
+ *
+ * @param {String} str
+ * @api public
+ */
+
+ Assertion.prototype.string = function(str){
+ this.obj.should().be.a('string');
+ this.assert(
+ ~this.obj.indexOf(str)
+ , 'expected ' + i(this.obj) + ' to include ' + i(str)
+ , 'expected ' + i(this.obj) + ' to not include ' + i(str));
+ return this;
+ };
+
+ /**
+ * Assert inclusion of object.
+ *
+ * @param {Object} obj
+ * @api public
+ */
+
+ Assertion.prototype.object = function(obj){
+ this.obj.should().be.a('object');
+ var included = true;
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key) && !should.eql(obj[key], this.obj[key])) {
+ included = false;
+ break;
+ }
+ }
+ this.assert(
+ included
+ , 'expected ' + i(this.obj) + ' to include ' + i(obj)
+ , 'expected ' + i(this.obj) + ' to not include ' + i(obj));
+ return this;
+ };
+
+ /**
+ * Assert property _name_ exists, with optional _val_.
+ *
+ * @param {String} name
+ * @param {Mixed} val
+ * @api public
+ */
+
+ Assertion.prototype.property = function (name, val) {
+ if (this.flags.own) {
+ this.assert(
+ this.obj.hasOwnProperty(name)
+ , 'expected ' + i(this.obj) + ' to have own property ' + i(name)
+ , 'expected ' + i(this.obj) + ' to not have own property ' + i(name));
+ return this;
+ }
+
+ if (this.flags.not && undefined !== val) {
+ if (undefined === this.obj[name]) {
+ throw new Error(i(this.obj) + ' has no property ' + i(name));
+ }
+ } else {
+ this.assert(
+ undefined !== this.obj[name]
+ , 'expected ' + i(this.obj) + ' to have a property ' + i(name)
+ , 'expected ' + i(this.obj) + ' to not have a property ' + i(name));
+ }
+
+ if (undefined !== val) {
+ this.assert(
+ val === this.obj[name]
+ , 'expected ' + i(this.obj) + ' to have a property ' + i(name)
+ + ' of ' + i(val) + ', but got ' + i(this.obj[name])
+ , 'expected ' + i(this.obj) + ' to not have a property ' + i(name)
+ + ' of ' + i(val));
+ }
+
+ this.obj = this.obj[name];
+ return this;
+ };
+
+ /**
+ * Assert that the array contains _obj_.
+ *
+ * @param {Mixed} obj
+ * @api public
+ */
+
+ Assertion.prototype.contain = function (obj) {
+ this.obj.should().be.an.instance.of(Array);
+ this.assert(
+ ~indexOf(this.obj, obj)
+ , 'expected ' + i(this.obj) + ' to contain ' + i(obj)
+ , 'expected ' + i(this.obj) + ' to not contain ' + i(obj));
+ return this;
+ };
+
+ /**
+ * Assert exact keys or inclusion of keys by using
+ * the `.include` modifier.
+ *
+ * @param {Array|String ...} keys
+ * @api public
+ */
+
+ Assertion.prototype.key =
+ Assertion.prototype.keys = function (keys) {
+ var str
+ , ok = true;
+
+ keys = keys instanceof Array
+ ? keys
+ : Array.prototype.slice.call(arguments);
+
+ if (!keys.length) throw new Error('keys required');
+
+ var actual = keys(this.obj)
+ , len = keys.length;
+
+ // Inclusion
+ ok = every(keys, function(key){
+ return ~indexOf(actual, key);
+ });
+
+ // Strict
+ if (!this.flags.not && !this.flags.include) {
+ ok = ok && keys.length == actual.length;
+ }
+
+ // Key string
+ if (len > 1) {
+ keys = map(keys, function(key){
+ return i(key);
+ });
+ var last = keys.pop();
+ str = keys.join(', ') + ', and ' + last;
+ } else {
+ str = i(keys[0]);
+ }
+
+ // Form
+ str = (len > 1 ? 'keys ' : 'key ') + str;
+
+ // Have / include
+ str = (this.flag.include ? 'include ' : 'have ') + str;
+
+ // Assertion
+ this.assert(
+ ok
+ , 'expected ' + i(this.obj) + ' to ' + str
+ , 'expected ' + i(this.obj) + ' to not ' + str);
+
+ return this;
+ };
+
+ /**
+ * Assertion with a flag.
+ *
+ * @api private
+ */
+
+ function FlaggedAssertion (parent, flag) {
+ this.parent = parent;
+ this.obj = parent.obj;
+
+ this.flag = flag;
+ this.flags = {};
+ this.flags[flag] = true;
+
+ for (var i in parent.flags) {
+ if (parent.flags.hasOwnProperty(i)) {
+ this.flags[i] = true;
+ }
+ }
+
+ var $flags = flags[flag];
+
+ for (var i = 0, l = $flags.length; i < l; i++) {
+ this[$flags[i]] = new FlaggedAssertion(this, $flags[i]);
+ }
+ };
+
+ /**
+ * Inherits from assertion
+ */
+
+ FlaggedAssertion.prototype = new Assertion;
+
+ /**
+ * Array every compatibility
+ *
+ * @see bit.ly/5Fq1N2
+ * @api public
+ */
+
+ function every (arr, fn, thisObj) {
+ var scope = thisObj || window;
+ for (var i = 0, j = arr.length; i < j; ++i) {
+ if (!fn.call(scope, arr[i], i, arr)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ /**
+ * Array indexOf compatibility.
+ *
+ * @see bit.ly/a5Dxa2
+ * @api public
+ */
+
+ function indexOf (arr, o, i) {
+ if (Array.prototype.indexOf) {
+ return Array.prototype.indexOf.call(arr, o, i);
+ }
+
+ for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0
+ ; i < j && arr[i] !== o; i++);
+
+ return j <= i ? -1 : i;
+ };
+
+ /**
+ * Inspects an object.
+ *
+ * @see taken from node.js `util` module (copyright Joyent, MIT license)
+ * @api private
+ */
+
+ function i (obj, showHidden, depth) {
+ var seen = [];
+
+ function stylize (str) {
+ return str;
+ };
+
+ function format (value, recurseTimes) {
+ // Provide a hook for user-specified inspect functions.
+ // Check that value is an object with an inspect function on it
+ if (value && typeof value.inspect === 'function' &&
+ // Filter out the util module, it's inspect function is special
+ value !== exports &&
+ // Also filter out any prototype objects using the circular check.
+ !(value.constructor && value.constructor.prototype === value)) {
+ return value.inspect(recurseTimes);
+ }
+
+ // Primitive types cannot have properties
+ switch (typeof value) {
+ case 'undefined':
+ return stylize('undefined', 'undefined');
+
+ case 'string':
+ var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '')
+ .replace(/'/g, "\\'")
+ .replace(/\\"/g, '"') + '\'';
+ return stylize(simple, 'string');
+
+ case 'number':
+ return stylize('' + value, 'number');
+
+ case 'boolean':
+ return stylize('' + value, 'boolean');
+ }
+ // For some reason typeof null is "object", so special case here.
+ if (value === null) {
+ return stylize('null', 'null');
+ }
+
+ // Look up the keys of the object.
+ var visible_keys = keys(value);
+ var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys;
+
+ // Functions without properties can be shortcutted.
+ if (typeof value === 'function' && $keys.length === 0) {
+ if (isRegExp(value)) {
+ return stylize('' + value, 'regexp');
+ } else {
+ var name = value.name ? ': ' + value.name : '';
+ return stylize('[Function' + name + ']', 'special');
+ }
+ }
+
+ // Dates without properties can be shortcutted
+ if (isDate(value) && $keys.length === 0) {
+ return stylize(value.toUTCString(), 'date');
+ }
+
+ var base, type, braces;
+ // Determine the object type
+ if (isArray(value)) {
+ type = 'Array';
+ braces = ['[', ']'];
+ } else {
+ type = 'Object';
+ braces = ['{', '}'];
+ }
+
+ // Make functions say that they are functions
+ if (typeof value === 'function') {
+ var n = value.name ? ': ' + value.name : '';
+ base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']';
+ } else {
+ base = '';
+ }
+
+ // Make dates with properties first say the date
+ if (isDate(value)) {
+ base = ' ' + value.toUTCString();
+ }
+
+ if ($keys.length === 0) {
+ return braces[0] + base + braces[1];
+ }
+
+ if (recurseTimes < 0) {
+ if (isRegExp(value)) {
+ return stylize('' + value, 'regexp');
+ } else {
+ return stylize('[Object]', 'special');
+ }
+ }
+
+ seen.push(value);
+
+ var output = map($keys, function(key) {
+ var name, str;
+ if (value.__lookupGetter__) {
+ if (value.__lookupGetter__(key)) {
+ if (value.__lookupSetter__(key)) {
+ str = stylize('[Getter/Setter]', 'special');
+ } else {
+ str = stylize('[Getter]', 'special');
+ }
+ } else {
+ if (value.__lookupSetter__(key)) {
+ str = stylize('[Setter]', 'special');
+ }
+ }
+ }
+ if (indexOf(visible_keys, key) < 0) {
+ name = '[' + key + ']';
+ }
+ if (!str) {
+ if (indexOf(seen, value[key]) < 0) {
+ if (recurseTimes === null) {
+ str = format(value[key]);
+ } else {
+ str = format(value[key], recurseTimes - 1);
+ }
+ if (str.indexOf('\n') > -1) {
+ if (isArray(value)) {
+ str = map(str.split('\n'), function(line) {
+ return ' ' + line;
+ }).join('\n').substr(2);
+ } else {
+ str = '\n' + map(str.split('\n'), function(line) {
+ return ' ' + line;
+ }).join('\n');
+ }
+ }
+ } else {
+ str = stylize('[Circular]', 'special');
+ }
+ }
+ if (typeof name === 'undefined') {
+ if (type === 'Array' && key.match(/^\d+$/)) {
+ return str;
+ }
+ name = json.stringify('' + key);
+ if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
+ name = name.substr(1, name.length - 2);
+ name = stylize(name, 'name');
+ } else {
+ name = name.replace(/'/g, "\\'")
+ .replace(/\\"/g, '"')
+ .replace(/(^"|"$)/g, "'");
+ name = stylize(name, 'string');
+ }
+ }
+
+ return name + ': ' + str;
+ });
+
+ seen.pop();
+
+ var numLinesEst = 0;
+ var length = reduce(output, function(prev, cur) {
+ numLinesEst++;
+ if (indexOf(cur, '\n') >= 0) numLinesEst++;
+ return prev + cur.length + 1;
+ }, 0);
+
+ if (length > 50) {
+ output = braces[0] +
+ (base === '' ? '' : base + '\n ') +
+ ' ' +
+ output.join(',\n ') +
+ ' ' +
+ braces[1];
+
+ } else {
+ output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+ }
+
+ return output;
+ }
+ return format(obj, (typeof depth === 'undefined' ? 2 : depth));
+ };
+
+ function isArray (ar) {
+ return ar instanceof Array ||
+ Object.prototype.toString.call(ar) == '[object Array]';
+ };
+
+ function isRegExp(re) {
+ var s = '' + re;
+ return re instanceof RegExp || // easy case
+ // duck-type for context-switching evalcx case
+ typeof(re) === 'function' &&
+ re.constructor.name === 'RegExp' &&
+ re.compile &&
+ re.test &&
+ re.exec &&
+ s.match(/^\/.*\/[gim]{0,3}$/);
+ };
+
+ function isDate(d) {
+ if (d instanceof Date) return true;
+ return false;
+ };
+
+ function keys (obj) {
+ if (Object.keys) {
+ return Object.keys(obj);
+ }
+
+ var keys = [];
+
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ keys.push(i);
+ }
+ }
+
+ return keys;
+ }
+
+ function map (arr, mapper, that) {
+ if (Array.prototype.map) {
+ return Array.prototype.map.call(arr, mapper, that);
+ }
+
+ var other= new Array(arr.length);
+
+ for (var i= 0, n = arr.length; i<n; i++)
+ if (i in arr)
+ other[i] = mapper.call(that, arr[i], i, arr);
+
+ return other;
+ };
+
+ function reduce (arr, fun) {
+ if (Array.prototype.reduce) {
+ return Array.prototype.reduce.apply(
+ arr
+ , Array.prototype.slice.call(arguments, 1)
+ );
+ }
+
+ var len = +this.length;
+
+ if (typeof fun !== "function")
+ throw new TypeError();
+
+ // no value to return if no initial value and an empty array
+ if (len === 0 && arguments.length === 1)
+ throw new TypeError();
+
+ var i = 0;
+ if (arguments.length >= 2) {
+ var rv = arguments[1];
+ } else {
+ do {
+ if (i in this) {
+ rv = this[i++];
+ break;
+ }
+
+ // if array contains no values, no initial value to return
+ if (++i >= len)
+ throw new TypeError();
+ } while (true);
+ }
+
+ for (; i < len; i++) {
+ if (i in this)
+ rv = fun.call(null, rv, this[i], i, this);
+ }
+
+ return rv;
+ };
+
+ /**
+ * Strict equality
+ *
+ * @api public
+ */
+
+ should.equal = function (a, b) {
+ if (a !== b) {
+ should.fail('expected ' + i(a) + ' to equal ' + i(b));
+ }
+ };
+
+ /**
+ * Fails with msg
+ *
+ * @param {String} msg
+ * @api public
+ */
+
+ should.fail = function (msg) {
+ throw new Error(msg);
+ };
+
+ /**
+ * Asserts deep equality
+ *
+ * @see taken from node.js `assert` module (copyright Joyent, MIT license)
+ * @api private
+ */
+
+ should.eql = function eql (actual, expected) {
+ // 7.1. All identical values are equivalent, as determined by ===.
+ if (actual === expected) {
+ return true;
+ } else if ('undefined' != typeof Buffer
+ && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
+ if (actual.length != expected.length) return false;
+
+ for (var i = 0; i < actual.length; i++) {
+ if (actual[i] !== expected[i]) return false;
+ }
+
+ return true;
+
+ // 7.2. If the expected value is a Date object, the actual value is
+ // equivalent if it is also a Date object that refers to the same time.
+ } else if (actual instanceof Date && expected instanceof Date) {
+ return actual.getTime() === expected.getTime();
+
+ // 7.3. Other pairs that do not both pass typeof value == "object",
+ // equivalence is determined by ==.
+ } else if (typeof actual != 'object' && typeof expected != 'object') {
+ return actual == expected;
+
+ // 7.4. For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical "prototype" property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ } else {
+ return objEquiv(actual, expected);
+ }
+ }
+
+ function isUndefinedOrNull (value) {
+ return value === null || value === undefined;
+ }
+
+ function isArguments (object) {
+ return Object.prototype.toString.call(object) == '[object Arguments]';
+ }
+
+ function objEquiv (a, b) {
+ if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
+ return false;
+ // an identical "prototype" property.
+ if (a.prototype !== b.prototype) return false;
+ //~~~I've managed to break Object.keys through screwy arguments passing.
+ // Converting to array solves the problem.
+ if (isArguments(a)) {
+ if (!isArguments(b)) {
+ return false;
+ }
+ a = pSlice.call(a);
+ b = pSlice.call(b);
+ return should.eql(a, b);
+ }
+ try{
+ var ka = keys(a),
+ kb = keys(b),
+ key, i;
+ } catch (e) {//happens when one is a string literal and the other isn't
+ return false;
+ }
+ // having the same number of owned properties (keys incorporates hasOwnProperty)
+ if (ka.length != kb.length)
+ return false;
+ //the same set of keys (although not necessarily the same order),
+ ka.sort();
+ kb.sort();
+ //~~~cheap key test
+ for (i = ka.length - 1; i >= 0; i--) {
+ if (ka[i] != kb[i])
+ return false;
+ }
+ //equivalent values for every corresponding key, and
+ //~~~possibly expensive deep test
+ for (i = ka.length - 1; i >= 0; i--) {
+ key = ka[i];
+ if (!should.eql(a[key], b[key]))
+ return false;
+ }
+ return true;
+ }
+
+ var json = (function () {
+ "use strict";
+
+ if ('object' == typeof JSON && JSON.parse && JSON.stringify) {
+ return {
+ parse: nativeJSON.parse
+ , stringify: nativeJSON.stringify
+ }
+ }
+
+ var JSON = {};
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ function date(d, key) {
+ return isFinite(d.valueOf()) ?
+ d.getUTCFullYear() + '-' +
+ f(d.getUTCMonth() + 1) + '-' +
+ f(d.getUTCDate()) + 'T' +
+ f(d.getUTCHours()) + ':' +
+ f(d.getUTCMinutes()) + ':' +
+ f(d.getUTCSeconds()) + 'Z' : null;
+ };
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+ // If the string contains no control characters, no quote characters, and no
+ // backslash characters, then we can safely slap some quotes around it.
+ // Otherwise we must also replace the offending characters with safe escape
+ // sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' : '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+ // Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+ // If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value instanceof Date) {
+ value = date(key);
+ }
+
+ // If we were called with a replacer function, then call the replacer to
+ // obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+ // What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+ // JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+ // If the value is a boolean or null, convert it to a string. Note:
+ // typeof null does not produce 'null'. The case is included here in
+ // the remote chance that this gets fixed someday.
+
+ return String(value);
+
+ // If the type is 'object', we might be dealing with an object or an array or
+ // null.
+
+ case 'object':
+
+ // Due to a specification blunder in ECMAScript, typeof null is 'object',
+ // so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+ // Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+ // Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+ // The value is an array. Stringify every element. Use null as a placeholder
+ // for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+ // Join all of the elements together, separated with commas, and wrap them in
+ // brackets.
+
+ v = partial.length === 0 ? '[]' : gap ?
+ '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+ // If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ if (typeof rep[i] === 'string') {
+ k = rep[i];
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+ // Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+ // Join all of the member texts together, separated with commas,
+ // and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' : gap ?
+ '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
+ '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+ // If the JSON object does not yet have a stringify method, give it one.
+
+ JSON.stringify = function (value, replacer, space) {
+
+ // The stringify method takes a value and an optional replacer, and an optional
+ // space parameter, and returns a JSON text. The replacer can be a function
+ // that can replace values, or an array of strings that will select the keys.
+ // A default replacer method can be provided. Use of the space parameter can
+ // produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+ // If the space parameter is a number, make an indent string containing that
+ // many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+ // If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+ // If there is a replacer, it must be a function or an array.
+ // Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+ // Make a fake root object containing our value under the key of ''.
+ // Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+
+ // If the JSON object does not yet have a parse method, give it one.
+
+ JSON.parse = function (text, reviver) {
+ // The parse method takes a text and an optional reviver function, and returns
+ // a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+ // The walk method is used to recursively walk the resulting structure so
+ // that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+ // Parsing happens in four stages. In the first stage, we replace certain
+ // Unicode characters with escape sequences. JavaScript handles many characters
+ // incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+ // In the second stage, we run the text against regular expressions that look
+ // for non-JSON patterns. We are especially concerned with '()' and 'new'
+ // because they can cause invocation, and '=' because it can cause mutation.
+ // But just to be safe, we want to reject all unexpected forms.
+
+ // We split the second stage into 4 regexp operations in order to work around
+ // crippling inefficiencies in IE's and Safari's regexp engines. First we
+ // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+ // replace all simple value tokens with ']' characters. Third, we delete all
+ // open brackets that follow a colon or comma or that begin the text. Finally,
+ // we look to see that the remaining characters are only whitespace or ']' or
+ // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+ // In the third stage we use the eval function to compile the text into a
+ // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+ // in JavaScript: it can begin a block or an object literal. We wrap the text
+ // in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+ // In the optional fourth stage, we recursively walk the new structure, passing
+ // each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+ // If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+
+ return JSON;
+ })();
+
+})('undefined' != typeof exports ? exports : (should = {}));
--- /dev/null
+
+/**
+ * Module dependencies.
+ */
+
+var express = require('express')
+ , stylus = require('stylus')
+ , sio = require('socket.io')
+ , path = require('path')
+ , fs = require('fs');
+
+/**
+ * App.
+ */
+
+var app = express.createServer();
+
+/**
+ * Initial port to listen to.
+ */
+
+var port = 3000;
+
+/**
+ * Transport to test with.
+ */
+
+var args = process.argv.slice(2)
+ , transport = args.length ? args[0] : 'xhr-polling';
+
+/**
+ * A map of tests to socket.io ports we're listening on.
+ */
+
+var testsPorts = {};
+
+/**
+ * App configuration.
+ */
+
+app.configure(function () {
+ app.use(stylus.middleware({ src: __dirname + '/public' }))
+ app.use(express.static(__dirname + '/public'));
+ app.set('views', __dirname);
+ app.set('view engine', 'jade');
+});
+
+/**
+ * App routes.
+ */
+
+app.get('/', function (req, res) {
+ res.render('index', {
+ layout: false
+ , testsPorts: testsPorts
+ });
+});
+
+/**
+ * Sends test files.
+ */
+
+app.get('/test/:file', function (req, res) {
+ res.sendfile(path.normalize(__dirname + '/../../test/' + req.params.file));
+});
+
+/**
+ * App listen.
+ */
+
+app.listen(port++, function () {
+ var addr = app.address();
+ console.error(' listening on http://' + addr.address + ':' + addr.port);
+});
+
+/**
+ * Override handler to simplify development
+ */
+
+function handler (req, res) {
+ fs.readFile(__dirname + '/../../dist/socket.io.js', 'utf8', function (err, b) {
+ if (err) {
+ res.writeHead(404);
+ res.end('Error');
+ return;
+ }
+
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
+ res.end(b);
+ });
+};
+
+/**
+ * Socket.IO default server (to serve client)
+ */
+
+var io = sio.listen(app);
+
+io.configure(function () {
+ io.set('browser client handler', handler);
+ io.set('transports', [
+ transport
+ ]);
+});
+
+/**
+ * Scopes servers for a given test suite.
+ */
+
+var currentSuite;
+
+function suite (name, fn) {
+ currentSuite = testsPorts[name] = {};
+ fn();
+};
+
+/**
+ * Creates a socket io server
+ */
+
+function server (name, fn) {
+ currentSuite[name] = port;
+
+ var io = sio.listen(port);
+ io.configure(function () {
+ io.set('transports', [transport]);
+ });
+
+ fn(io);
+ port++;
+};
+
+/**
+ * Socket.IO servers.
+ */
+
+suite('socket.test.js', function () {
+
+ server('test connecting the socket and disconnecting', function (io) {
+ io.sockets.on('connection', function () {});
+ });
+
+ server('test receiving messages', function (io) {
+ io.sockets.on('connection', function (socket) {
+ var messages = 0;
+ var interval = setInterval(function () {
+ socket.send(++messages);
+
+ if (messages == 3) {
+ clearInterval(interval);
+ setTimeout(function () {
+ socket.disconnect();
+ }, 500);
+ }
+ }, 50);
+ });
+ });
+
+ server('test sending messages', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ socket.send(msg);
+ });
+ });
+ });
+
+ server('test acks sent from client', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.send('tobi', function () {
+ socket.send('tobi 2');
+ });
+ });
+ });
+
+ server('test acks sent from server', function (io) {
+ io.sockets.on('connection', function (socket) {});
+ });
+
+ server('test connecting to namespaces', function (io) {
+ io.of('/woot').on('connection', function (socket) {
+ socket.send('connected to woot');
+ });
+
+ io.of('/chat').on('connection', function (socket) {
+ socket.send('connected to chat');
+ });
+ });
+
+ server('test disconnecting from namespaces', function (io) {
+ io.of('/a').on('connection', function (socket) {});
+ io.of('/b').on('connection', function (socket) {});
+ });
+
+ server('test authorizing for namespaces', function (io) {
+ io.of('/a')
+ .authorization(function (data, fn) {
+ fn(null, false);
+ })
+ .on('connection', function (socket) {});
+ });
+
+ server('test sending json from server', function (io) {
+ io.sockets.on('connection', function (socket) {
+ io.sockets.json.send(3141592);
+ });
+ });
+
+ server('test sending json from client', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (arr) {
+ if (Array.isArray(arr) && arr.length == 3) {
+ socket.send('echo');
+ }
+ });
+ });
+ });
+
+ server('test emitting an event from server', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.emit('woot');
+ });
+ });
+
+ server('test emitting multiple events at once to the server', function (io) {
+ io.sockets.on('connection', function (socket) {
+ var messages = [];
+
+ socket.on('print', function (msg) {
+ if (messages.indexOf(msg) >= 0) {
+ console.error('duplicate message');
+ }
+
+ messages.push(msg);
+ if (messages.length == 2) {
+ socket.emit('done');
+ }
+ });
+ });
+ });
+
+ server('test emitting an event to server', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.on('woot', function () {
+ socket.emit('echo');
+ });
+ });
+ });
+
+ server('test emitting an event from server and sending back data', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.emit('woot', 1, function (a) {
+ if (a === 'test') {
+ socket.emit('done');
+ }
+ });
+ });
+ });
+
+ server('test emitting an event to server and sending back data', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.on('tobi', function (a, b, fn) {
+ if (a === 1 && b === 2) {
+ fn({ hello: 'world' });
+ }
+ });
+ });
+ });
+
+ server('test encoding a payload', function (io) {
+ io.of('/woot').on('connection', function (socket) {
+ var count = 0;
+
+ socket.on('message', function (a) {
+ if (a == 'ñ') {
+ if (++count == 4) {
+ socket.emit('done');
+ }
+ }
+ });
+ });
+ });
+
+ server('test sending query strings to the server', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.json.send(socket.handshake);
+ })
+ });
+
+ server('test sending newline', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ if (msg == '\n') {
+ socket.emit('done');
+ }
+ });
+ });
+ });
+
+ server('test sending unicode', function (io) {
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ if (msg.test == "\u2028") {
+ socket.emit('done');
+ }
+ });
+ });
+ });
+
+});
--- /dev/null
+doctype 5
+html
+ head
+ link(href='/stylesheets/main.css', rel='stylesheet', media='all')
+ script(src='/socket.io/socket.io.js')
+ script(src='/javascript/jquery.js')
+ script(src='/javascript/should.js')
+ script(src='/javascript/script.js')
+ script(src='/javascript/runner.js')
+
+ - var json = JSON.stringify(testsPorts);
+ script
+ var testsPorts = !{json}
+
+ script
+ $(function () {
+ run(
+ 'io.test.js'
+ , 'parser.test.js'
+ , 'util.test.js'
+ , 'events.test.js'
+ , 'socket.test.js'
+ );
+ });
+
+ title Socket.IO tests runner
+ body
+ h2 Socket.IO test runner
--- /dev/null
+/*!
+ * jQuery JavaScript Library v1.6.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu May 12 15:04:36 2011 -0400
+ */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(y,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function L(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function F(){return!0}function E(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.1",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;a.setAttribute("className","t"),a.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};f=c.createElement("select"),g=f.appendChild(c.createElement("option")),h=a.getElementsByTagName("input")[0],j={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},h.checked=!0,j.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,j.optDisabled=!g.disabled;try{delete a.test}catch(s){j.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function b(){j.noCloneEvent=!1,a.detachEvent("onclick",b)}),a.cloneNode(!0).fireEvent("onclick")),h=c.createElement("input"),h.value="t",h.setAttribute("type","radio"),j.radioValue=h.value==="t",h.setAttribute("checked","checked"),a.appendChild(h),k=c.createDocumentFragment(),k.appendChild(a.firstChild),j.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",l=c.createElement("body"),m={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(q in m)l.style[q]=m[q];l.appendChild(a),b.insertBefore(l,b.firstChild),j.appendChecked=h.checked,j.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,j.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",j.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",n=a.getElementsByTagName("td"),r=n[0].offsetHeight===0,n[0].style.display="",n[1].style.display="none",j.reliableHiddenOffsets=r&&n[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(i=c.createElement("div"),i.style.width="0",i.style.marginRight="0",a.appendChild(i),j.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(i,null)||{marginRight:0}).marginRight,10)||0)===0),l.innerHTML="",b.removeChild(l);if(a.attachEvent)for(q in{submit:1,change:1,focusin:1})p="on"+q,r=p in a,r||(a.setAttribute(p,"return;"),r=typeof a[p]=="function"),j[q+"Bubbles"]=r;return j}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c],i||(!t.test(c)||typeof d!="boolean"&&d!==b&&d.toLowerCase()!==c.toLowerCase()?v&&(f.nodeName(a,"form")||u.test(c))&&(i=v):i=w);if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return a[f.propFix[c]||c]?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=b),a.setAttribute(c,c.toLowerCase()));return c}},f.attrHooks.value={get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return a.value},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=Object.prototype.hasOwnProperty,y=/\.(.*)$/,z=/^(?:textarea|input|select)$/i,A=/\./g,B=/ /g,C=/[^\w\s.|`]/g,D=function(a){return a.replace(C,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=E;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=E);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),D).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem
+)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=J(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:K,beforedeactivate:K,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&K.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&K.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",J(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in I)f.event.add(this,c+".specialChange",I[c]);return z.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return z.test(this.nodeName)}},I=f.event.special.change.filters,I.focus=I.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var M={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||E,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=y.exec(h),k="",j&&(k=j[0],h=h.replace(y,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,M[h]?(a.push(M[h]+k),h=h+k):h=(M[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+O(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+O(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var P=/Until$/,Q=/^(?:parents|prevUntil|prevAll)/,R=/,/,S=/^.[^:#\[\.,]*$/,T=Array.prototype.slice,U=f.expr.match.POS,V={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(X(this,a,!1),"not",a)},filter:function(a){return this.pushStack(X(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=U.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=U.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(W(c[0])||W(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=T.call(arguments);P.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!V[a]?f.unique(e):e,(this.length>1||R.test(d))&&Q.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y=/ jQuery\d+="(?:\d+|null)"/g,Z=/^\s+/,$=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,_=/<([\w:]+)/,ba=/<tbody/i,bb=/<|&#?\w+;/,bc=/<(?:script|object|embed|option|style)/i,bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Y,""):null;if(typeof a=="string"&&!bc.test(a)&&(f.support.leadingWhitespace||!Z.test(a))&&!bg[(_.exec(a)||["",""])[1].toLowerCase()]){a=a.replace($,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bh(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bn)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bc.test(a[0])&&(f.support.checkClone||!bd.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bj(a,d),e=bk(a),g=bk(d);for(h=0;e[h];++h)bj(e[h],g[h])}if(b){bi(a,d);if(c){e=bk(a),g=bk(d);for(h=0;e[h];++h)bi(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||
+b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bE=/%20/g,bF=/\[\]$/,bG=/\r?\n/g,bH=/#.*$/,bI=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bJ=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bK=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bL=/^(?:GET|HEAD)$/,bM=/^\/\//,bN=/\?/,bO=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bP=/^(?:select|textarea)/i,bQ=/\s+/,bR=/([?&])_=[^&]*/,bS=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bT=f.fn.load,bU={},bV={},bW,bX;try{bW=e.href}catch(bY){bW=c.createElement("a"),bW.href="",bW=bW.href}bX=bS.exec(bW.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bT)return bT.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bO,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bP.test(this.nodeName)||bJ.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bG,"\r\n")}}):{name:b.name,value:c.replace(bG,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bW,isLocal:bK.test(bX[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bZ(bU),ajaxTransport:bZ(bV),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?ca(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=cb(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bI.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bH,"").replace(bM,bX[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bQ),d.crossDomain==null&&(r=bS.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bX[1]&&r[2]==bX[2]&&(r[3]||(r[1]==="http:"?80:443))==(bX[3]||(bX[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bU,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bL.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bN.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bR,"$1_="+x);d.url=y+(y===d.url?(bN.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bV,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bE,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq,cr=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cv(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cm.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cn.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cq||cs(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!co&&(cr?(co=1,g=function(){co&&(cr(g),e.tick())},cr(g)):co=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cq||cs(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
--- /dev/null
+// useful globals
+
+var currentSuite, currentCase;
+
+// loads common.js module
+function load (test, fn) {
+ module = {};
+ $script('/test/' + test, function () {
+ fn(module.exports);
+ });
+};
+
+// load all tests
+function run () {
+ var tests = Array.prototype.slice.call(arguments)
+ , i = 0;
+
+ function complete () {
+ $('body').append('<p>All suites completed</p>');
+ };
+
+ if (tests.length) {
+ // load dom
+ $('body').append('<ul class="test-list">');
+
+ // run suites
+ suite(tests[i], function check (res) {
+ if (tests[++i]) {
+ suite(tests[i], check);
+ } else {
+ complete();
+ }
+ });
+ } else {
+ complete();
+ }
+};
+
+// gets keys for an object
+function keys (obj) {
+ if (Object.keys) return Object.keys(obj);
+
+ var keys = [];
+
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ keys.push(i);
+ }
+ }
+
+ return keys;
+};
+
+// runs a suite
+function suite (file, fn) {
+ var passed = {}
+ , failed = {};
+
+ // register current suite
+ currentSuite = file;
+
+ // inject test case
+ var li = $('<li class="loading">').append(
+ $('<span class="name">').append(
+ $('<a>').attr('href', '/test/' + file).text(file)
+ )
+ ).appendTo('.test-list');
+
+ // dynamically load module
+ load(file, function (suite) {
+ if (!suite) return;
+
+ var start = new Date;
+
+ function complete () {
+ var ok = !keys(failed).length
+ , elapsed = Math.round((new Date - start) / 1000);
+
+ // update dom
+ li.removeClass('loading');
+ li.append('<span class="bullet">•</span>');
+
+ if (ok) {
+ li.append(' all passed');
+ } else {
+ li.append(' failing test');
+ li.addClass('test-failure');
+ }
+
+ li.append(
+ $('<div class="details">')
+ .html(
+ 'Passed: ' + keys(passed).length
+ + ' — Failed: <em>' + keys(failed).length
+ + '</em> — Elapsed: <em>' + elapsed
+ + '</em> seconds — '
+ )
+ .append(
+ $('<a>Show details</a>')
+ .attr('href', '#')
+ .click(function () {
+ li.toggleClass('cases');
+ $(this).text(
+ li.hasClass('cases')
+ ? 'Hide details'
+ : 'Show details'
+ );
+ return false;
+ })
+ )
+ );
+
+ var casesUl = $('<ul class="cases">').appendTo(li);
+ for (var i = 0, l = cases.length; i < l; i++) {
+ var detail = $('<li>')
+ .text(cases[i])
+ .addClass(failed[cases[i]] ? 'failed' : '')
+ .appendTo(casesUl);
+
+ if (failed[cases[i]]) {
+ if (window.console && console.log) {
+ console.log(failed[cases[i]]);
+ }
+
+ detail.append($('<span class="error">').text(String(failed[cases[i]])));
+ }
+ }
+
+ // fire callback
+ fn({
+ status: ok
+ , passed: passed
+ , failed: failed
+ });
+ };
+
+ var cases = keys(suite)
+ , i = 0;
+
+ if (!cases.length) {
+ return complete();
+ }
+
+ currentCase = cases[i];
+
+ test(suite[cases[i]], function check (err) {
+ if (err) {
+ failed[cases[i]] = err;
+ } else {
+ passed[cases[i]] = true;
+ }
+
+ if (cases[++i]) {
+ currentCase = cases[i];
+ test(suite[cases[i]], check);
+ } else {
+ complete();
+ }
+ });
+ });
+};
+
+// runs a test
+function test (testcase, fn) {
+ var timer;
+
+ window.onerror = function (err) {
+ complete(err);
+ };
+
+ function complete (err) {
+ if (complete.run) return;
+ if (timer) clearTimeout(timer);
+ complete.run = true;
+ window.onerror = null;
+ fn(err);
+ };
+
+ try {
+ if (testcase.length > 0) {
+ var timer = setTimeout(function () {
+ complete(new Error('Timeout'));
+ }, 2000);
+
+ testcase(complete);
+ } else {
+ testcase();
+ complete();
+ }
+ } catch (e) {
+ complete(e);
+ }
+};
+
+// exposes a function to easily create a server for the current test
+
+function create (nsp) {
+ if (!testsPorts[currentSuite]) {
+ throw new Error('No socket server defined for suite "' + currentSuite + '"');
+ }
+
+ if (!testsPorts[currentSuite][currentCase]) {
+ throw new Error('No socket server defined for suite "' + currentSuite
+ + '" and case "' + currentCase + '"');
+ }
+
+ return io.connect(
+ document.location.protocol + '//' + document.location.hostname
+ + ':' + testsPorts[currentSuite][currentCase] + (nsp || '')
+ );
+};
--- /dev/null
+!function(win, doc, timeout) {
+ var head = doc.getElementsByTagName('head')[0],
+ list = {}, ids = {}, delay = {},
+ scripts = {}, s = 'string', f = false,
+ push = 'push', domContentLoaded = 'DOMContentLoaded', readyState = 'readyState',
+ addEventListener = 'addEventListener', onreadystatechange = 'onreadystatechange',
+ every = function(ar, fn) {
+ for (var i = 0, j = ar.length; i < j; ++i) {
+ if (!fn(ar[i])) {
+ return f;
+ }
+ }
+ return 1;
+ };
+ function each(ar, fn) {
+ every(ar, function(el) {
+ return !fn(el);
+ });
+ }
+
+ if (!doc[readyState] && doc[addEventListener]) {
+ doc[addEventListener](domContentLoaded, function fn() {
+ doc.removeEventListener(domContentLoaded, fn, f);
+ doc[readyState] = "complete";
+ }, f);
+ doc[readyState] = "loading";
+ }
+
+ var $script = function(paths, idOrDone, optDone) {
+ paths = paths[push] ? paths : [paths];
+ var idOrDoneIsDone = idOrDone && idOrDone.call,
+ done = idOrDoneIsDone ? idOrDone : optDone,
+ id = idOrDoneIsDone ? paths.join('') : idOrDone,
+ queue = paths.length;
+ function loopFn(item) {
+ return item.call ? item() : list[item];
+ }
+ function callback() {
+ if (!--queue) {
+ list[id] = 1;
+ done && done();
+ for (var dset in delay) {
+ every(dset.split('|'), loopFn) && !each(delay[dset], loopFn) && (delay[dset] = []);
+ }
+ }
+ }
+ timeout(function() {
+ each(paths, function(path) {
+ if (scripts[path]) {
+ id && (ids[id] = 1);
+ callback();
+ return;
+ }
+ scripts[path] = 1;
+ id && (ids[id] = 1);
+ create($script.path ?
+ $script.path + path + '.js' :
+ path, callback);
+ });
+ }, 0);
+ return $script;
+ };
+
+ function create(path, fn) {
+ var el = doc.createElement("script"),
+ loaded = f;
+ el.onload = el.onerror = el[onreadystatechange] = function () {
+ if ((el[readyState] && !(/^c|loade/.test(el[readyState]))) || loaded) {
+ return;
+ }
+ el.onload = el[onreadystatechange] = null;
+ loaded = 1;
+ fn();
+ };
+ el.async = 1;
+ el.src = path;
+ head.insertBefore(el, head.firstChild);
+ }
+
+ $script.get = create;
+
+ $script.ready = function(deps, ready, req) {
+ deps = deps[push] ? deps : [deps];
+ var missing = [];
+ !each(deps, function(dep) {
+ list[dep] || missing[push](dep);
+ }) && every(deps, function(dep) {
+ return list[dep];
+ }) ? ready() : !function(key) {
+ delay[key] = delay[key] || [];
+ delay[key][push](ready);
+ req && req(missing);
+ }(deps.join('|'));
+ return $script;
+ };
+
+ var old = win.$script;
+ $script.noConflict = function () {
+ win.$script = old;
+ return this;
+ };
+
+ (typeof module !== 'undefined' && module.exports) ?
+ (module.exports = $script) :
+ (win['$script'] = $script);
+
+}(this, document, setTimeout);
--- /dev/null
+body {
+ font-family: "Helvetica", "Helvetica Neue", sans-serif;
+ margin: 0;
+ color: #333;
+ font-size: 16px;
+ line-height: 1.5em;
+ word-spacing: 0.1em;
+ font-weight: 400;
+ background: #f9f9f9;
+ padding: 50px;
+}
+a {
+ border-bottom: 1px solid #999;
+ text-decoration: none;
+}
+a:hover {
+ border-color: transparent;
+ background: #eaeaea;
+}
+h2 {
+ font-weight: normal;
+ font-size: 28px;
+ margin: 24px 0 32px;
+}
+h2 a {
+ color: #666;
+ font-style: normal;
+}
+.test-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.test-list > li {
+ margin-left: 36px;
+ margin-bottom: 12px;
+ position: relative;
+}
+.test-list > li::before {
+ content: "✔";
+ background: #acd843;
+ padding: 1px 6px;
+ position: absolute;
+ top: -2px;
+ right: 100%;
+ margin-right: 8px;
+ color: #fff;
+}
+.test-list > li.loading::before {
+ content: "…";
+ font-size: 18px;
+ line-height: 16px;
+ vertical-align: middle;
+ padding: 0px 4px 10px;
+ background: #ccc;
+}
+.test-list > li.test-failure::before {
+ content: "✘";
+ font-size: 18px;
+ padding: 1px 7px;
+ background: #cd3a33;
+}
+.test-list > li span.name {
+ display: inline-block;
+ width: 150px;
+}
+.test-list > li span.bullet {
+ display: inline-block;
+ width: 20px;
+ font-size: 20px;
+ vertical-align: middle;
+}
+.test-list > li a {
+ color: #333;
+}
+.test-list > li .details {
+ font-size: 12px;
+ color: #666;
+}
+.test-list > li .details em {
+ font-style: normal;
+ color: #333;
+}
+.test-list > li ul.cases {
+ font-size: 11px;
+ padding: 0;
+ display: none;
+}
+.test-list > li ul.cases li {
+ list-style-type: none;
+ color: #000;
+}
+.test-list > li ul.cases li::before {
+ content: "✔";
+ color: #acd843;
+ margin-right: 5px;
+}
+.test-list > li ul.cases li.failed::before {
+ content: "✘";
+ color: #cd3a33;
+ margin-right: 6px;
+ margin-left: 1px;
+}
+.test-list > li ul.cases li.failed {
+ color: #cd3a33;
+}
+.test-list > li.cases ul.cases {
+ display: block;
+}
+.test-list > li.cases ul.cases span.error {
+ color: #cd3a33;
+ display: block;
+ line-height: 5px;
+ padding-left: 14px;
+ padding-bottom: 5px;
+}
--- /dev/null
+// design heavily based on https://github.com/kkaefer/overlord, released under MIT
+// Copyright (c) 2010-2011 Konstantin Käfer
+
+body
+ font-family 'Helvetica', 'Helvetica Neue', sans-serif
+ margin 0
+ color #333
+ font-size 16px
+ line-height 1.5em
+ word-spacing 0.1em
+ font-weight 400
+ background #F9F9F9
+ padding 50px
+
+a
+ border-bottom 1px solid #999
+ text-decoration none
+ &:hover
+ border-color transparent
+ background #EAEAEA
+
+h2
+ font-weight normal
+ font-size 28px
+ margin 24px 0 32px
+ a
+ color #666
+ font-style normal
+
+.test-list
+ margin 0
+ padding 0
+ list-style none
+ > li
+ margin-left 36px
+ margin-bottom 12px
+ position relative
+ &::before
+ content "✔"
+ background #ACD843
+ padding 1px 6px
+ position absolute
+ top -2px
+ right 100%
+ margin-right 8px
+ color white
+ &.loading::before
+ content "…"
+ font-size 18px
+ line-height 16px
+ vertical-align middle
+ padding 0px 4px 10px
+ background #ccc
+ &.test-failure::before
+ content "✘"
+ font-size 18px
+ padding 1px 7px
+ background #CD3A33
+ span.name
+ display inline-block
+ width 150px
+ span.bullet
+ display inline-block
+ width 20px
+ font-size 20px
+ vertical-align middle
+ a
+ color #333
+ .details
+ font-size 12px
+ color #666
+ em
+ font-style normal
+ color #333
+ ul.cases
+ font-size 11px
+ padding 0
+ display none
+ li
+ list-style-type none
+ color #000
+ &::before
+ content "✔"
+ color #ACD843
+ margin-right 5px
+ &.failed::before
+ content "✘"
+ color #CD3A33
+ margin-right 6px
+ margin-left 1px
+ &.failed
+ color #CD3A33
+ > li.cases
+ ul.cases
+ display block
+ span.error
+ color #CD3A33
+ display block
+ line-height 5px
+ padding-left 14px
+ padding-bottom 5px
var lines = result.split('\n');
lines.length.should().be.below(5);
lines[0].should().match(/production/gi);
- Buffer.byteLength(result).should().be.below(41000);
+ Buffer.byteLength(result).should().be.below(43000);
});
},
});
},
+ 'preserve the encoding during minification': function () {
+ builder(function (err, result) {
+ should.strictEqual(err, null);
+
+ result.should().match(/(\\ufffd)/g);
+ })
+ },
+
'globals': function () {
builder(function (err, result) {
should.strictEqual(err, null);
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
]).should().eql('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d')
+ },
+
+ 'test decoding newline': function () {
+ parser.decodePacket('3:::\n').should().eql({
+ type: 'message'
+ , endpoint: ''
+ , data: '\n'
+ });
}
};
next();
}
- io.on('connect', function(){
+ socket.on('connect', function(){
connect++;
});
}).on('error', function (msg) {
throw new Error(msg || 'Received an error');
});
-
- },
-
- 'test different namespace connection methods': function (next) {
- var io = create('/a')
- , connect = 0
- , message = 0
- , socket = io.socket;
-
- function finish () {
- socket.of('').disconnect();
- connect.should().equal(3);
- message.should().equal(3);
- next();
- }
-
- io.on('connect', function () {
- ++connect;
- }).on('message', function (data) {
- data.should().eql('a');
-
- if (++message === 3) finish();
- }).on('error', function (msg) {
- throw new Error(msg || 'Received an error');
- });
-
- socket.of('/b').on('connect', function () {
- ++connect;
- }).on('message', function (data) {
- data.should().eql('b');
-
- if (++message === 3) finish();
- }).on('error', function (msg) {
- throw new Error(msg || 'Received an error');
- });
-
- io.of('/c').on('connect', function () {
- ++connect;
- }).on('message', function (data) {
- data.should().eql('c');
-
- if (++message === 3) finish();
- }).on('error', function (msg) {
- throw new Error(msg || 'Received an error');
- });
-
},
'test disconnecting from namespaces': function (next) {
})
},
+ 'test emitting multiple events at once to the server': function (next) {
+ var socket = create();
+
+ socket.on('connect', function () {
+ socket.emit('print', 'foo');
+ socket.emit('print', 'bar');
+ });
+
+ socket.on('done', function () {
+ socket.disconnect();
+ next();
+ });
+ },
+
'test emitting an event from server and sending back data': function (next) {
var socket = create();
socket.disconnect();
next();
});
+ },
+
+ 'test encoding a payload': function (next) {
+ var socket = create('/woot');
+
+ socket.on('error', function (msg) {
+ throw new Error(msg || 'Received an error');
+ });
+
+ socket.on('connect', function () {
+ socket.socket.setBuffer(true);
+ socket.send('ñ');
+ socket.send('ñ');
+ socket.send('ñ');
+ socket.send('ñ');
+ socket.socket.setBuffer(false);
+ });
+
+ socket.on('done', function () {
+ socket.disconnect();
+ next();
+ });
+ },
+
+ 'test sending query strings to the server': function (next) {
+ var socket = create('?foo=bar');
+
+ socket.on('error', function (msg) {
+ throw new Error(msg || 'Received an error');
+ });
+
+ socket.on('message', function (data) {
+ data.query.foo.should().eql('bar');
+
+ socket.disconnect();
+ next();
+ });
+ },
+
+ 'test sending newline': function (next) {
+ var socket = create();
+
+ socket.on('error', function (msg) {
+ throw new Error(msg || 'Received an error');
+ });
+
+ socket.send('\n');
+
+ socket.on('done', function () {
+ socket.disconnect();
+ next();
+ });
+ },
+
+ 'test sending unicode': function (next) {
+ var socket = create();
+
+ socket.on('error', function (msg) {
+ throw new Error(msg || 'Received an error');
+ });
+
+ socket.json.send({ test: "\u2028" });
+
+ socket.on('done', function () {
+ socket.disconnect();
+ next();
+ });
}
};
io.util.uniqueUri(path).should().eql('https://google.com:443');
},
+ 'chunk query string': function () {
+ io.util.chunkQuery('foo=bar').should().be.a('object');
+ io.util.chunkQuery('foo=bar').foo.should().eql('bar');
+ },
+
+ 'merge query strings': function () {
+ var base = io.util.query('foo=bar', 'foo=baz')
+ , add = io.util.query('foo=bar', 'bar=foo')
+
+ base.should().eql('?foo=baz');
+ add.should().eql('?foo=bar&bar=foo');
+
+ io.util.query('','').should().eql('');
+ io.util.query('foo=bar', '').should().eql('?foo=bar');
+ io.util.query('', 'foo=bar').should().eql('?foo=bar');
+ },
+
'request': function () {
- if ('undefined' == typeof window) {
- should.equal(io.util.request(), null);
- } else {
- io.util.request().should().be.a('object');
- }
+ var type = typeof io.util.request();
+ type.should().eql('object');
},
'is array': function () {
+0.8.3 / 2011-09-03
+==================
+
+ * Fixed `\n` parsing for non-JSON packets (fixes #479).
+ * Fixed parsing of certain unicode characters (fixes #451).
+ * Fixed transport message packet logging.
+ * Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476).
+ * Fixed; allow for falsy values as the configuration value of `log level` (fixes #491).
+ * Fixed repository URI in `package.json`. Fixes #504.
+ * Added text/plain content-type to handshake responses [einaros]
+ * Improved single byte writes [einaros]
+ * Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden]
+ * Updated client.
+
+0.8.2 / 2011-08-29
+==================
+
+ * Updated client.
+
+0.8.1 / 2011-08-29
+==================
+
+ * Fixed utf8 bug in send framing in websocket [einaros]
+ * Fixed typo in docs [Znarkus]
+ * Fixed bug in send framing for over 64kB of data in websocket [einaros]
+ * Corrected ping handling in websocket transport [einaros]
+
+0.8.0 / 2011-08-28
+==================
+
+ * Updated to work with two-level websocket versioning. [einaros]
+ * Added hybi07 support. [einaros]
+ * Added hybi10 support. [einaros]
+ * Added http referrer verification to manager.js verifyOrigin. [einaors]
+
+0.7.11 / 2011-08-27
+===================
+
+ * Updated socket.io-client.
+
+0.7.10 / 2011-08-27
+===================
+
+ * Updated socket.io-client.
+
+0.7.9 / 2011-08-12
+==================
+
+ * Updated socket.io-client.
+ * Make sure we only do garbage collection when the server we receive is actually run.
+
+0.7.8 / 2011-08-08
+==================
+
+ * Changed; make sure sio#listen passes options to both HTTP server and socket.io manager.
+ * Added docs for sio#listen.
+ * Added options parameter support for Manager constructor.
+ * Added memory leaks tests and test-leaks Makefile task.
+ * Removed auto npm-linking from make test.
+ * Make sure that you can disable heartbeats. [3rd-Eden]
+ * Fixed rooms memory leak [3rd-Eden]
+ * Send response once we got all POST data, not immediately [Pita]
+ * Fixed onLeave behavior with missing clientsk [3rd-Eden]
+ * Prevent duplicate references in rooms.
+ * Added alias for `to` to `in` and `in` to `to`.
+ * Fixed roomClients definition.
+ * Removed dependency on redis for installation without npm [3rd-Eden]
+ * Expose path and querystring in handshakeData [3rd-Eden]
+
0.7.7 / 2011-07-12
==================
ALL_TESTS = $(shell find test/ -name '*.test.js')
run-tests:
- @npm link > /dev/null --local
@./node_modules/.bin/expresso \
-t 3000 \
-I support \
test-cov:
@TESTFLAGS=--cov $(MAKE) test
+test-leaks:
+ @ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
+
.PHONY: test
web framework:
```js
-var app = express.createServer();
+var app = express.createServer()
, io = io.listen(app);
app.listen(80);
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
- io.sockets.emit('this', { will: 'be received by everyone');
+ io.sockets.emit('this', { will: 'be received by everyone' });
socket.on('private message', function (from, msg) {
console.log('I received a private message by ', from, ' saying ', msg);
+++ /dev/null
-Initialize the Socket object upon handshake.
-Pass it to authorization function(s).
-When a transport opens, publish the transport name with the 'open' channel
-Set the transport name to the Socket object.
-Upon handshake, set a timeout of x seconds to GC that Socket object.
--- /dev/null
+
+/**
+ * Bootstrap app.
+ */
+
+require.paths.unshift(__dirname + '/../../lib/');
+
+/**
+ * Module dependencies.
+ */
+
+var express = require('express')
+ , stylus = require('stylus')
+ , nib = require('nib')
+ , sio = require('socket.io');
+
+/**
+ * App.
+ */
+
+var app = express.createServer();
+
+/**
+ * App configuration.
+ */
+
+app.configure(function () {
+ app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }))
+ app.use(express.static(__dirname + '/public'));
+ app.set('views', __dirname);
+ app.set('view engine', 'jade');
+
+ function compile (str, path) {
+ return stylus(str)
+ .set('filename', path)
+ .use(nib());
+ };
+});
+
+/**
+ * App routes.
+ */
+
+app.get('/', function (req, res) {
+ res.render('index', { layout: false });
+});
+
+/**
+ * App listen.
+ */
+
+app.listen(3000, function () {
+ var addr = app.address();
+ console.log(' app listening on http://' + addr.address + ':' + addr.port);
+});
+
+/**
+ * Socket.IO server (single process only)
+ */
+
+var io = sio.listen(app)
+ , nicknames = {};
+
+io.sockets.on('connection', function (socket) {
+ socket.on('user message', function (msg) {
+ socket.broadcast.emit('user message', socket.nickname, msg);
+ });
+
+ socket.on('nickname', function (nick, fn) {
+ if (nicknames[nick]) {
+ fn(true);
+ } else {
+ fn(false);
+ nicknames[nick] = socket.nickname = nick;
+ socket.broadcast.emit('announcement', nick + ' connected');
+ io.sockets.emit('nicknames', nicknames);
+ }
+ });
+
+ socket.on('disconnect', function () {
+ if (!socket.nickname) return;
+
+ delete nicknames[socket.nickname];
+ socket.broadcast.emit('announcement', socket.nickname + ' disconnected');
+ socket.broadcast.emit('nicknames', nicknames);
+ });
+});
--- /dev/null
+doctype 5
+html
+ head
+ link(href='/stylesheets/style.css', rel='stylesheet')
+ script(src='http://code.jquery.com/jquery-1.6.1.min.js')
+ script(src='/socket.io/socket.io.js')
+ script
+ // socket.io specific code
+ var socket = io.connect();
+
+ socket.on('connect', function () {
+ $('#chat').addClass('connected');
+ });
+
+ socket.on('announcement', function (msg) {
+ $('#lines').append($('<p>').append($('<em>').text(msg)));
+ });
+
+ socket.on('nicknames', function (nicknames) {
+ $('#nicknames').empty().append($('<span>Online: </span>'));
+ for (var i in nicknames) {
+ $('#nicknames').append($('<b>').text(nicknames[i]));
+ }
+ });
+
+ socket.on('user message', message);
+ socket.on('reconnect', function () {
+ $('#lines').remove();
+ message('System', 'Reconnected to the server');
+ });
+
+ socket.on('reconnecting', function () {
+ message('System', 'Attempting to re-connect to the server');
+ });
+
+ socket.on('error', function (e) {
+ message('System', e ? e : 'A unknown error occurred');
+ });
+
+ function message (from, msg) {
+ $('#lines').append($('<p>').append($('<b>').text(from), msg));
+ }
+
+ // dom manipulation
+ $(function () {
+ $('#set-nickname').submit(function (ev) {
+ socket.emit('nickname', $('#nick').val(), function (set) {
+ if (!set) {
+ clear();
+ return $('#chat').addClass('nickname-set');
+ }
+ $('#nickname-err').css('visibility', 'visible');
+ });
+ return false;
+ });
+
+ $('#send-message').submit(function () {
+ message('me', $('#message').val());
+ socket.emit('user message', $('#message').val());
+ clear();
+ $('#lines').get(0).scrollTop = 10000000;
+ return false;
+ });
+
+ function clear () {
+ $('#message').val('').focus();
+ };
+ });
+ body
+ #chat
+ #nickname
+ form.wrap#set-nickname
+ p Please type in your nickname and press enter.
+ input#nick
+ p#nickname-err Nickname already in use
+ #connecting
+ .wrap Connecting to socket.io server
+ #messages
+ #nicknames
+ #lines
+ form#send-message
+ input#message
+ button Send
--- /dev/null
+{
+ "name": "chat.io"
+ , "description": "example chat application with socket.io"
+ , "version": "0.0.1"
+ , "dependencies": {
+ "express": "2.3.11"
+ , "jade": "0.12.1"
+ , "stylus": "0.13.3"
+ , "nib": "0.0.8"
+ }
+}
--- /dev/null
+border-radius(n)
+ -webkit-border-radius n
+ -moz-border-radius n
+ border-radius n
+
+// replace str with val
+
+replace(expr, str, val)
+ expr = clone(expr)
+ for e, i in expr
+ if str == e
+ expr[i] = val
+ expr
+
+// normalize gradient point (webkit)
+
+grad-point(pos)
+ if length(pos) == 1
+ return left pos if pos in (top bottom)
+ return pos top if pos in (left right)
+ else if pos[0] in (top bottom)
+ pos[1] pos[0]
+ else
+ pos
+
+// implicit color stop position
+
+pos-in-stops(i, stops)
+ len = length(stops)
+ if len - 1 == i
+ 100%
+ else if i
+ unit(i / len * 100, '%')
+ else
+ 0%
+
+// normalize color stops
+// - (color pos) -> (pos color)
+// - (color) -> (implied-pos color)
+
+normalize-stops(stops)
+ stops = clone(stops)
+ for stop, i in stops
+ if length(stop) == 1
+ color = stop[0]
+ stop[0] = pos-in-stops(i, stops)
+ stop[1] = color
+ else if typeof(stop[1]) == 'unit'
+ pos = stop[1]
+ stop[1] = stop[0]
+ stop[0] = pos
+ stops
+
+// join color stops with the given translation function
+
+join-stops(stops, translate)
+ str = ''
+ len = length(stops)
+ for stop, i in stops
+ str += ', ' if i
+ pos = stop[0]
+ color = stop[1]
+ str += translate(color, pos)
+ unquote(str)
+
+// webkit translation function
+
+webkit-stop(color, pos)
+ s('color-stop(%d, %s)', pos / 100, color)
+
+// mozilla translation function
+
+moz-stop(color, pos)
+ s('%s %s', color, pos)
+
+// create a linear gradient with the given start
+// position, followed by color stops
+
+linear-gradient(start, stops...)
+ error('color stops required') unless length(stops)
+ prop = current-property[0]
+ val = current-property[1]
+ stops = normalize-stops(stops)
+
+ // webkit
+ end = grad-point(opposite-position(start))
+ webkit = s('-webkit-gradient(linear, %s, %s, %s)', grad-point(start), end, join-stops(stops, webkit-stop))
+ add-property(prop, replace(val, '__CALL__', webkit))
+
+ // moz
+ stops = join-stops(stops, moz-stop)
+ moz = s('-moz-linear-gradient(%s, %s)', start, stops)
+ add-property(prop, replace(val, '__CALL__', moz))
+
+ // literal
+ s('linear-gradient(%s, %s)', start, stops)
--- /dev/null
+#chat,
+#nickname,
+#messages {
+ width: 600px;
+}
+#chat {
+ position: relative;
+ border: 1px solid #ccc;
+}
+#nickname,
+#connecting {
+ position: absolute;
+ height: 410px;
+ z-index: 100;
+ left: 0;
+ top: 0;
+ background: #fff;
+ text-align: center;
+ width: 600px;
+ font: 15px Georgia;
+ color: #666;
+ display: block;
+}
+#nickname .wrap,
+#connecting .wrap {
+ padding-top: 150px;
+}
+#nickname input {
+ border: 1px solid #ccc;
+ padding: 10px;
+}
+#nickname input:focus {
+ border-color: #999;
+ outline: 0;
+}
+#nickname #nickname-err {
+ color: #8b0000;
+ font-size: 12px;
+ visibility: hidden;
+}
+.connected #connecting {
+ display: none;
+}
+.nickname-set #nickname {
+ display: none;
+}
+#messages {
+ height: 380px;
+ background: #eee;
+}
+#messages em {
+ text-shadow: 0 1px 0 #fff;
+ color: #999;
+}
+#messages p {
+ padding: 0;
+ margin: 0;
+ font: 12px Helvetica, Arial;
+ padding: 5px 10px;
+}
+#messages p b {
+ display: inline-block;
+ padding-right: 10px;
+}
+#messages p:nth-child(even) {
+ background: #fafafa;
+}
+#messages #nicknames {
+ background: #ccc;
+ padding: 2px 4px 4px;
+ font: 11px Helvetica;
+}
+#messages #nicknames span {
+ color: #000;
+}
+#messages #nicknames b {
+ display: inline-block;
+ color: #fff;
+ background: #999;
+ padding: 3px 6px;
+ margin-right: 5px;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+ text-shadow: 0 1px 0 #666;
+}
+#messages #lines {
+ height: 355px;
+ overflow: auto;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+#messages #lines::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+#messages #lines::-webkit-scrollbar-button:start:decrement,
+#messages #lines ::-webkit-scrollbar-button:end:increment {
+ display: block;
+ height: 10px;
+}
+#messages #lines::-webkit-scrollbar-button:vertical:increment {
+ background-color: #fff;
+}
+#messages #lines::-webkit-scrollbar-track-piece {
+ background-color: #fff;
+ -webkit-border-radius: 3px;
+}
+#messages #lines::-webkit-scrollbar-thumb:vertical {
+ height: 50px;
+ background-color: #ccc;
+ -webkit-border-radius: 3px;
+}
+#messages #lines::-webkit-scrollbar-thumb:horizontal {
+ width: 50px;
+ background-color: #fff;
+ -webkit-border-radius: 3px;
+}
+#send-message {
+ background: #fff;
+ position: relative;
+}
+#send-message input {
+ border: none;
+ height: 30px;
+ padding: 0 10px;
+ line-height: 30px;
+ vertical-align: middle;
+ width: 580px;
+}
+#send-message input:focus {
+ outline: 0;
+}
+#send-message button {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+}
+button {
+ margin: 0;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ display: inline-block;
+ text-decoration: none;
+ background: #43a1f7;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #377ad0));
+ background: -webkit-linear-gradient(top, #43a1f7 0%, #377ad0 100%);
+ background: -moz-linear-gradient(top, #43a1f7 0%, #377ad0 100%);
+ background: linear-gradient(top, #43a1f7 0%, #377ad0 100%);
+ border: 1px solid #2e70c4;
+ -webkit-border-radius: 16px;
+ -moz-border-radius: 16px;
+ border-radius: 16px;
+ color: #fff;
+ font-family: "lucida grande", sans-serif;
+ font-size: 11px;
+ font-weight: normal;
+ line-height: 1;
+ padding: 3px 10px 5px 10px;
+ text-align: center;
+ text-shadow: 0 -1px 1px #2d6dc0;
+}
+button:hover,
+button.hover {
+ background: darker;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #2e70c4));
+ background: -webkit-linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
+ background: -moz-linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
+ background: linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
+ border: 1px solid #2e70c4;
+ cursor: pointer;
+ text-shadow: 0 -1px 1px #2c6bbb;
+}
+button:active,
+button.active {
+ background: #2e70c4;
+ border: 1px solid #2e70c4;
+ border-bottom: 1px solid #2861aa;
+ text-shadow: 0 -1px 1px #2b67b5;
+}
+button:focus,
+button.focus {
+ outline: none;
+ -webkit-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
+ -moz-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
+ box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
+}
--- /dev/null
+@import 'nib'
+
+#chat, #nickname, #messages
+ width 600px
+
+#chat
+ position relative
+ border 1px solid #ccc
+
+#nickname, #connecting
+ position absolute
+ height 410px
+ z-index 100
+ left 0
+ top 0
+ background #fff
+ text-align center
+ width 600px
+ font 15px Georgia
+ color #666
+ display block
+ .wrap
+ padding-top 150px
+
+#nickname
+ input
+ border 1px solid #ccc
+ padding 10px
+ &:focus
+ border-color #999
+ outline 0
+ #nickname-err
+ color darkred
+ font-size 12px
+ visibility hidden
+
+.connected
+ #connecting
+ display none
+
+.nickname-set
+ #nickname
+ display none
+
+#messages
+ height 380px
+ background #eee
+ em
+ text-shadow 0 1px 0 #fff
+ color #999
+ p
+ padding 0
+ margin 0
+ font 12px Helvetica, Arial
+ padding 5px 10px
+ b
+ display inline-block
+ padding-right 10px
+ p:nth-child(even)
+ background #fafafa
+ #nicknames
+ background #ccc
+ padding 2px 4px 4px
+ font 11px Helvetica
+ span
+ color black
+ b
+ display inline-block
+ color #fff
+ background #999
+ padding 3px 6px
+ margin-right 5px
+ border-radius 10px
+ text-shadow 0 1px 0 #666
+ #lines
+ height 355px
+ overflow auto
+ overflow-x hidden
+ overflow-y auto
+ &::-webkit-scrollbar
+ width 6px
+ height 6px
+ &::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment
+ display block
+ height 10px
+ &::-webkit-scrollbar-button:vertical:increment
+ background-color #fff
+ &::-webkit-scrollbar-track-piece
+ background-color #fff
+ -webkit-border-radius 3px
+ &::-webkit-scrollbar-thumb:vertical
+ height 50px
+ background-color #ccc
+ -webkit-border-radius 3px
+ &::-webkit-scrollbar-thumb:horizontal
+ width 50px
+ background-color #fff
+ -webkit-border-radius 3px
+
+#send-message
+ background #fff
+ position relative
+ input
+ border none
+ height 30px
+ padding 0 10px
+ line-height 30px
+ vertical-align middle
+ width 580px
+ &:focus
+ outline 0
+ button
+ position absolute
+ top 5px
+ right 5px
+
+button
+ download-button()
--- /dev/null
+
+/**
+ * Bootstrap app.
+ */
+
+require.paths.unshift(__dirname + '/../../lib/');
+
+/**
+ * Module dependencies.
+ */
+
+var express = require('express')
+ , stylus = require('stylus')
+ , nib = require('nib')
+ , sio = require('socket.io')
+ , irc = require('./irc');
+
+/**
+ * App.
+ */
+
+var app = express.createServer();
+
+/**
+ * App configuration.
+ */
+
+app.configure(function () {
+ app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }))
+ app.use(express.static(__dirname + '/public'));
+ app.set('views', __dirname);
+ app.set('view engine', 'jade');
+
+ function compile (str, path) {
+ return stylus(str)
+ .set('filename', path)
+ .use(nib());
+ };
+});
+
+/**
+ * App routes.
+ */
+
+app.get('/', function (req, res) {
+ res.render('index', { layout: false });
+});
+
+/**
+ * App listen.
+ */
+
+app.listen(3000, function () {
+ var addr = app.address();
+ console.log(' app listening on http://' + addr.address + ':' + addr.port);
+});
+
+/**
+ * Socket.IO server
+ */
+
+var io = sio.listen(app)
+
+/**
+ * Connect to IRC.
+ */
+
+var client = new irc.Client('irc.freenode.net', 6667);
+client.connect('socketio\\test\\' + String(Math.random()).substr(-3));
+client.on('001', function () {
+ this.send('JOIN', '#node.js');
+});
+client.on('PART', function (prefix) {
+ io.sockets.emit('announcement', irc.user(prefix) + ' left the channel');
+});
+client.on('JOIN', function (prefix) {
+ io.sockets.emit('announcement', irc.user(prefix) + ' joined the channel');
+});
+client.on('PRIVMSG', function (prefix, channel, text) {
+ io.sockets.emit('irc message', irc.user(prefix), text);
+});
--- /dev/null
+doctype 5
+html
+ head
+ link(href='/stylesheets/style.css', rel='stylesheet')
+ script(src='http://code.jquery.com/jquery-1.6.1.min.js')
+ script(src='/socket.io/socket.io.js')
+ script
+ var socket = io.connect();
+
+ socket.on('connect', function () {
+ $('#irc').addClass('connected');
+ });
+
+ socket.on('announcement', function (msg) {
+ $('#messages').append($('<p>').append($('<em>').text(msg)));
+ $('#messages').get(0).scrollTop = 10000000;
+ });
+
+ socket.on('irc message', function (user, msg) {
+ $('#messages').append($('<p>').append($('<b>').text(user), msg));
+ $('#messages').get(0).scrollTop = 10000000;
+ });
+ body
+ h2 Node.JS IRC
+ #irc
+ #connecting
+ .wrap Connecting to socket.io server
+ #messages
--- /dev/null
+/**
+ * From https://github.com/felixge/nodelog/
+ */
+
+var sys = require('sys');
+var tcp = require('net');
+var irc = exports;
+
+function bind(fn, scope) {
+ var bindArgs = Array.prototype.slice.call(arguments);
+ bindArgs.shift();
+ bindArgs.shift();
+
+ return function() {
+ var args = Array.prototype.slice.call(arguments);
+ fn.apply(scope, bindArgs.concat(args));
+ };
+}
+
+function each(set, iterator) {
+ for (var i = 0; i < set.length; i++) {
+ var r = iterator(set[i], i);
+ if (r === false) {
+ return;
+ }
+ }
+}
+
+var Client = irc.Client = function(host, port) {
+ this.host = host || 'localhost';
+ this.port = port || 6667;
+
+ this.connection = null;
+ this.buffer = '';
+ this.encoding = 'utf8';
+ this.timeout = 10 * 60 * 60 * 1000;
+
+ this.nick = null;
+ this.user = null;
+ this.real = null;
+}
+sys.inherits(Client, process.EventEmitter);
+
+Client.prototype.connect = function(nick, user, real) {
+ var connection = tcp.createConnection(this.port, this.host);
+ connection.setEncoding(this.encoding);
+ connection.setTimeout(this.timeout);
+ connection.addListener('connect', bind(this.onConnect, this));
+ connection.addListener('data', bind(this.onReceive, this));
+ connection.addListener('end', bind(this.onEof, this));
+ connection.addListener('timeout', bind(this.onTimeout, this));
+ connection.addListener('close', bind(this.onClose, this));
+
+ this.nick = nick;
+ this.user = user || 'guest';
+ this.real = real || 'Guest';
+
+ this.connection = connection;
+};
+
+Client.prototype.disconnect = function(why) {
+ if (this.connection.readyState !== 'closed') {
+ this.connection.close();
+ sys.puts('disconnected (reason: '+why+')');
+ this.emit('DISCONNECT', why);
+ }
+};
+
+Client.prototype.send = function(arg1) {
+ if (this.connection.readyState !== 'open') {
+ return this.disconnect('cannot send with readyState: '+this.connection.readyState);
+ }
+
+ var message = [];
+ for (var i = 0; i< arguments.length; i++) {
+ if (arguments[i]) {
+ message.push(arguments[i]);
+ }
+ }
+ message = message.join(' ');
+
+ sys.puts('> '+message);
+ message = message + "\r\n";
+ this.connection.write(message, this.encoding);
+};
+
+Client.prototype.parse = function(message) {
+ var match = message.match(/(?:(:[^\s]+) )?([^\s]+) (.+)/);
+ var parsed = {
+ prefix: match[1],
+ command: match[2]
+ };
+
+ var params = match[3].match(/(.*?) ?:(.*)/);
+ if (params) {
+ // Params before :
+ params[1] = (params[1])
+ ? params[1].split(' ')
+ : [];
+ // Rest after :
+ params[2] = params[2]
+ ? [params[2]]
+ : [];
+
+ params = params[1].concat(params[2]);
+ } else {
+ params = match[3].split(' ');
+ }
+
+ parsed.params = params;
+ return parsed;
+};
+
+Client.prototype.onConnect = function() {
+ this.send('NICK', this.nick);
+ this.send('USER', this.user, '0', '*', ':'+this.real);
+};
+
+Client.prototype.onReceive = function(chunk) {
+ this.buffer = this.buffer + chunk;
+
+ while (this.buffer) {
+ var offset = this.buffer.indexOf("\r\n");
+ if (offset < 0) {
+ return;
+ }
+
+ var message = this.buffer.substr(0, offset);
+ this.buffer = this.buffer.substr(offset + 2);
+ sys.puts('< '+message);
+
+ message = this.parse(message);
+
+ this.emit.apply(this, [message.command, message.prefix].concat(message.params));
+
+ if (message !== false) {
+ this.onMessage(message);
+ }
+ }
+};
+
+Client.prototype.onMessage = function(message) {
+ switch (message.command) {
+ case 'PING':
+ this.send('PONG', ':'+message.params[0]);
+ break;
+ }
+};
+
+Client.prototype.onEof = function() {
+ this.disconnect('eof');
+};
+
+Client.prototype.onTimeout = function() {
+ this.disconnect('timeout');
+};
+
+Client.prototype.onClose = function() {
+ this.disconnect('close');
+};
+
+exports.user = function(prefix) {
+ return prefix.match(/:([^!]+)!/)[1]
+};
{
- "name": "socket.io-website"
+ "name": "socket.io-irc"
, "version": "0.0.1"
, "dependencies": {
"express": "2.3.11"
, "jade": "0.12.1"
, "stylus": "0.13.3"
+ , "nib": "0.0.8"
}
}
--- /dev/null
+@import 'nib'
+
+h2
+ font bold 18px Helvetica Neue, Arial
+
+#irc, #messages
+ width 600px
+
+#irc
+ position relative
+ border 1px solid #ccc
+
+#connecting
+ position absolute
+ height 410px
+ z-index 100
+ left 0
+ top 0
+ background #fff
+ text-align center
+ width 600px
+ font 15px Georgia
+ color #666
+ display block
+ .wrap
+ padding-top 150px
+
+.connected
+ #connecting
+ display none
+
+#messages
+ height 380px
+ background #eee
+ overflow auto
+ overflow-x hidden
+ overflow-y auto
+ &::-webkit-scrollbar
+ width 6px
+ height 6px
+ &::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment
+ display block
+ height 10px
+ &::-webkit-scrollbar-button:vertical:increment
+ background-color #fff
+ &::-webkit-scrollbar-track-piece
+ background-color #fff
+ -webkit-border-radius 3px
+ &::-webkit-scrollbar-thumb:vertical
+ height 50px
+ background-color #ccc
+ -webkit-border-radius 3px
+ &::-webkit-scrollbar-thumb:horizontal
+ width 50px
+ background-color #fff
+ -webkit-border-radius 3px
+ em
+ text-shadow 0 1px 0 #fff
+ color #999
+ p
+ padding 0
+ margin 0
+ font 12px Helvetica, Arial
+ padding 5px 10px
+ b
+ display inline-block
+ padding-right 10px
+ p:nth-child(even)
+ background #fafafa
-
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* Module dependencies.
*/
-var http = require('http')
- , https = require('https')
- , fs = require('fs')
+var fs = require('fs')
, url = require('url')
, util = require('./util')
, store = require('./store')
* @api public
*/
-function Manager (server) {
+function Manager (server, options) {
this.server = server;
this.namespaces = {};
this.sockets = this.of('');
, 'heartbeat interval': 20
, 'polling duration': 20
, 'flash policy server': true
- , 'flash policy port': 843
+ , 'flash policy port': 10843
, 'destroy upgrade': true
, 'browser client': true
, 'browser client minification': false
, 'client store expiration': 15
};
+ for (var i in options) {
+ this.settings[i] = options[i];
+ }
+
this.initStore();
// reset listeners
self.handleUpgrade(req, socket, head);
});
+ server.on('close', function () {
+ clearInterval(self.gc);
+ });
+
+ server.once('listening', function () {
+ self.gc = setInterval(self.garbageCollection.bind(self), 10000);
+ });
+
for (var i in transports) {
if (transports[i].init) {
transports[i].init(this);
if (this.disabled('log')) return;
var logger = this.get('logger');
- logger.level = this.get('log level');
+ logger.level = this.get('log level') || -1;
return logger;
});
Manager.prototype.onJoin = function (id, name) {
if (!this.roomClients[id]) {
- this.roomClients[id] = [];
+ this.roomClients[id] = {};
}
if (!this.rooms[name]) {
this.rooms[name] = [];
}
- this.rooms[name].push(id);
- this.roomClients[id][name] = true;
+ if (!~this.rooms[name].indexOf(id)) {
+ this.rooms[name].push(id);
+ this.roomClients[id][name] = true;
+ }
};
/**
Manager.prototype.onLeave = function (id, room) {
if (this.rooms[room]) {
- this.rooms[room].splice(this.rooms[room].indexOf(id), 1);
+ var index = this.rooms[room].indexOf(id);
+
+ if (index >= 0) {
+ this.rooms[room].splice(index, 1);
+ }
+
+ if (!this.rooms[room].length) {
+ delete this.rooms[room];
+ }
delete this.roomClients[id][room];
}
};
*/
Manager.prototype.onClientDisconnect = function (id, reason) {
- this.onDisconnect(id);
-
for (var name in this.namespaces) {
if (this.roomClients[id][name]) {
this.namespaces[name].handleDisconnect(id, reason);
}
}
+
+ this.onDisconnect(id);
};
/**
if (this.roomClients[id]) {
for (var room in this.roomClients[id]) {
- this.rooms[room].splice(this.rooms[room].indexOf(id), 1);
+ this.onLeave(id, room);
}
+ delete this.roomClients[id]
}
this.store.destroyClient(id, this.get('client store expiration'));
return;
}
- var transport = new transports[data.transport](this, data, req);
+ var transport = new transports[data.transport](this, data, req)
+ , handshaken = this.handshaken[data.id];
- if (this.handshaken[data.id]) {
+ if (handshaken) {
if (transport.open) {
if (this.closed[data.id] && this.closed[data.id].length) {
transport.payload(this.closed[data.id]);
this.onConnect(data.id);
this.store.publish('connect', data.id);
+ // flag as used
+ delete handshaken.issued;
+ this.onHandshake(data.id, handshaken);
+ this.store.publish('handshake', data.id, handshaken);
+
// initialize the socket for all namespaces
for (var i in this.namespaces) {
var socket = this.namespaces[i].socket(data.id, true);
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));');
} else {
- res.writeHead(status);
+ res.writeHead(status, { 'Content-Type': 'text/plain' });
res.end(message);
}
};
var id = self.generateId()
, hs = [
id
- , self.get('heartbeat timeout') || ''
+ , self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : ''
, self.get('close timeout') || ''
, self.transports(data).join(',')
].join(':');
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
res.writeHead(200, { 'Content-Type': 'application/javascript' });
} else {
- res.writeHead(200);
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
}
res.end(hs);
Manager.prototype.handshakeData = function (data) {
var connection = data.request.connection
- , connectionAddress;
+ , connectionAddress
+ , date = new Date;
if (connection.remoteAddress) {
connectionAddress = {
return {
headers: data.headers
, address: connectionAddress
- , time: (new Date).toString()
+ , time: date.toString()
+ , query: data.query
+ , url: data.request.url
, xdomain: !!data.request.headers.origin
, secure: data.request.connection.secure
+ , issued: +date
};
};
*/
Manager.prototype.verifyOrigin = function (request) {
- var origin = request.headers.origin
+ var origin = request.headers.origin || request.headers.referer
, origins = this.get('origins');
if (origin === 'null') origin = '*';
if (origin) {
try {
var parts = url.parse(origin);
-
- return
- ~origins.indexOf(parts.host + ':' + parts.port) ||
- ~origins.indexOf(parts.host + ':*') ||
+ var ok =
+ ~origins.indexOf(parts.hostname + ':' + parts.port) ||
+ ~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
- } catch (ex) {}
+ if (!ok) this.log.warn('illegal origin: ' + origin);
+ return ok;
+ } catch (ex) {
+ this.log.warn('error parsing origin');
+ }
+ }
+ else {
+ this.log.warn('origin missing from handshake, yet required by config');
}
-
return false;
};
/**
* Declares a socket namespace
+ *
+ * @api public
*/
Manager.prototype.of = function (nsp) {
return this.namespaces[nsp] = new SocketNamespace(this, nsp);
};
+
+/**
+ * Perform garbage collection on long living objects and properties that cannot
+ * be removed automatically.
+ *
+ * @api private
+ */
+
+Manager.prototype.garbageCollection = function () {
+ // clean up unused handshakes
+ var ids = Object.keys(this.handshaken)
+ , i = ids.length
+ , now = Date.now()
+ , handshake;
+
+ while (i--) {
+ handshake = this.handshaken[ids[i]];
+
+ if ('issued' in handshake && (now - handshake.issued) >= 3E4) {
+ this.onDisconnect(ids[i]);
+ }
+ }
+};
* @api public
*/
-SocketNamespace.prototype.in = function (room) {
+SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) {
this.flags.endpoint = this.name + (room ? '/' + room : '');
return this;
};
SocketNamespace.prototype.handleDisconnect = function (sid, reason) {
if (this.sockets[sid] && this.sockets[sid].readable) {
this.sockets[sid].onDisconnect(reason);
+ delete this.sockets[sid];
}
};
case 'event':
var params = [packet.name].concat(packet.args);
- if (dataAck)
+ if (dataAck) {
params.push(ack);
+ }
socket.$emit.apply(socket, params);
break;
* @api private
*/
-var regexp = /^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;
+var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
exports.decodePacket = function (data) {
var pieces = data.match(regexp);
* Version.
*/
-exports.version = '0.7.7';
+exports.version = '0.8.4';
/**
* Supported protocol version.
/**
* Attaches a manager
*
+ * @param {HTTPServer/Number} a HTTP/S server or a port number to listen on.
+ * @param {Object} opts to be passed to Manager and/or http server
+ * @param {Function} callback if a port is supplied
* @api public
*/
}
// otherwise assume a http/s server
- return new exports.Manager(server);
+ return new exports.Manager(server, options);
};
/**
var parser = require('./parser')
, util = require('./util')
- , EventEmitter = process.EventEmitter;
+ , EventEmitter = process.EventEmitter
/**
* Export the constructor.
exports = module.exports = Socket;
+/**
+ * Default error event listener to prevent uncaught exceptions.
+ */
+
+var defaultError = function () {};
+
/**
* Socket constructor.
*
this.setFlags();
this.readable = readable;
this.store = this.manager.store.client(this.id);
+ this.on('error', defaultError);
};
/**
* @api public
*/
-Socket.prototype.to = function (room) {
+Socket.prototype.to = Socket.prototype.in = function (room) {
this.flags.room = room;
return this;
};
};
/**
- * Joins a user to a room.
+ * Un-joins a user from a room.
*
* @api public
*/
var crypto = require('crypto')
, Store = require('../store')
- , assert = require('assert')
- , redis = require('redis');
+ , assert = require('assert');
/**
* Exports the constructor.
* Redis store.
* Options:
* - nodeId (fn) gets an id that uniquely identifies this node
+ * - redis (fn) redis constructor, defaults to redis
* - redisPub (object) options to pass to the pub redis client
* - redisSub (object) options to pass to the sub redis client
* - redisClient (object) options to pass to the general redis client
}
}
+ var redis = opts.redis || require('redis');
+
// initialize a pubsub client and a regular client
this.pub = redis.createClient(opts.redisPub);
this.sub = redis.createClient(opts.redisSub);
*/
Transport.prototype.setHeartbeatTimeout = function () {
- if (!this.heartbeatTimeout) {
+ if (!this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
var self = this;
this.heartbeatTimeout = setTimeout(function () {
*/
Transport.prototype.clearHeartbeatTimeout = function () {
- if (this.heartbeatTimeout) {
+ if (this.heartbeatTimeout && this.manager.enabled('heartbeats')) {
clearTimeout(this.heartbeatTimeout);
this.heartbeatTimeout = null;
this.log.debug('cleared heartbeat timeout for client', this.id);
*/
Transport.prototype.setHeartbeatInterval = function () {
- if (!this.heartbeatInterval) {
+ if (!this.heartbeatInterval && this.manager.enabled('heartbeats')) {
var self = this;
this.heartbeatInterval = setTimeout(function () {
*/
Transport.prototype.clearHeartbeatInterval = function () {
- if (this.heartbeatInterval) {
+ if (this.heartbeatInterval && this.manager.enabled('heartbeats')) {
clearTimeout(this.heartbeatInterval);
this.heartbeatInterval = null;
this.log.debug('cleared heartbeat interval for client', this.id);
*/
function FlashSocket (mng, data, req) {
- WebSocket.call(this, mng, data, req);
+ return WebSocket.call(this, mng, data, req);
}
/**
});
req.on('end', function () {
+ res.writeHead(200, headers);
+ res.end('1');
+
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
});
headers['Access-Control-Allow-Credentials'] = 'true';
}
}
-
- res.writeHead(200, headers);
- res.end('1');
} else {
this.response = req.res;
HTTPTransport.prototype.onData = function (data) {
var messages = parser.decodePayload(data);
+ this.log.debug(this.name + ' received data packet', data);
for (var i = 0, l = messages.length; i < l; i++) {
- this.log.debug(this.name + ' received data packet', data);
this.onMessage(messages[i]);
}
};
'Content-Type': 'text/javascript; charset=UTF-8'
, 'Content-Length': Buffer.byteLength(data)
, 'Connection': 'Keep-Alive'
+ , 'X-XSS-Protection': '0'
});
this.response.write(data);
* Module requirements.
*/
-var Transport = require('../transport')
- , EventEmitter = process.EventEmitter
- , crypto = require('crypto')
- , parser = require('../parser');
+var protocolVersions = require('./websocket/');
/**
* Export the constructor.
*/
function WebSocket (mng, data, req) {
- // parser
- var self = this;
-
- this.parser = new Parser();
- this.parser.on('data', function (packet) {
- self.log.debug(self.name + ' received data packet', packet);
- self.onMessage(parser.decodePacket(packet));
- });
- this.parser.on('close', function () {
- self.end();
- });
- this.parser.on('error', function () {
- self.end();
- });
-
- Transport.call(this, mng, data, req);
-};
-
-/**
- * Inherits from Transport.
- */
-
-WebSocket.prototype.__proto__ = Transport.prototype;
-
-/**
- * Transport name
- *
- * @api public
- */
-
-WebSocket.prototype.name = 'websocket';
-
-/**
- * Called when the socket connects.
- *
- * @api private
- */
-
-WebSocket.prototype.onSocketConnect = function () {
- var self = this;
-
- this.socket.setNoDelay(true);
-
- this.buffer = true;
- this.buffered = [];
-
- if (this.req.headers.upgrade !== 'WebSocket') {
- this.log.warn(this.name + ' connection invalid');
- this.end();
- return;
+ var version = req.headers['sec-websocket-version'];
+ if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
+ return new protocolVersions[version](mng, data, req);
}
-
- var origin = this.req.headers.origin
- , location = (this.socket.encrypted ? 'wss' : 'ws')
- + '://' + this.req.headers.host + this.req.url
- , waitingForNonce = false;
-
- if (this.req.headers['sec-websocket-key1']) {
- // If we don't have the nonce yet, wait for it (HAProxy compatibility).
- if (! (this.req.head && this.req.head.length >= 8)) {
- waitingForNonce = true;
- }
-
- var headers = [
- 'HTTP/1.1 101 WebSocket Protocol Handshake'
- , 'Upgrade: WebSocket'
- , 'Connection: Upgrade'
- , 'Sec-WebSocket-Origin: ' + origin
- , 'Sec-WebSocket-Location: ' + location
- ];
-
- if (this.req.headers['sec-websocket-protocol']){
- headers.push('Sec-WebSocket-Protocol: '
- + this.req.headers['sec-websocket-protocol']);
- }
- } else {
- var headers = [
- 'HTTP/1.1 101 Web Socket Protocol Handshake'
- , 'Upgrade: WebSocket'
- , 'Connection: Upgrade'
- , 'WebSocket-Origin: ' + origin
- , 'WebSocket-Location: ' + location
- ];
- }
-
- try {
- this.socket.write(headers.concat('', '').join('\r\n'));
- this.socket.setTimeout(0);
- this.socket.setNoDelay(true);
- this.socket.setEncoding('utf8');
- } catch (e) {
- this.end();
- return;
- }
-
- if (waitingForNonce) {
- this.socket.setEncoding('binary');
- } else if (this.proveReception(headers)) {
- self.flush();
- }
-
- var headBuffer = '';
-
- this.socket.on('data', function (data) {
- if (waitingForNonce) {
- headBuffer += data;
-
- if (headBuffer.length < 8) {
- return;
- }
-
- // Restore the connection to utf8 encoding after receiving the nonce
- self.socket.setEncoding('utf8');
- waitingForNonce = false;
-
- // Stuff the nonce into the location where it's expected to be
- self.req.head = headBuffer.substr(0, 8);
- headBuffer = '';
-
- if (self.proveReception(headers)) {
- self.flush();
- }
-
- return;
- }
-
- self.parser.add(data);
- });
-};
-
-/**
- * Writes to the socket.
- *
- * @api private
- */
-
-WebSocket.prototype.write = function (data) {
- if (this.open) {
- this.drained = false;
-
- if (this.buffer) {
- this.buffered.push(data);
- return this;
- }
-
- var length = Buffer.byteLength(data)
- , buffer = new Buffer(2 + length);
-
- buffer.write('\u0000', 'binary');
- buffer.write(data, 1, 'utf8');
- buffer.write('\uffff', 1 + length, 'binary');
-
- try {
- if (this.socket.write(buffer)) {
- this.drained = true;
- }
- } catch (e) {
- this.end();
- }
-
- this.log.debug(this.name + ' writing', data);
- }
-};
-
-/**
- * Flushes the internal buffer
- *
- * @api private
- */
-
-WebSocket.prototype.flush = function () {
- this.buffer = false;
-
- for (var i = 0, l = this.buffered.length; i < l; i++) {
- this.write(this.buffered.splice(0, 1)[0]);
- }
-};
-
-/**
- * Finishes the handshake.
- *
- * @api private
- */
-
-WebSocket.prototype.proveReception = function (headers) {
- var self = this
- , k1 = this.req.headers['sec-websocket-key1']
- , k2 = this.req.headers['sec-websocket-key2'];
-
- if (k1 && k2){
- var md5 = crypto.createHash('md5');
-
- [k1, k2].forEach(function (k) {
- var n = parseInt(k.replace(/[^\d]/g, ''))
- , spaces = k.replace(/[^ ]/g, '').length;
-
- if (spaces === 0 || n % spaces !== 0){
- self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
- self.end();
- return false;
- }
-
- n /= spaces;
-
- md5.update(String.fromCharCode(
- n >> 24 & 0xFF,
- n >> 16 & 0xFF,
- n >> 8 & 0xFF,
- n & 0xFF));
- });
-
- md5.update(this.req.head.toString('binary'));
-
- try {
- this.socket.write(md5.digest('binary'), 'binary');
- } catch (e) {
- this.end();
- }
- }
-
- return true;
-};
-
-/**
- * Writes a payload.
- *
- * @api private
- */
-
-WebSocket.prototype.payload = function (msgs) {
- for (var i = 0, l = msgs.length; i < l; i++) {
- this.write(msgs[i]);
- }
-
- return this;
-};
-
-/**
- * Closes the connection.
- *
- * @api private
- */
-
-WebSocket.prototype.doClose = function () {
- this.socket.end();
-};
-
-/**
- * WebSocket parser
- *
- * @api public
- */
-
-function Parser () {
- this.buffer = '';
- this.i = 0;
-};
-
-/**
- * Inherits from EventEmitter.
- */
-
-Parser.prototype.__proto__ = EventEmitter.prototype;
-
-/**
- * Adds data to the buffer.
- *
- * @api public
- */
-
-Parser.prototype.add = function (data) {
- this.buffer += data;
- this.parse();
-};
-
-/**
- * Parses the buffer.
- *
- * @api private
- */
-
-Parser.prototype.parse = function () {
- for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
- chr = this.buffer[i];
-
- if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
- this.emit('close');
- this.buffer = '';
- this.i = 0;
- return;
- }
-
- if (i === 0){
- if (chr != '\u0000')
- this.error('Bad framing. Expected null byte as first frame');
- else
- continue;
- }
-
- if (chr == '\ufffd'){
- this.emit('data', this.buffer.substr(1, i - 1));
- this.buffer = this.buffer.substr(i + 1);
- this.i = 0;
- return this.parse();
- }
- }
-};
-
-/**
- * Handles an error
- *
- * @api private
- */
-
-Parser.prototype.error = function (reason) {
- this.buffer = '';
- this.i = 0;
- this.emit('error', reason);
- return this;
+ return new protocolVersions['default'](mng, data, req);
};
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module requirements.
+ */
+
+var Transport = require('../../transport')
+ , EventEmitter = process.EventEmitter
+ , crypto = require('crypto')
+ , parser = require('../../parser');
+
+/**
+ * Export the constructor.
+ */
+
+exports = module.exports = WebSocket;
+
+/**
+ * HTTP interface constructor. Interface compatible with all transports that
+ * depend on request-response cycles.
+ *
+ * @api public
+ */
+
+function WebSocket (mng, data, req) {
+ // parser
+ var self = this;
+
+ this.parser = new Parser();
+ this.parser.on('data', function (packet) {
+ self.log.debug(self.name + ' received data packet', packet);
+ self.onMessage(parser.decodePacket(packet));
+ });
+ this.parser.on('close', function () {
+ self.end();
+ });
+ this.parser.on('error', function () {
+ self.end();
+ });
+
+ Transport.call(this, mng, data, req);
+};
+
+/**
+ * Inherits from Transport.
+ */
+
+WebSocket.prototype.__proto__ = Transport.prototype;
+
+/**
+ * Transport name
+ *
+ * @api public
+ */
+
+WebSocket.prototype.name = 'websocket';
+
+/**
+ * Called when the socket connects.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.onSocketConnect = function () {
+ var self = this;
+
+ this.socket.setNoDelay(true);
+
+ this.buffer = true;
+ this.buffered = [];
+
+ if (this.req.headers.upgrade !== 'WebSocket') {
+ this.log.warn(this.name + ' connection invalid');
+ this.end();
+ return;
+ }
+
+ var origin = this.req.headers.origin
+ , location = (this.socket.encrypted ? 'wss' : 'ws')
+ + '://' + this.req.headers.host + this.req.url
+ , waitingForNonce = false;
+
+ if (this.req.headers['sec-websocket-key1']) {
+ // If we don't have the nonce yet, wait for it (HAProxy compatibility).
+ if (! (this.req.head && this.req.head.length >= 8)) {
+ waitingForNonce = true;
+ }
+
+ var headers = [
+ 'HTTP/1.1 101 WebSocket Protocol Handshake'
+ , 'Upgrade: WebSocket'
+ , 'Connection: Upgrade'
+ , 'Sec-WebSocket-Origin: ' + origin
+ , 'Sec-WebSocket-Location: ' + location
+ ];
+
+ if (this.req.headers['sec-websocket-protocol']){
+ headers.push('Sec-WebSocket-Protocol: '
+ + this.req.headers['sec-websocket-protocol']);
+ }
+ } else {
+ var headers = [
+ 'HTTP/1.1 101 Web Socket Protocol Handshake'
+ , 'Upgrade: WebSocket'
+ , 'Connection: Upgrade'
+ , 'WebSocket-Origin: ' + origin
+ , 'WebSocket-Location: ' + location
+ ];
+ }
+
+ try {
+ this.socket.write(headers.concat('', '').join('\r\n'));
+ this.socket.setTimeout(0);
+ this.socket.setNoDelay(true);
+ this.socket.setEncoding('utf8');
+ } catch (e) {
+ this.end();
+ return;
+ }
+
+ if (waitingForNonce) {
+ this.socket.setEncoding('binary');
+ } else if (this.proveReception(headers)) {
+ self.flush();
+ }
+
+ var headBuffer = '';
+
+ this.socket.on('data', function (data) {
+ if (waitingForNonce) {
+ headBuffer += data;
+
+ if (headBuffer.length < 8) {
+ return;
+ }
+
+ // Restore the connection to utf8 encoding after receiving the nonce
+ self.socket.setEncoding('utf8');
+ waitingForNonce = false;
+
+ // Stuff the nonce into the location where it's expected to be
+ self.req.head = headBuffer.substr(0, 8);
+ headBuffer = '';
+
+ if (self.proveReception(headers)) {
+ self.flush();
+ }
+
+ return;
+ }
+
+ self.parser.add(data);
+ });
+};
+
+/**
+ * Writes to the socket.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.write = function (data) {
+ if (this.open) {
+ this.drained = false;
+
+ if (this.buffer) {
+ this.buffered.push(data);
+ return this;
+ }
+
+ var length = Buffer.byteLength(data)
+ , buffer = new Buffer(2 + length);
+
+ buffer.write('\x00', 'binary');
+ buffer.write(data, 1, 'utf8');
+ buffer.write('\xff', 1 + length, 'binary');
+
+ try {
+ if (this.socket.write(buffer)) {
+ this.drained = true;
+ }
+ } catch (e) {
+ this.end();
+ }
+
+ this.log.debug(this.name + ' writing', data);
+ }
+};
+
+/**
+ * Flushes the internal buffer
+ *
+ * @api private
+ */
+
+WebSocket.prototype.flush = function () {
+ this.buffer = false;
+
+ for (var i = 0, l = this.buffered.length; i < l; i++) {
+ this.write(this.buffered.splice(0, 1)[0]);
+ }
+};
+
+/**
+ * Finishes the handshake.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.proveReception = function (headers) {
+ var self = this
+ , k1 = this.req.headers['sec-websocket-key1']
+ , k2 = this.req.headers['sec-websocket-key2'];
+
+ if (k1 && k2){
+ var md5 = crypto.createHash('md5');
+
+ [k1, k2].forEach(function (k) {
+ var n = parseInt(k.replace(/[^\d]/g, ''))
+ , spaces = k.replace(/[^ ]/g, '').length;
+
+ if (spaces === 0 || n % spaces !== 0){
+ self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
+ self.end();
+ return false;
+ }
+
+ n /= spaces;
+
+ md5.update(String.fromCharCode(
+ n >> 24 & 0xFF,
+ n >> 16 & 0xFF,
+ n >> 8 & 0xFF,
+ n & 0xFF));
+ });
+
+ md5.update(this.req.head.toString('binary'));
+
+ try {
+ this.socket.write(md5.digest('binary'), 'binary');
+ } catch (e) {
+ this.end();
+ }
+ }
+
+ return true;
+};
+
+/**
+ * Writes a payload.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.payload = function (msgs) {
+ for (var i = 0, l = msgs.length; i < l; i++) {
+ this.write(msgs[i]);
+ }
+
+ return this;
+};
+
+/**
+ * Closes the connection.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.doClose = function () {
+ this.socket.end();
+};
+
+/**
+ * WebSocket parser
+ *
+ * @api public
+ */
+
+function Parser () {
+ this.buffer = '';
+ this.i = 0;
+};
+
+/**
+ * Inherits from EventEmitter.
+ */
+
+Parser.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Adds data to the buffer.
+ *
+ * @api public
+ */
+
+Parser.prototype.add = function (data) {
+ this.buffer += data;
+ this.parse();
+};
+
+/**
+ * Parses the buffer.
+ *
+ * @api private
+ */
+
+Parser.prototype.parse = function () {
+ for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
+ chr = this.buffer[i];
+
+ if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
+ this.emit('close');
+ this.buffer = '';
+ this.i = 0;
+ return;
+ }
+
+ if (i === 0){
+ if (chr != '\u0000')
+ this.error('Bad framing. Expected null byte as first frame');
+ else
+ continue;
+ }
+
+ if (chr == '\ufffd'){
+ this.emit('data', this.buffer.substr(1, i - 1));
+ this.buffer = this.buffer.substr(i + 1);
+ this.i = 0;
+ return this.parse();
+ }
+ }
+};
+
+/**
+ * Handles an error
+ *
+ * @api private
+ */
+
+Parser.prototype.error = function (reason) {
+ this.buffer = '';
+ this.i = 0;
+ this.emit('error', reason);
+ return this;
+};
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module requirements.
+ */
+
+var Transport = require('../../transport')
+ , EventEmitter = process.EventEmitter
+ , crypto = require('crypto')
+ , parser = require('../../parser')
+ , util = require('../../util');
+
+/**
+ * Export the constructor.
+ */
+
+exports = module.exports = WebSocket;
+exports.Parser = Parser;
+
+/**
+ * HTTP interface constructor. Interface compatible with all transports that
+ * depend on request-response cycles.
+ *
+ * @api public
+ */
+
+function WebSocket (mng, data, req) {
+ // parser
+ var self = this;
+
+ this.parser = new Parser();
+ this.parser.on('data', function (packet) {
+ self.onMessage(parser.decodePacket(packet));
+ });
+ this.parser.on('ping', function () {
+ // version 8 ping => pong
+ self.socket.write('\u008a\u0000');
+ });
+ this.parser.on('close', function () {
+ self.end();
+ });
+ this.parser.on('error', function (reason) {
+ self.log.warn(self.name + ' parser error: ' + reason);
+ self.end();
+ });
+
+ Transport.call(this, mng, data, req);
+};
+
+/**
+ * Inherits from Transport.
+ */
+
+WebSocket.prototype.__proto__ = Transport.prototype;
+
+/**
+ * Transport name
+ *
+ * @api public
+ */
+
+WebSocket.prototype.name = 'websocket';
+
+/**
+ * Called when the socket connects.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.onSocketConnect = function () {
+ var self = this;
+
+ if (this.req.headers.upgrade !== 'websocket') {
+ this.log.warn(this.name + ' connection invalid');
+ this.end();
+ return;
+ }
+
+ var origin = this.req.headers.origin
+ , location = (this.socket.encrypted ? 'wss' : 'ws')
+ + '://' + this.req.headers.host + this.req.url;
+
+ if (!this.req.headers['sec-websocket-key']) {
+ this.log.warn(this.name + ' connection invalid: received no key');
+ this.end();
+ return;
+ }
+
+ // calc key
+ var key = this.req.headers['sec-websocket-key'];
+ var shasum = crypto.createHash('sha1');
+ shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+ key = shasum.digest('base64');
+
+ var headers = [
+ 'HTTP/1.1 101 Switching Protocols'
+ , 'Upgrade: websocket'
+ , 'Connection: Upgrade'
+ , 'Sec-WebSocket-Accept: ' + key
+ ];
+
+ try {
+ this.socket.write(headers.concat('', '').join('\r\n'));
+ this.socket.setTimeout(0);
+ this.socket.setNoDelay(true);
+ } catch (e) {
+ this.end();
+ return;
+ }
+
+ this.socket.on('data', function (data) {
+ self.parser.add(data);
+ });
+};
+
+/**
+ * Writes to the socket.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.write = function (data) {
+ if (this.open) {
+ var buf = this.frame(0x81, data);
+ this.socket.write(buf, 'binary');
+ this.log.debug(this.name + ' writing', data);
+ }
+};
+
+/**
+ * Frame server-to-client output as a text packet.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.frame = function (opcode, str) {
+ var dataBuffer = new Buffer(str)
+ , dataLength = dataBuffer.length
+ , startOffset = 2
+ , secondByte = dataLength;
+ if (dataLength > 65536) {
+ startOffset = 10;
+ secondByte = 127;
+ }
+ else if (dataLength > 125) {
+ startOffset = 4;
+ secondByte = 126;
+ }
+ var outputBuffer = new Buffer(dataLength + startOffset);
+ outputBuffer[0] = opcode;
+ outputBuffer[1] = secondByte;
+ dataBuffer.copy(outputBuffer, startOffset);
+ switch (secondByte) {
+ case 126:
+ outputBuffer[2] = dataLength >>> 8;
+ outputBuffer[3] = dataLength % 256;
+ break;
+ case 127:
+ var l = dataLength;
+ for (var i = 1; i <= 8; ++i) {
+ outputBuffer[startOffset - i] = l & 0xff;
+ l >>>= 8;
+ }
+ }
+ return outputBuffer;
+};
+
+/**
+ * Closes the connection.
+ *
+ * @api private
+ */
+
+WebSocket.prototype.doClose = function () {
+ this.socket.end();
+};
+
+/**
+ * WebSocket parser
+ *
+ * @api public
+ */
+
+function Parser () {
+ this.state = {
+ activeFragmentedOperation: null,
+ lastFragment: false,
+ masked: false,
+ opcode: 0
+ };
+ this.overflow = null;
+ this.expectOffset = 0;
+ this.expectBuffer = null;
+ this.expectHandler = null;
+ this.currentMessage = '';
+
+ var self = this;
+ this.opcodeHandlers = {
+ // text
+ '1': function(data) {
+ var finish = function(mask, data) {
+ self.currentMessage += self.unmask(mask, data);
+ if (self.state.lastFragment) {
+ self.emit('data', self.currentMessage);
+ self.currentMessage = '';
+ }
+ self.endPacket();
+ }
+
+ var expectData = function(length) {
+ if (self.state.masked) {
+ self.expect('Mask', 4, function(data) {
+ var mask = data;
+ self.expect('Data', length, function(data) {
+ finish(mask, data);
+ });
+ });
+ }
+ else {
+ self.expect('Data', length, function(data) {
+ finish(null, data);
+ });
+ }
+ }
+
+ // decode length
+ var firstLength = data[1] & 0x7f;
+ if (firstLength < 126) {
+ expectData(firstLength);
+ }
+ else if (firstLength == 126) {
+ self.expect('Length', 2, function(data) {
+ expectData(util.unpack(data));
+ });
+ }
+ else if (firstLength == 127) {
+ self.expect('Length', 8, function(data) {
+ if (util.unpack(data.slice(0, 4)) != 0) {
+ self.error('packets with length spanning more than 32 bit is currently not supported');
+ return;
+ }
+ var lengthBytes = data.slice(4); // note: cap to 32 bit length
+ expectData(util.unpack(data));
+ });
+ }
+ },
+ // close
+ '8': function(data) {
+ self.emit('close');
+ self.reset();
+ },
+ // ping
+ '9': function(data) {
+ if (self.state.lastFragment == false) {
+ self.error('fragmented ping is not supported');
+ return;
+ }
+
+ var finish = function(mask, data) {
+ self.emit('ping', self.unmask(mask, data));
+ self.endPacket();
+ }
+
+ var expectData = function(length) {
+ if (self.state.masked) {
+ self.expect('Mask', 4, function(data) {
+ var mask = data;
+ self.expect('Data', length, function(data) {
+ finish(mask, data);
+ });
+ });
+ }
+ else {
+ self.expect('Data', length, function(data) {
+ finish(null, data);
+ });
+ }
+ }
+
+ // decode length
+ var firstLength = data[1] & 0x7f;
+ if (firstLength == 0) {
+ finish(null, null);
+ }
+ else if (firstLength < 126) {
+ expectData(firstLength);
+ }
+ else if (firstLength == 126) {
+ self.expect('Length', 2, function(data) {
+ expectData(util.unpack(data));
+ });
+ }
+ else if (firstLength == 127) {
+ self.expect('Length', 8, function(data) {
+ expectData(util.unpack(data));
+ });
+ }
+ }
+ }
+
+ this.expect('Opcode', 2, this.processPacket);
+};
+
+/**
+ * Inherits from EventEmitter.
+ */
+
+Parser.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Add new data to the parser.
+ *
+ * @api public
+ */
+
+Parser.prototype.add = function(data) {
+ if (this.expectBuffer == null) {
+ this.addToOverflow(data);
+ return;
+ }
+ var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
+ data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
+ this.expectOffset += toRead;
+ if (toRead < data.length) {
+ // at this point the overflow buffer shouldn't at all exist
+ this.overflow = new Buffer(data.length - toRead);
+ data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
+ }
+ if (this.expectOffset == this.expectBuffer.length) {
+ var bufferForHandler = this.expectBuffer;
+ this.expectBuffer = null;
+ this.expectOffset = 0;
+ this.expectHandler.call(this, bufferForHandler);
+ }
+}
+
+/**
+ * Adds a piece of data to the overflow.
+ *
+ * @api private
+ */
+
+Parser.prototype.addToOverflow = function(data) {
+ if (this.overflow == null) this.overflow = data;
+ else {
+ var prevOverflow = this.overflow;
+ this.overflow = new Buffer(this.overflow.length + data.length);
+ prevOverflow.copy(this.overflow, 0);
+ data.copy(this.overflow, prevOverflow.length);
+ }
+}
+
+/**
+ * Waits for a certain amount of bytes to be available, then fires a callback.
+ *
+ * @api private
+ */
+
+Parser.prototype.expect = function(what, length, handler) {
+ this.expectBuffer = new Buffer(length);
+ this.expectOffset = 0;
+ this.expectHandler = handler;
+ if (this.overflow != null) {
+ var toOverflow = this.overflow;
+ this.overflow = null;
+ this.add(toOverflow);
+ }
+}
+
+/**
+ * Start processing a new packet.
+ *
+ * @api private
+ */
+
+Parser.prototype.processPacket = function (data) {
+ if ((data[0] & 0x70) != 0) this.error('reserved fields not empty');
+ this.state.lastFragment = (data[0] & 0x80) == 0x80;
+ this.state.masked = (data[1] & 0x80) == 0x80;
+ var opcode = data[0] & 0xf;
+ if (opcode == 0) {
+ // continuation frame
+ if (this.state.opcode != 1 || this.state.opcode != 2) {
+ this.error('continuation frame cannot follow current opcode')
+ return;
+ }
+ }
+ else this.state.opcode = opcode;
+ this.state.opcode = data[0] & 0xf;
+ var handler = this.opcodeHandlers[this.state.opcode];
+ if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
+ else handler(data);
+}
+
+/**
+ * Endprocessing a packet.
+ *
+ * @api private
+ */
+
+Parser.prototype.endPacket = function() {
+ this.expectOffset = 0;
+ this.expectBuffer = null;
+ this.expectHandler = null;
+ if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
+ // end current fragmented operation
+ this.state.activeFragmentedOperation = null;
+ }
+ this.state.lastFragment = false;
+ this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
+ this.state.masked = false;
+ this.expect('Opcode', 2, this.processPacket);
+}
+
+/**
+ * Reset the parser state.
+ *
+ * @api private
+ */
+
+Parser.prototype.reset = function() {
+ this.state = {
+ activeFragmentedOperation: null,
+ lastFragment: false,
+ masked: false,
+ opcode: 0
+ };
+ this.expectOffset = 0;
+ this.expectBuffer = null;
+ this.expectHandler = null;
+ this.overflow = null;
+ this.currentMessage = '';
+}
+
+/**
+ * Unmask received data.
+ *
+ * @api private
+ */
+
+Parser.prototype.unmask = function (mask, buf) {
+ if (mask != null) {
+ for (var i = 0, ll = buf.length; i < ll; i++) {
+ buf[i] ^= mask[i % 4];
+ }
+ }
+ return buf != null ? buf.toString('utf8') : '';
+}
+
+/**
+ * Handles an error
+ *
+ * @api private
+ */
+
+Parser.prototype.error = function (reason) {
+ this.reset();
+ this.emit('error', reason);
+ return this;
+};
--- /dev/null
+
+/**
+ * Export websocket versions.
+ */
+
+module.exports = {
+ 7: require('./hybi-07-12'),
+ 8: require('./hybi-07-12'),
+ default: require('./default')
+};
return arr;
};
+
+/**
+ * Unpacks a buffer to a number.
+ *
+ * @api public
+ */
+
+exports.unpack = function (buffer) {
+ var n = 0;
+ for (var i = 0; i < buffer.length; ++i) {
+ n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
+ }
+ return n;
+}
+
+/**
+ * Left pads a string.
+ *
+ * @api public
+ */
+
+exports.padl = function (s,n,c) {
+ return new Array(1 + n - s.length).join(c) + s;
+}
+
+++ /dev/null
-
-var express = require('express')
- , stylus = require('stylus');
-
-var app = express.createServer();
-
-app.configure(function () {
- app.use(stylus.middleware({ src: __dirname + '/public/css' }));
- app.use(express.static(__dirname + '/public'));
- app.set('views', __dirname);
- app.set('view engine', 'jade');
-});
-
-app.get('/', function (req, res) {
- res.render('index');
-});
-
-app.get('/new', function (req, res) {
- res.render('new');
-});
-
-app.listen(3000);
+++ /dev/null
-e inde doctype 5
-html
- head
- title Socket.IO: realtime cross-browser web apps toolkit.
- link(href='css/main.css', rel='stylesheet', media='all')
-
- script(src='js/main.js')
- script
- var _gaq = _gaq || [];
- _gaq.push(['_setAccount', 'UA-18488944-1']);
- _gaq.push(['_trackPageview']);
-
- (function() {
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- })();
- body
- #wrap
- #header
- .subtext Introducing
- a(href='/', class='logo') Socket.IO
- a(href='http://github.com/learnboost/socket.io-node', class='download')
- span.version v
- span.version .7
- #content
- .section
- form(action='http://groups.google.com/group/socket_io/boxsubscribe')
- a#google-subscribe-link(href='http://groups.google.com/group/socket_io')
- img(src='images/groups.png', border='0')
- #google-subscribe-input
- | Email:
- input#google-subscribe-email(type='text', name='email')
- input(type='submit', name='go', value='Subscribe')
+++ /dev/null
-doctype 5
-html
- head
- title Socket.IO: realtime cross-browser web apps toolkit.
- link(href='css/main.css', rel='stylesheet', media='all')
-
- script(src='js/main.js')
- script
- var _gaq = _gaq || [];
- _gaq.push(['_setAccount', 'UA-18488944-1']);
- _gaq.push(['_trackPageview']);
-
- (function() {
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- })();
- body
- #wrap
- #header
- .subtext Introducing
- a(href='/', class='logo') Socket.IO
- a(href='http://github.com/learnboost/socket.io-node', class='download')
- span.version v
- span.version .6
- #content
- .section
- form(action='http://groups.google.com/group/socket_io/boxsubscribe')
- a#google-subscribe-link(href='http://groups.google.com/group/socket_io')
- img(src='images/groups.png', border='0')
- #google-subscribe-input
- | Email:
- input#google-subscribe-email(type='text', name='email')
- input(type='submit', name='go', value='Subscribe')
- .section#menu
- ul
- li.current: a(href='#') Home
- li.current: a(href='#') Projects
- li.current: a(href='#') Docs
- li.current: a(href='#') FAQ
- .section
- a(href='/new') Socket.IO 0.7 is out! Click here to see what's new and what's changed.
+++ /dev/null
-function getMembers(data){
- if (!(data && data.query && data.query.results && data.query.results.p)) return;
- var members = document.createElement('span');
- members.id = 'google-members-count';
- members.innerHTML = '('+ data.query.results.p +' members)';
- document.getElementsByTagName('FORM')[0].insertBefore(members, document.getElementById('google-subscribe-input'));
-};
-
-window.onload = function(){
- document.getElementById('google-subscribe-email').onfocus = function(){
- document.getElementById('google-subscribe-input').className = 'focus';
- };
- document.getElementById('google-subscribe-email').onblur = function(){
- document.getElementById('google-subscribe-input').className = '';
- };
-
- // lame jsonp
- var script = document.createElement('script');
- // yql: select * from html where url="http://groups.google.com/group/socket_io/about" and xpath='//div[@class=\'maincontbox\']/table/tr[1]/td/p[1]
- script.src = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22http%3A%2F%2Fgroups.google.com%2Fgroup%2Fsocket_io%2Fabout%22%20and%20xpath%3D'%2F%2Fdiv%5B%40class%3D%5C'maincontbox%5C'%5D%2Ftable%2Ftr%5B1%5D%2Ftd%2Fp%5B1%5D'%0A&format=json&callback=getMembers";
- document.head.appendChild(script);
-};
{
"name": "socket.io"
- , "version": "0.7.7"
+ , "version": "0.8.4"
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
, "homepage": "http://socket.io"
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
+ , { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
]
, "repository":{
"type": "git"
- , "url": "https://github.com/LearnBoost/Socket.IO-node.git"
+ , "url": "https://github.com/LearnBoost/socket.io.git"
}
, "dependencies": {
- "socket.io-client": "0.7.4"
- , "policyfile": "0.0.3"
- , "redis": "0.6.0"
+ "socket.io-client": "0.8.4"
+ , "policyfile": "0.0.4"
+ , "redis": "0.6.6"
}
, "devDependencies": {
"expresso": "0.7.7"
, "should": "0.0.4"
+ , "assertvanish": "0.0.3-1"
}
, "main": "index"
, "engines": { "node": ">= 0.4.0" }
--- /dev/null
+Copyright (c) 2010, Peter Griess <pg@std.in>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of node-websocket-client nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+# This makefile exists to help run tests.
+#
+# If TEST_UNIX is a non-empty value, runs tests for UNIX sockets. This
+# functionality is not in node-websocket-server at the moment.
+
+.PHONY: test
+
+all: test test-unix
+
+test:
+ for f in `ls -1 test/test-*.js | grep -v unix` ; do \
+ echo $$f ; \
+ node $$f ; \
+ done
+
+test-unix:
+ if [[ -n "$$TEST_UNIX" ]] ; then \
+ for f in `ls -1 test/test-*.js | grep unix` ; do \
+ echo $$f ; \
+ node $$f ; \
+ done \
+ fi
--- /dev/null
+A prototype [Web Socket](http://www.whatwg.org/specs/web-socket-protocol/)
+client implementation for [node.js](http://nodejs.org).
+
+Tested with
+[miksago/node-websocket-server](http://github.com/miksago/node-websocket-server)
+v1.2.00.
+
+Requires [nodejs](http://nodejs.org) 0.1.98 or later.
+
+## Installation
+
+Install this using `npm` as follows
+
+ npm install websocket-client
+
+... or just dump `lib/websocket.js` in your `$NODE_PATH`.
+
+## Usage
+
+ var sys = require('sys');
+ var WebSocket = require('websocket').WebSocket;
+
+ var ws = new WebSocket('ws://localhost:8000/biff', 'borf');
+ ws.addListener('data', function(buf) {
+ sys.debug('Got data: ' + sys.inspect(buf));
+ });
+ ws.onmessage = function(m) {
+ sys.debug('Got message: ' + m);
+ }
+
+## API
+
+This supports the `send()` and `onmessage()` APIs. The `WebSocket` object will
+also emit `data` events that are node `Buffer` objects, in case you want to
+work with something lower-level than strings.
+
+## Transports
+
+Multiple transports are supported, indicated by the scheme provided to the
+`WebSocket` constructor. `ws://` is a standard TCP-based Web Socket;
+`ws+unix://` allows connection to a UNIX socket at the given path.
--- /dev/null
+var sys = require('sys');
+var WebSocket = require('../lib/websocket').WebSocket;
+
+var ws = new WebSocket('ws+unix://' + process.argv[2], 'boffo');
+
+ws.addListener('message', function(d) {
+ sys.debug('Received message: ' + d.toString('utf8'));
+});
+
+ws.addListener('open', function() {
+ ws.send('This is a message', 1);
+});
--- /dev/null
+var sys = require('sys');
+var WebSocket = require('../lib/websocket').WebSocket;
+
+var ws = new WebSocket('ws://localhost:8000/biff', 'borf');
+ws.addListener('data', function(buf) {
+ sys.debug('Got data: ' + sys.inspect(buf));
+});
+ws.onmessage = function(m) {
+ sys.debug('Got message: ' + m);
+}
--- /dev/null
+var sys = require('sys');
+var ws = require('websocket-server/ws');
+
+var srv = ws.createServer({ debug : true});
+srv.addListener('connection', function(s) {
+ sys.debug('Got a connection!');
+
+ s._req.socket.addListener('fd', function(fd) {
+ sys.debug('Got an fd: ' + fd);
+ });
+});
+
+srv.listen(process.argv[2]);
--- /dev/null
+var assert = require('assert');
+var buffer = require('buffer');
+var crypto = require('crypto');
+var events = require('events');
+var http = require('http');
+var net = require('net');
+var urllib = require('url');
+var sys = require('sys');
+
+var FRAME_NO = 0;
+var FRAME_LO = 1;
+var FRAME_HI = 2;
+
+// Values for readyState as per the W3C spec
+var CONNECTING = 0;
+var OPEN = 1;
+var CLOSING = 2;
+var CLOSED = 3;
+
+var debugLevel = parseInt(process.env.NODE_DEBUG, 16);
+var debug = (debugLevel & 0x4) ?
+ function() { sys.error.apply(this, arguments); } :
+ function() { };
+
+// Generate a Sec-WebSocket-* value
+var createSecretKey = function() {
+ // How many spaces will we be inserting?
+ var numSpaces = 1 + Math.floor(Math.random() * 12);
+ assert.ok(1 <= numSpaces && numSpaces <= 12);
+
+ // What is the numerical value of our key?
+ var keyVal = (Math.floor(
+ Math.random() * (4294967295 / numSpaces)
+ ) * numSpaces);
+
+ // Our string starts with a string representation of our key
+ var s = keyVal.toString();
+
+ // Insert 'numChars' worth of noise in the character ranges
+ // [0x21, 0x2f] (14 characters) and [0x3a, 0x7e] (68 characters)
+ var numChars = 1 + Math.floor(Math.random() * 12);
+ assert.ok(1 <= numChars && numChars <= 12);
+
+ for (var i = 0; i < numChars; i++) {
+ var pos = Math.floor(Math.random() * s.length + 1);
+
+ var c = Math.floor(Math.random() * (14 + 68));
+ c = (c <= 14) ?
+ String.fromCharCode(c + 0x21) :
+ String.fromCharCode((c - 14) + 0x3a);
+
+ s = s.substring(0, pos) + c + s.substring(pos, s.length);
+ }
+
+ // We shoudln't have any spaces in our value until we insert them
+ assert.equal(s.indexOf(' '), -1);
+
+ // Insert 'numSpaces' worth of spaces
+ for (var i = 0; i < numSpaces; i++) {
+ var pos = Math.floor(Math.random() * (s.length - 1)) + 1;
+ s = s.substring(0, pos) + ' ' + s.substring(pos, s.length);
+ }
+
+ assert.notEqual(s.charAt(0), ' ');
+ assert.notEqual(s.charAt(s.length), ' ');
+
+ return s;
+};
+
+// Generate a challenge sequence
+var createChallenge = function() {
+ var c = '';
+ for (var i = 0; i < 8; i++) {
+ c += String.fromCharCode(Math.floor(Math.random() * 255));
+ }
+
+ return c;
+};
+
+// Get the value of a secret key string
+//
+// This strips non-digit values and divides the result by the number of
+// spaces found.
+var secretKeyValue = function(sk) {
+ var ns = 0;
+ var v = 0;
+
+ for (var i = 0; i < sk.length; i++) {
+ var cc = sk.charCodeAt(i);
+
+ if (cc == 0x20) {
+ ns++;
+ } else if (0x30 <= cc && cc <= 0x39) {
+ v = v * 10 + cc - 0x30;
+ }
+ }
+
+ return Math.floor(v / ns);
+}
+
+// Get the to-be-hashed value of a secret key string
+//
+// This takes the result of secretKeyValue() and encodes it in a big-endian
+// byte string
+var secretKeyHashValue = function(sk) {
+ var skv = secretKeyValue(sk);
+
+ var hv = '';
+ hv += String.fromCharCode((skv >> 24) & 0xff);
+ hv += String.fromCharCode((skv >> 16) & 0xff);
+ hv += String.fromCharCode((skv >> 8) & 0xff);
+ hv += String.fromCharCode((skv >> 0) & 0xff);
+
+ return hv;
+};
+
+// Compute the secret key signature based on two secret key strings and some
+// handshaking data.
+var computeSecretKeySignature = function(s1, s2, hs) {
+ assert.equal(hs.length, 8);
+
+ var hash = crypto.createHash('md5');
+
+ hash.update(secretKeyHashValue(s1));
+ hash.update(secretKeyHashValue(s2));
+ hash.update(hs);
+
+ return hash.digest('binary');
+};
+
+// Return a hex representation of the given binary string; used for debugging
+var str2hex = function(str) {
+ var hexChars = [
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f'
+ ];
+
+ var out = '';
+ for (var i = 0; i < str.length; i++) {
+ var c = str.charCodeAt(i);
+ out += hexChars[(c & 0xf0) >>> 4];
+ out += hexChars[c & 0x0f];
+ out += ' ';
+ }
+
+ return out.trim();
+};
+
+// Get the scheme for a URL, undefined if none is found
+var getUrlScheme = function(url) {
+ var i = url.indexOf(':');
+ if (i == -1) {
+ return undefined;
+ }
+
+ return url.substring(0, i);
+};
+
+// Set a constant on the given object
+var setConstant = function(obj, name, value) {
+ Object.defineProperty(obj, name, {
+ get : function() {
+ return value;
+ }
+ });
+};
+
+// WebSocket object
+//
+// This is intended to conform (mostly) to http://dev.w3.org/html5/websockets/
+//
+// N.B. Arguments are parsed in the anonymous function at the bottom of the
+// constructor.
+var WebSocket = function(url, proto, opts) {
+ events.EventEmitter.call(this);
+
+ // Retain a reference to our object
+ var self = this;
+
+ // State of our end of the connection
+ var readyState = CONNECTING;
+
+ // Whether or not the server has sent a close handshake
+ var serverClosed = false;
+
+ // Our underlying net.Stream instance
+ var stream = undefined;
+
+ opts = opts || {
+ origin : 'http://www.example.com'
+ };
+
+ // Frame parsing functions
+ //
+ // These read data from the given buffer starting at the given offset,
+ // looking for the end of the current frame. If found, the current frame is
+ // emitted and the function returns. Only a single frame is processed at a
+ // time.
+ //
+ // The number of bytes read to complete a frame is returned, which the
+ // caller is to use to advance along its buffer. If 0 is returned, no
+ // completed frame bytes were found, and the caller should probably enqueue
+ // the buffer as a continuation of the current message. If a complete frame
+ // is read, the function is responsible for resting 'frameType'.
+
+ // Framing data
+ var frameType = FRAME_NO;
+ var bufs = [];
+ var bufsBytes = 0;
+
+ // Frame-parsing functions
+ var frameFuncs = [
+ // FRAME_NO
+ function(buf, off) {
+ if (buf[off] & 0x80) {
+ frameType = FRAME_HI;
+ } else {
+ frameType = FRAME_LO;
+ }
+
+ return 1;
+ },
+
+ // FRAME_LO
+ function(buf, off) {
+ debug('frame_lo(' + sys.inspect(buf) + ', ' + off + ')');
+
+ // Find the first instance of 0xff, our terminating byte
+ for (var i = off; i < buf.length && buf[i] != 0xff; i++)
+ ;
+
+ // We didn't find a terminating byte
+ if (i >= buf.length) {
+ return 0;
+ }
+
+ // We found a terminating byte; collect all bytes into a single buffer
+ // and emit it
+ var mb = null;
+ if (bufs.length == 0) {
+ mb = buf.slice(off, i);
+ } else {
+ mb = new buffer.Buffer(bufsBytes + i);
+
+ var mbOff = 0;
+ bufs.forEach(function(b) {
+ b.copy(mb, mbOff, 0, b.length);
+ mbOff += b.length;
+ });
+
+ assert.equal(mbOff, bufsBytes);
+
+ // Don't call Buffer.copy() if we're coping 0 bytes. Rather
+ // than being a no-op, this will trigger a range violation on
+ // the destination.
+ if (i > 0) {
+ buf.copy(mb, mbOff, off, i);
+ }
+
+ // We consumed all of the buffers that we'd been saving; clear
+ // things out
+ bufs = [];
+ bufsBytes = 0;
+ }
+
+ process.nextTick(function() {
+ var b = mb;
+ return function() {
+ var m = b.toString('utf8');
+
+ self.emit('data', b);
+ self.emit('message', m); // wss compat
+
+ if (self.onmessage) {
+ self.onmessage({data: m});
+ }
+ };
+ }());
+
+ frameType = FRAME_NO;
+ return i - off + 1;
+ },
+
+ // FRAME_HI
+ function(buf, off) {
+ debug('frame_hi(' + sys.inspect(buf) + ', ' + off + ')');
+
+ if (buf[off] !== 0) {
+ throw new Error('High-byte framing not supported.');
+ }
+
+ serverClosed = true;
+ return 1;
+ }
+ ];
+
+ // Handle data coming from our socket
+ var dataListener = function(buf) {
+ if (buf.length <= 0 || serverClosed) {
+ return;
+ }
+
+ debug('dataListener(' + sys.inspect(buf) + ')');
+
+ var off = 0;
+ var consumed = 0;
+
+ do {
+ if (frameType < 0 || frameFuncs.length <= frameType) {
+ throw new Error('Unexpected frame type: ' + frameType);
+ }
+
+ assert.equal(bufs.length === 0, bufsBytes === 0);
+ assert.ok(off < buf.length);
+
+ consumed = frameFuncs[frameType](buf, off);
+ off += consumed;
+ } while (!serverClosed && consumed > 0 && off < buf.length);
+
+ if (serverClosed) {
+ serverCloseHandler();
+ }
+
+ if (consumed == 0) {
+ bufs.push(buf.slice(off, buf.length));
+ bufsBytes += buf.length - off;
+ }
+ };
+
+ // Handle incoming file descriptors
+ var fdListener = function(fd) {
+ self.emit('fd', fd);
+ };
+
+ // Handle errors from any source (HTTP client, stream, etc)
+ var errorListener = function(e) {
+ process.nextTick(function() {
+ self.emit('wserror', e);
+
+ if (self.onerror) {
+ self.onerror(e);
+ }
+ });
+ };
+
+ // Finish the closing process; destroy the socket and tell the application
+ // that we've closed.
+ var finishClose = self.finishClose = function() {
+ readyState = CLOSED;
+
+ if (stream) {
+ stream.end();
+ stream.destroy();
+ stream = undefined;
+ }
+
+ process.nextTick(function() {
+ self.emit('close');
+ if (self.onclose) {
+ self.onclose();
+ }
+ });
+ };
+
+ // Send a close frame to the server
+ var sendClose = function() {
+ assert.equal(OPEN, readyState);
+
+ readyState = CLOSING;
+ stream.write('\xff\x00', 'binary');
+ };
+
+ // Handle a close packet sent from the server
+ var serverCloseHandler = function() {
+ assert.ok(serverClosed);
+ assert.ok(readyState === OPEN || readyState === CLOSING);
+
+ bufs = [];
+ bufsBytes = 0;
+
+ // Handle state transitions asynchronously so that we don't change
+ // readyState before the application has had a chance to process data
+ // events which are already in the delivery pipeline. For example, a
+ // 'data' event could be delivered with a readyState of CLOSING if we
+ // received both frames in the same packet.
+ process.nextTick(function() {
+ if (readyState === OPEN) {
+ sendClose();
+ }
+
+ finishClose();
+ });
+ };
+
+ // External API
+ self.close = function(timeout) {
+ if (readyState === CONNECTING) {
+ // If we're still in the process of connecting, the server is not
+ // in a position to understand our close frame. Just nuke the
+ // connection and call it a day.
+ finishClose();
+ } else if (readyState === OPEN) {
+ sendClose();
+
+ if (timeout) {
+ setTimeout(finishClose, timeout * 1000);
+ }
+ }
+ };
+
+ self.send = function(str, fd) {
+ if (readyState != OPEN) {
+ return;
+ }
+
+ stream.write('\x00', 'binary');
+ stream.write(str, 'utf8', fd);
+ stream.write('\xff', 'binary');
+ };
+
+ // wss compat
+ self.write = self.send;
+
+ setConstant(self, 'url', url);
+
+ Object.defineProperty(self, 'readyState', {
+ get : function() {
+ return readyState;
+ }
+ });
+
+ // Connect and perform handshaking with the server
+ (function() {
+ // Parse constructor arguments
+ if (!url) {
+ throw new Error('Url and must be specified.');
+ }
+
+ // Secrets used for handshaking
+ var key1 = createSecretKey();
+ var key2 = createSecretKey();
+ var challenge = createChallenge();
+
+ debug(
+ 'key1=\'' + str2hex(key1) + '\'; ' +
+ 'key2=\'' + str2hex(key2) + '\'; ' +
+ 'challenge=\'' + str2hex(challenge) + '\''
+ );
+
+ var httpHeaders = {
+ 'Connection' : 'Upgrade',
+ 'Upgrade' : 'WebSocket',
+ 'Sec-WebSocket-Key1' : key1,
+ 'Sec-WebSocket-Key2' : key2
+ };
+ if (opts.origin) {
+ httpHeaders['Origin'] = opts.origin;
+ }
+ if (proto) {
+ httpHeaders['Sec-WebSocket-Protocol'] = proto;
+ }
+
+ var httpPath = '/';
+
+ // Create the HTTP client that we'll use for handshaking. We'll cannabalize
+ // its socket via the 'upgrade' event and leave it to rot.
+ //
+ // N.B. The ws+unix:// scheme makes use of the implementation detail
+ // that http.Client passes its constructor arguments through,
+ // un-inspected to net.Stream.connect(). The latter accepts a
+ // string as its first argument to connect to a UNIX socket.
+ var httpClient = undefined;
+ switch (getUrlScheme(url)) {
+ case 'ws':
+ var u = urllib.parse(url);
+ httpClient = http.createClient(u.port || 80, u.hostname);
+ httpPath = (u.pathname || '/') + (u.search || '');
+ httpHeaders.Host = u.hostname + (u.port ? (":" + u.port) : "");
+ break;
+
+ case 'ws+unix':
+ var sockPath = url.substring('ws+unix://'.length, url.length);
+ httpClient = http.createClient(sockPath);
+ httpHeaders.Host = 'localhost';
+ break;
+
+ default:
+ throw new Error('Invalid URL scheme \'' + urlScheme + '\' specified.');
+ }
+
+ httpClient.on('upgrade', (function() {
+ var data = undefined;
+
+ return function(req, s, head) {
+ stream = s;
+
+ stream.on('data', function(d) {
+ if (d.length <= 0) {
+ return;
+ }
+
+ if (!data) {
+ data = d;
+ } else {
+ var data2 = new buffer.Buffer(data.length + d.length);
+
+ data.copy(data2, 0, 0, data.length);
+ d.copy(data2, data.length, 0, d.length);
+
+ data = data2;
+ }
+
+ if (data.length >= 16) {
+ var expected = computeSecretKeySignature(key1, key2, challenge);
+ var actual = data.slice(0, 16).toString('binary');
+
+ // Handshaking fails; we're donezo
+ if (actual != expected) {
+ debug(
+ 'expected=\'' + str2hex(expected) + '\'; ' +
+ 'actual=\'' + str2hex(actual) + '\''
+ );
+
+ process.nextTick(function() {
+ // N.B. Emit 'wserror' here, as 'error' is a reserved word in the
+ // EventEmitter world, and gets thrown.
+ self.emit(
+ 'wserror',
+ new Error('Invalid handshake from server:' +
+ 'expected \'' + str2hex(expected) + '\', ' +
+ 'actual \'' + str2hex(actual) + '\''
+ )
+ );
+
+ if (self.onerror) {
+ self.onerror();
+ }
+
+ finishClose();
+ });
+ }
+
+ // Un-register our data handler and add the one to be used
+ // for the normal, non-handshaking case. If we have extra
+ // data left over, manually fire off the handler on
+ // whatever remains.
+ //
+ // XXX: This is lame. We should only remove the listeners
+ // that we added.
+ httpClient.removeAllListeners('upgrade');
+ stream.removeAllListeners('data');
+ stream.on('data', dataListener);
+
+ readyState = OPEN;
+
+ process.nextTick(function() {
+ self.emit('open');
+
+ if (self.onopen) {
+ self.onopen();
+ }
+ });
+
+ // Consume any leftover data
+ if (data.length > 16) {
+ stream.emit('data', data.slice(16, data.length));
+ }
+ }
+ });
+ stream.on('fd', fdListener);
+ stream.on('error', errorListener);
+ stream.on('close', function() {
+ errorListener(new Error('Stream closed unexpectedly.'));
+ });
+
+ stream.emit('data', head);
+ };
+ })());
+ httpClient.on('error', function(e) {
+ httpClient.end();
+ errorListener(e);
+ });
+
+ var httpReq = httpClient.request(httpPath, httpHeaders);
+
+ httpReq.write(challenge, 'binary');
+ httpReq.end();
+ })();
+};
+sys.inherits(WebSocket, events.EventEmitter);
+exports.WebSocket = WebSocket;
+
+// Add some constants to the WebSocket object
+setConstant(WebSocket.prototype, 'CONNECTING', CONNECTING);
+setConstant(WebSocket.prototype, 'OPEN', OPEN);
+setConstant(WebSocket.prototype, 'CLOSING', CLOSING);
+setConstant(WebSocket.prototype, 'CLOSED', CLOSED);
+
+// vim:ts=4 sw=4 et
--- /dev/null
+{
+ "name" : "websocket-client",
+ "version" : "1.0.0",
+ "description" : "An HTML5 Web Sockets client",
+ "author" : "Peter Griess <pg@std.in>",
+ "engines" : {
+ "node" : ">=0.1.98"
+ },
+ "repositories" : [
+ {
+ "type" : "git",
+ "url" : "http://github.com/pgriess/node-websocket-client.git"
+ }
+ ],
+ "licenses" : [
+ {
+ "type" : "BSD",
+ "url" : "http://github.com/pgriess/node-websocket-client/blob/master/LICENSE"
+ }
+ ],
+ "main" : "./lib/websocket"
+}
--- /dev/null
+// Verify that we can connect to a WebSocket server, exchange messages, and
+// shut down cleanly.
+
+var assert = require('assert');
+var WebSocket = require('../lib/websocket').WebSocket;
+var WebSocketServer = require('websocket-server/ws/server').Server;
+
+var PORT = 1024 + Math.floor(Math.random() * 4096);
+var C_MSG = 'Client test: ' + (Math.random() * 100);
+var S_MSG = 'Server test: ' + (Math.random() * 100);
+
+var serverGotConnection = false;
+var clientGotOpen = false;
+var clientGotData = false;
+var clientGotMessage = false;
+var serverGotMessage = false;
+var serverGotClose = false;
+var clientGotClose = false;
+
+var wss = new WebSocketServer();
+wss.listen(PORT, 'localhost');
+wss.on('connection', function(c) {
+ serverGotConnection = true;
+
+ c.on('message', function(m) {
+ assert.equal(m, C_MSG);
+ serverGotMessage = true;
+
+ c.close();
+ });
+
+ c.on('close', function() {
+ serverGotClose = true;
+ wss.close();
+ });
+
+ c.write(S_MSG);
+});
+
+var ws = new WebSocket('ws://localhost:' + PORT + '/', 'biff');
+ws.on('open', function() {
+ clientGotOpen = true;
+});
+ws.on('data', function(buf) {
+ assert.equal(typeof buf, 'object');
+ assert.equal(buf.toString('utf8'), S_MSG);
+
+ clientGotData = true;
+
+ ws.send(C_MSG);
+});
+ws.onmessage = function(m) {
+ assert.deepEqual(m, {data : S_MSG});
+ clientGotMessage = true;
+};
+ws.onclose = function() {
+ clientGotClose = true;
+};
+
+process.on('exit', function() {
+ assert.ok(serverGotConnection);
+ assert.ok(clientGotOpen);
+ assert.ok(clientGotData);
+ assert.ok(clientGotMessage);
+ assert.ok(serverGotMessage);
+ assert.ok(serverGotClose);
+ assert.ok(clientGotClose);
+});
--- /dev/null
+// Verify that a connection can be closed gracefully from the client.
+
+var assert = require('assert');
+var WebSocket = require('../lib/websocket').WebSocket;
+var WebSocketServer = require('websocket-server/ws/server').Server;
+
+var PORT = 1024 + Math.floor(Math.random() * 4096);
+var C_MSG = 'Client test: ' + (Math.random() * 100);
+
+var serverGotClientMessage = false;
+var clientGotServerClose = false;
+var serverGotClientClose = false;
+
+var wss = new WebSocketServer();
+wss.listen(PORT, 'localhost');
+wss.on('connection', function(c) {
+ c.on('message', function(m) {
+ assert.equal(m, C_MSG);
+ serverGotClientMessage = true;
+ });
+ c.on('close', function() {
+ serverGotClientClose = true;
+ wss.close();
+ });
+});
+
+var ws = new WebSocket('ws://localhost:' + PORT);
+ws.onopen = function() {
+ ws.send(C_MSG);
+
+ // XXX: Add a timeout here
+ ws.close(5);
+};
+ws.onclose = function() {
+ assert.equal(ws.CLOSED, ws.readyState);
+ clientGotServerClose = true;
+};
+
+process.on('exit', function() {
+ assert.ok(serverGotClientMessage);
+ assert.ok(clientGotServerClose);
+ assert.ok(serverGotClientClose);
+});
--- /dev/null
+// Verify that some attributes of a WebSocket object are read-only.
+
+var assert = require('assert');
+var sys = require('sys');
+var WebSocket = require('../lib/websocket').WebSocket;
+var WebSocketServer = require('websocket-server/ws/server').Server;
+
+var PORT = 1024 + Math.floor(Math.random() * 4096);
+
+var wss = new WebSocketServer();
+wss.listen(PORT, 'localhost');
+wss.on('connection', function(c) {
+ c.close();
+ wss.close();
+});
+var ws = new WebSocket('ws://localhost:' + PORT + '/', 'biff');
+ws.on('open', function() {
+ assert.equal(ws.CONNECTING, 0);
+ try {
+ ws.CONNECTING = 13;
+ assert.equal(
+ ws.CONNECTING, 0,
+ 'Should not have been able to set read-only CONNECTING attribute'
+ );
+ } catch (e) {
+ assert.equal(e.type, 'no_setter_in_callback');
+ }
+
+ assert.equal(ws.OPEN, 1);
+ assert.equal(ws.CLOSING, 2);
+ assert.equal(ws.CLOSED, 3);
+
+ assert.equal(ws.url, 'ws://localhost:' + PORT + '/');
+ try {
+ ws.url = 'foobar';
+ assert.equal(
+ ws.url, 'ws://localhost:' + PORT + '/',
+ 'Should not have been able to set read-only url attribute'
+ );
+ } catch (e) {
+ assert.equal(e.type, 'no_setter_in_callback');
+ }
+});
--- /dev/null
+// Verify that readyState transitions are implemented correctly
+
+var assert = require('assert');
+var WebSocket = require('../lib/websocket').WebSocket;
+var WebSocketServer = require('websocket-server/ws/server').Server;
+
+var PORT = 1024 + Math.floor(Math.random() * 4096);
+
+var wss = new WebSocketServer();
+wss.listen(PORT, 'localhost');
+wss.on('connection', function(c) {
+ c.close();
+});
+
+var ws = new WebSocket('ws://localhost:' + PORT);
+assert.equal(ws.readyState, ws.CONNECTING);
+ws.onopen = function() {
+ assert.equal(ws.readyState, ws.OPEN);
+
+ ws.close();
+ assert.ok(ws.readyState == ws.CLOSING);
+};
+ws.onclose = function() {
+ assert.equal(ws.readyState, ws.CLOSED);
+ wss.close();
+};
--- /dev/null
+// Verify that a connection can be closed gracefully from the server.
+
+var assert = require('assert');
+var WebSocket = require('../lib/websocket').WebSocket;
+var WebSocketServer = require('websocket-server/ws/server').Server;
+
+var PORT = 1024 + Math.floor(Math.random() * 4096);
+var S_MSG = 'Server test: ' + (Math.random() * 100);
+
+var clientGotServerMessage = false;
+var clientGotServerClose = false;
+var serverGotClientClose = false;
+
+var wss = new WebSocketServer();
+wss.listen(PORT, 'localhost');
+wss.on('connection', function(c) {
+ c.on('close', function() {
+ serverGotClientClose = true;
+ wss.close();
+ });
+
+ c.write(S_MSG);
+ c.close();
+});
+
+var ws = new WebSocket('ws://localhost:' + PORT);
+ws.onmessage = function(m) {
+ assert.deepEqual(m, {data: S_MSG});
+
+ clientGotServerMessage = true;
+};
+ws.onclose = function() {
+ assert.equal(ws.CLOSED, ws.readyState);
+ clientGotServerClose = true;
+};
+
+process.on('exit', function() {
+ assert.ok(clientGotServerMessage);
+ assert.ok(clientGotServerClose);
+ assert.ok(serverGotClientClose);
+});
--- /dev/null
+// Verify that both sides of the WS connection can both send and receive file
+// descriptors.
+
+var assert = require('assert');
+var fs = require('fs');
+var path = require('path');
+var sys = require('sys');
+var WebSocket = require('../lib/websocket').WebSocket;
+var WebSocketServer = require('websocket-server/ws/server').Server;
+
+var PATH = path.join(__dirname, 'sock.' + process.pid);
+var C_MSG = 'Client test: ' + (Math.random() * 100);
+var S_MSG = 'Server test: ' + (Math.random() * 100);
+
+var clientReceivedData = false;
+var clientReceivedFD = false;
+var serverReceivedData = false;
+var serverReceivedFD = false;
+
+var wss = new WebSocketServer();
+wss.on('listening', function() {
+ var ws = new WebSocket('ws+unix://' + PATH);
+ ws.on('data', function(d) {
+ assert.equal(d.toString('utf8'), S_MSG);
+
+ clientReceivedData = true;
+
+ ws.send(C_MSG, 1);
+ ws.close();
+ });
+ ws.on('fd', function(fd) {
+ assert.ok(fd >= 0);
+
+ clientReceivedFD = true;
+ });
+});
+wss.on('connection', function(c) {
+ c.write(S_MSG, 0);
+ c._req.socket.on('fd', function(fd) {
+ assert.ok(fd >= 0);
+
+ serverReceivedFD = true;
+ });
+ c.on('message', function(d) {
+ assert.equal(d, C_MSG);
+
+ serverReceivedData = true;
+
+ wss.close();
+ });
+});
+wss.listen(PATH);
+
+process.on('exit', function() {
+ assert.ok(clientReceivedFD);
+ assert.ok(clientReceivedData);
+ assert.ok(serverReceivedFD);
+ assert.ok(serverReceivedData);
+
+ try {
+ fs.unlinkSync(PATH);
+ } catch (e) { }
+});
--- /dev/null
+// Verify that we can connect to a server over UNIX domain sockets.
+
+var assert = require('assert');
+var fs = require('fs');
+var path = require('path');
+var sys = require('sys');
+var WebSocket = require('../lib/websocket').WebSocket;
+var WebSocketServer = require('websocket-server/ws/server').Server;
+
+var PATH = path.join(__dirname, 'sock.' + process.pid);
+var S_MSG = 'Server test: ' + (Math.random() * 100);
+
+var serverGotConnection = false;
+var clientGotOpen = false;
+var clientGotData = false;
+
+var wss = new WebSocketServer();
+wss.on('listening', function() {
+ var ws = new WebSocket('ws+unix://' + PATH);
+ ws.on('open', function() {
+ clientGotOpen = true;
+
+ ws.close();
+ });
+ ws.on('data', function(d) {
+ assert.equal(d.toString('utf8'), S_MSG);
+ clientGotData = true;
+ });
+});
+wss.on('connection', function(c) {
+ serverGotConnection = true;
+
+ c.write(S_MSG);
+ wss.close();
+});
+wss.listen(PATH);
+
+process.on('exit', function() {
+ assert.ok(serverGotConnection);
+ assert.ok(clientGotOpen);
+ assert.ok(clientGotData);
+
+ try {
+ fs.unlinkSync(PATH);
+ } catch(e) { }
+});
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var io = require('socket.io')
+ , parser = io.parser
+ , http = require('http')
+ , https = require('https')
+ , WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket;
+
+/**
+ * Exports.
+ */
+
+var should = module.exports = require('should');
+
+should.HTTPClient = HTTPClient;
+
+/**
+ * Client utility.
+ *
+ * @api publiC
+ */
+
+function HTTPClient (port) {
+ this.port = port;
+ this.agent = new http.Agent({
+ host: 'localhost'
+ , port: port
+ });
+};
+
+/**
+ * Issue a request
+ *
+ * @api private
+ */
+
+HTTPClient.prototype.request = function (path, opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ opts = opts || {};
+ opts.agent = this.agent;
+ opts.host = 'localhost';
+ opts.port = this.port;
+ opts.path = path.replace(/{protocol}/g, io.protocol);
+
+ opts.headers = opts.headers || {};
+ opts.headers.Host = 'localhost';
+ opts.headers.Connection = 'keep-alive';
+
+ var req = http.request(opts, function (res) {
+ if (false === opts.buffer)
+ return fn && fn(res);
+
+ var buf = '';
+
+ res.on('data', function (chunk) {
+ buf += chunk;
+ });
+
+ res.on('end', function () {
+ fn && fn(res, opts.parse ? opts.parse(buf) : buf);
+ });
+ });
+
+ req.on('error', function (err) { });
+
+ if (undefined !== opts.data)
+ req.write(opts.data);
+
+ req.end();
+
+ return req;
+};
+
+/**
+ * Terminates the client and associated connections.
+ *
+ * @api public
+ */
+
+HTTPClient.prototype.end = function () {
+ this.agent.sockets.forEach(function (socket) {
+ socket.end();
+ });
+};
+
+/**
+ * Issue a GET request
+ *
+ * @api public
+ */
+
+HTTPClient.prototype.get = function (path, opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ opts = opts || {};
+ opts.method = 'GET';
+
+ // override the parser for transport requests
+ if (/\/(xhr-polling|htmlfile|jsonp-polling)\//.test(path)) {
+ // parser that might be necessary for transport-specific framing
+ var transportParse = opts.parse;
+ opts.parse = function (data) {
+ if (data === '') return data;
+
+ data = transportParse ? transportParse(data) : data;
+ return parser.decodePayload(data);
+ };
+ } else {
+ opts.parse = undefined;
+ }
+
+ return this.request(path, opts, fn);
+};
+
+/**
+ * Issue a POST request
+ *
+ * @api private
+ */
+
+HTTPClient.prototype.post = function (path, data, opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ opts = opts || {};
+ opts.method = 'POST';
+ opts.data = data;
+
+ return this.request(path, opts, fn);
+};
+
+/**
+ * Performs a handshake (GET) request
+ *
+ * @api private
+ */
+
+HTTPClient.prototype.handshake = function (opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ return this.get('/socket.io/{protocol}', opts, function (res, data) {
+ fn && fn.apply(null, data.split(':'));
+ });
+};
+
+/**
+ * Generates a new client for the given port.
+ *
+ * @api private
+ */
+
+client = function (port) {
+ return new HTTPClient(port);
+};
+
+/**
+ * Create a socket.io server.
+ */
+
+create = function (cl) {
+ console.log('');
+ var manager = io.listen(cl.port);
+ manager.set('client store expiration', 0);
+ return manager;
+};
+
+/**
+ * WebSocket socket.io client.
+ *
+ * @api private
+ */
+
+function WSClient (port, sid) {
+ this.sid = sid;
+ this.port = port;
+
+ WebSocket.call(
+ this
+ , 'ws://localhost:' + port + '/socket.io/'
+ + io.protocol + '/websocket/' + sid
+ );
+};
+
+/**
+ * Inherits from WebSocket.
+ */
+
+WSClient.prototype.__proto__ = WebSocket.prototype;
+
+/**
+ * Overrides message event emission.
+ *
+ * @api private
+ */
+
+WSClient.prototype.emit = function (name) {
+ var args = arguments;
+
+ if (name == 'message' || name == 'data') {
+ args[1] = parser.decodePacket(args[1].toString());
+ }
+
+ return WebSocket.prototype.emit.apply(this, arguments);
+};
+
+/**
+ * Writes a packet
+ */
+
+WSClient.prototype.packet = function (pack) {
+ this.write(parser.encodePacket(pack));
+ return this;
+};
+
+/**
+ * Creates a websocket client.
+ *
+ * @api public
+ */
+
+websocket = function (cl, sid) {
+ return new WSClient(cl.port, sid);
+};
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAMUSOvlaeyQHMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTAxMTE2MDkzMjQ5WhcNMTMxMTE1MDkzMjQ5WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEVwfPQQp4X
+wtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+1FAE0c5o
+exPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404WthquTqg
+S7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy25IyBK3QJ
+c+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWAQsqW+COL
+0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABo1AwTjAdBgNVHQ4EFgQUDnV4d6mD
+tOnluLoCjkUHTX/n4agwHwYDVR0jBBgwFoAUDnV4d6mDtOnluLoCjkUHTX/n4agw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAFwV4MQfTo+qMv9JMiyno
+IEiqfOz4RgtmBqRnXUffcjS2dhc7/z+FPZnM79Kej8eLHoVfxCyWRHFlzm93vEdv
+wxOCrD13EDOi08OOZfxWyIlCa6Bg8cMAKqQzd2OvQOWqlRWBTThBJIhWflU33izX
+Qn5GdmYqhfpc+9ZHHGhvXNydtRQkdxVK2dZNzLBvBlLlRmtoClU7xm3A+/5dddeP
+AQHEPtyFlUw49VYtZ3ru6KqPms7MKvcRhYLsy9rwSfuuniMlx4d0bDR7TOkw0QQS
+A0N8MGQRQpzl4mw4jLzyM5d5QtuGBh2P6hPGa0YQxtI3RPT/p6ENzzBiAKXiSfzo
+xw==
+-----END CERTIFICATE-----
--- /dev/null
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEV
+wfPQQp4XwtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+
+1FAE0c5oexPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404
+WthquTqgS7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy2
+5IyBK3QJc+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWA
+QsqW+COL0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABAoIBAGe4+9VqZfJN+dsq
+8Osyuz01uQ8OmC0sAWTIqUlQgENIyf9rCJsUBlYmwR5BT6Z69XP6QhHdpSK+TiAR
+XUz0EqG9HYzcxHIBaACP7j6iRoQ8R4kbbiWKo0z3WqQGIOqFjvD/mKEuQdE5mEYw
+eOUCG6BnX1WY2Yr8WKd2AA/tp0/Y4d8z04u9eodMpSTbHTzYMJb5SbBN1vo6FY7q
+8zSuO0BMzXlAxUsCwHsk1GQHFr8Oh3zIR7bQGtMBouI+6Lhh7sjFYsfxJboqMTBV
+IKaA216M6ggHG7MU1/jeKcMGDmEfqQLQoyWp29rMK6TklUgipME2L3UD7vTyAVzz
+xbVOpZkCgYEA8CXW4sZBBrSSrLR5SB+Ubu9qNTggLowOsC/kVKB2WJ4+xooc5HQo
+mFhq1v/WxPQoWIxdYsfg2odlL+JclK5Qcy6vXmRSdAQ5lK9gBDKxZSYc3NwAw2HA
+zyHCTK+I0n8PBYQ+yGcrxu0WqTGnlLW+Otk4CejO34WlgHwbH9bbY5UCgYEA3ZvT
+C4+OoMHXlmICSt29zUrYiL33IWsR3/MaONxTEDuvgkOSXXQOl/8Ebd6Nu+3WbsSN
+bjiPC/JyL1YCVmijdvFpl4gjtgvfJifs4G+QHvO6YfsYoVANk4u6g6rUuBIOwNK4
+RwYxwDc0oysp+g7tPxoSgDHReEVKJNzGBe9NGGsCgYEA4O4QP4gCEA3B9BF2J5+s
+n9uPVxmiyvZUK6Iv8zP4pThTBBMIzNIf09G9AHPQ7djikU2nioY8jXKTzC3xGTHM
+GJZ5m6fLsu7iH+nDvSreDSeNkTBfZqGAvoGYQ8uGE+L+ZuRfCcXYsxIOT5s6o4c3
+Dle2rVFpsuKzCY00urW796ECgYBn3go75+xEwrYGQSer6WR1nTgCV29GVYXKPooy
+zmmMOT1Yw80NSkEw0pFD4cTyqVYREsTrPU0mn1sPfrOXxnGfZSVFpcR/Je9QVfQ7
+eW7GYxwfom335aqHVj10SxRqteP+UoWWnHujCPz94VRKZMakBddYCIGSan+G6YdS
+7sdmwwKBgBc2qj0wvGXDF2kCLwSGfWoMf8CS1+5fIiUIdT1e/+7MfDdbmLMIFVjF
+QKS3zVViXCbrG5SY6wS9hxoc57f6E2A8vcaX6zy2xkZlGHQCpWRtEM5R01OWJQaH
+HsHMmQZGUQVoDm1oRkDhrTFK4K3ukc3rAxzeTZ96utOQN8/KJsTv
+-----END RSA PRIVATE KEY-----
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , fs = require('fs')
+ , http = require('http')
+ , https = require('https')
+ , should = require('./common')
+ , ports = 15000;
+
+/**
+ * Test.
+ */
+
+module.exports = {
+
+ 'test that protocol version is present': function (done) {
+ sio.protocol.should.be.a('number');
+ done();
+ },
+
+ 'test that default transports are present': function (done) {
+ sio.Manager.defaultTransports.should.be.an.instanceof(Array);
+ done();
+ },
+
+ 'test that version is present': function (done) {
+ sio.version.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
+ done();
+ },
+
+ 'test listening with a port': function (done) {
+ var cl = client(++ports)
+ , io = create(cl);
+
+ io.server.should.be.an.instanceof(http.Server);
+
+ cl.get('/', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('Welcome to socket.io.');
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test listening with a server': function (done) {
+ var server = http.createServer()
+ , io = sio.listen(server)
+ , port = ++ports
+ , cl = client(port);
+
+ server.listen(port);
+
+ cl.get('/socket.io', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('Welcome to socket.io.');
+
+ cl.end();
+ server.close();
+ done();
+ });
+ },
+
+ 'test listening with a https server': function (done) {
+ var server = https.createServer({
+ key: fs.readFileSync(__dirname + '/fixtures/key.key')
+ , cert: fs.readFileSync(__dirname + '/fixtures/cert.crt')
+ }, function () { })
+ , io = sio.listen(server)
+ , port = ++ports;
+
+ server.listen(port);
+
+ var req = require('https').get({
+ host: 'localhost'
+ , port: port
+ , path: '/socket.io'
+ }, function (res) {
+ res.statusCode.should.eql(200);
+
+ var buf = '';
+
+ res.on('data', function (data) {
+ buf += data;
+ });
+
+ res.on('end', function () {
+ buf.should.eql('Welcome to socket.io.');
+
+ res.socket.end();
+ server.close();
+ done();
+ });
+ });
+ },
+
+ 'test listening with no arguments listens on 80': function (done) {
+ try {
+ var io = sio.listen()
+ , cl = client(80);
+
+ cl.get('/socket.io', function (res) {
+ res.statusCode.should.eql(200);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ } catch (e) {
+ e.should.match(/EACCES/);
+ done();
+ }
+ }
+
+};
--- /dev/null
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+require.paths.unshift(__dirname + '/../../lib');
+
+var assertvanish = require('assertvanish')
+ , common = require('../common')
+ , ports = 15800;
+
+function resultCallback (leaks, leakedSocket) {
+ if (leaks) {
+ console.error('Leak detected');
+ process.exit(1);
+ } else {
+ console.error('No leaks');
+ process.exit(0);
+ }
+};
+
+/**
+ * Test.
+ */
+
+var cl = client(++ports);
+var io = create(cl);
+
+io.sockets.on('connection', function (socket) {
+ console.log('connected');
+
+ socket.on('disconnect', function() {
+ console.log("client gone");
+ setTimeout(gc, 1000);
+ assertvanish(socket, 2000, {silent: true, callback: resultCallback});
+ });
+});
+
+setTimeout(function() {
+ cl.handshake(function (sid) {
+ var ws = websocket(cl, sid);
+ ws.on('open', function () {
+ console.log('open!');
+ setTimeout(function() {
+ ws.close();
+ }, 500);
+ });
+ });
+}, 100);
--- /dev/null
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , http = require('http')
+ , should = require('./common')
+ , ports = 15100;
+
+/**
+ * Test.
+ */
+
+module.exports = {
+
+ 'test setting and getting a configuration flag': function (done) {
+ var port = ++ports
+ , io = sio.listen(http.createServer());
+
+ io.set('a', 'b');
+ io.get('a').should.eql('b');
+
+ var port = ++ports
+ , io = sio.listen(http.createServer());
+
+ io.configure(function () {
+ io.set('a', 'b');
+ io.enable('tobi');
+ });
+
+ io.get('a').should.eql('b');
+
+ done();
+ },
+
+ 'test enabling and disabling a configuration flag': function (done) {
+ var port = ++ports
+ , io = sio.listen(http.createServer());
+
+ io.enable('flag');
+ io.enabled('flag').should.be.true;
+ io.disabled('flag').should.be.false;
+
+ io.disable('flag');
+ var port = ++ports
+ , io = sio.listen(http.createServer());
+
+ io.configure(function () {
+ io.enable('tobi');
+ });
+
+ io.enabled('tobi').should.be.true;
+
+ done();
+ },
+
+ 'test configuration callbacks with envs': function (done) {
+ var port = ++ports
+ , io = sio.listen(http.createServer());
+
+ process.env.NODE_ENV = 'development';
+
+ io.configure('production', function () {
+ io.set('ferret', 'tobi');
+ });
+
+ io.configure('development', function () {
+ io.set('ferret', 'jane');
+ });
+
+ io.get('ferret').should.eql('jane');
+ done();
+ },
+
+ 'test configuration callbacks conserve scope': function (done) {
+ var port = ++ports
+ , io = sio.listen(http.createServer())
+ , calls = 0;
+
+ process.env.NODE_ENV = 'development';
+
+ io.configure(function () {
+ this.should.eql(io);
+ calls++;
+ });
+
+ io.configure('development', function () {
+ this.should.eql(io);
+ calls++;
+ });
+
+ calls.should.eql(2);
+ done();
+ },
+
+ 'test configuration update notifications': function (done) {
+ var port = ++ports
+ , io = sio.listen(http.createServer())
+ , calls = 0;
+
+ io.on('set:foo', function () {
+ calls++;
+ });
+
+ io.set('foo', 'bar');
+ io.set('baz', 'bar');
+
+ calls.should.eql(1);
+
+ io.enable('foo');
+ io.disable('foo');
+
+ calls.should.eql(3);
+ done();
+ },
+
+ 'test that normal requests are still served': function (done) {
+ var server = http.createServer(function (req, res) {
+ res.writeHead(200);
+ res.end('woot');
+ });
+
+ var io = sio.listen(server)
+ , port = ++ports
+ , cl = client(port);
+
+ server.listen(ports);
+
+ cl.get('/socket.io', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('Welcome to socket.io.');
+
+ cl.get('/woot', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('woot');
+
+ cl.end();
+ server.close();
+ done();
+ });
+ });
+ },
+
+ 'test that the client is served': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ res.headers['content-type'].should.eql('application/javascript');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ should.strictEqual(res.headers.etag, undefined);
+
+ data.should.match(/XMLHttpRequest/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that the client etag is served': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.enable('browser client etag');
+ });
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ res.headers['content-type'].should.eql('application/javascript');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
+
+ data.should.match(/XMLHttpRequest/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that the cached client is served': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ res.headers['content-type'].should.eql('application/javascript');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ should.strictEqual(res.headers.etag, undefined);
+
+ data.should.match(/XMLHttpRequest/);
+ var static = sio.Manager.static;
+ static.cache['/socket.io.js'].content.should.match(/XMLHttpRequest/);
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ res.headers['content-type'].should.eql('application/javascript');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ should.strictEqual(res.headers.etag, undefined);
+
+ data.should.match(/XMLHttpRequest/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+ },
+
+ 'test that the cached client etag is served': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.enable('browser client etag');
+ });
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ res.headers['content-type'].should.eql('application/javascript');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
+
+ data.should.match(/XMLHttpRequest/);
+ var static = sio.Manager.static
+ , cache = static.cache['/socket.io.js'];
+
+ cache.content.toString().should.match(/XMLHttpRequest/);
+ Buffer.isBuffer(cache.content).should.be.true;
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ res.headers['content-type'].should.eql('application/javascript');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
+
+ data.should.match(/XMLHttpRequest/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+ },
+
+ 'test that the cached client sends a 304 header': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.enable('browser client etag');
+ });
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ cl.get('/socket.io/socket.io.js', {headers:{'if-none-match':res.headers.etag}}, function (res, data) {
+ res.statusCode.should.eql(304);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+ },
+
+ 'test that client minification works': function (done) {
+ // server 1
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ // server 2
+ var port = ++ports
+ , io2 = sio.listen(port)
+ , cl2 = client(port);
+
+ io.configure(function () {
+ io.enable('browser client minification');
+ });
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ var length = data.length;
+
+ cl.end();
+ io.server.close();
+
+ cl2.get('/socket.io/socket.io.js', function (res, data) {
+ res.headers['content-type'].should.eql('application/javascript');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ should.strictEqual(res.headers.etag, undefined);
+
+ data.should.match(/XMLHttpRequest/);
+ data.length.should.be.greaterThan(length);
+
+ cl2.end();
+ io2.server.close();
+ done();
+ });
+ });
+ },
+
+ 'test that the WebSocketMain.swf is served': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', function (res, data) {
+ res.headers['content-type'].should.eql('application/x-shockwave-flash');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ should.strictEqual(res.headers.etag, undefined);
+
+ var static = sio.Manager.static
+ , cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
+
+ Buffer.isBuffer(cache.content).should.be.true;
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that the WebSocketMainInsecure.swf is served': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ cl.get('/socket.io/static/flashsocket/WebSocketMainInsecure.swf', function (res, data) {
+ res.headers['content-type'].should.eql('application/x-shockwave-flash');
+ res.headers['content-length'].should.match(/([0-9]+)/);
+ should.strictEqual(res.headers.etag, undefined);
+
+ var static = sio.Manager.static
+ , cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
+
+ Buffer.isBuffer(cache.content).should.be.true;
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that you can serve custom clients': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('browser client handler', function (req, res) {
+ res.writeHead(200, {
+ 'Content-Type': 'application/javascript'
+ , 'Content-Length': 13
+ , 'ETag': '1.0'
+ });
+ res.end('custom_client');
+ });
+ });
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ res.headers['content-type'].should.eql('application/javascript');
+ res.headers['content-length'].should.eql(13);
+ res.headers.etag.should.eql('1.0');
+
+ data.should.eql('custom_client');
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that you can disable clients': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.disable('browser client');
+ });
+
+ cl.get('/socket.io/socket.io.js', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('Welcome to socket.io.');
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test handshake': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ cl.get('/socket.io/{protocol}/', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.match(/([^:]+):([0-9]+)?:([0-9]+)?:(.+)/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test handshake with unsupported protocol version': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ cl.get('/socket.io/-1/', function (res, data) {
+ res.statusCode.should.eql(500);
+ data.should.match(/Protocol version not supported/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test authorization failure in handshake': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ function auth (data, fn) {
+ fn(null, false);
+ };
+
+ io.set('authorization', auth);
+ });
+
+ cl.get('/socket.io/{protocol}/', function (res, data) {
+ res.statusCode.should.eql(403);
+ data.should.match(/handshake unauthorized/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test a handshake error': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ function auth (data, fn) {
+ fn(new Error);
+ };
+
+ io.set('authorization', auth);
+ });
+
+ cl.get('/socket.io/{protocol}/', function (res, data) {
+ res.statusCode.should.eql(500);
+ data.should.match(/handshake error/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that a referer is accepted for *:* origin': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('origins', '*:*');
+ });
+
+ cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:82/something' } }, function (res, data) {
+ res.statusCode.should.eql(200);
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that valid referer is accepted for addr:* origin': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('origins', 'foo.bar.com:*');
+ });
+
+ cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
+ res.statusCode.should.eql(200);
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that erroneous referer is denied for addr:* origin': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('origins', 'foo.bar.com:*');
+ });
+
+ cl.get('/socket.io/{protocol}', { headers: { referer: 'http://baz.bar.com/something' } }, function (res, data) {
+ res.statusCode.should.eql(403);
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that valid referer port is accepted for addr:port origin': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('origins', 'foo.bar.com:81');
+ });
+
+ cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com:81/something' } }, function (res, data) {
+ res.statusCode.should.eql(200);
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test that erroneous referer port is denied for addr:port origin': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('origins', 'foo.bar.com:81');
+ });
+
+ cl.get('/socket.io/{protocol}', { headers: { referer: 'http://foo.bar.com/something' } }, function (res, data) {
+ res.statusCode.should.eql(403);
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test limiting the supported transports for a manager': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('transports', ['tobi', 'jane']);
+ });
+
+ cl.get('/socket.io/{protocol}/', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.match(/([^:]+):([0-9]+)?:([0-9]+)?:tobi,jane/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test setting a custom close timeout': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('close timeout', 66);
+ });
+
+ cl.get('/socket.io/{protocol}/', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.match(/([^:]+):([0-9]+)?:66?:(.*)/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test setting a custom heartbeat timeout': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('heartbeat timeout', 33);
+ });
+
+ cl.get('/socket.io/{protocol}/', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.match(/([^:]+):33:([0-9]+)?:(.*)/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test disabling timeouts': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port);
+
+ io.configure(function () {
+ io.set('heartbeat timeout', null);
+ io.set('close timeout', '');
+ });
+
+ cl.get('/socket.io/{protocol}/', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.match(/([^:]+)::?:(.*)/);
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ },
+
+ 'test disabling heartbeats': function (done) {
+ var port = ++ports
+ , io = sio.listen(port)
+ , cl = client(port)
+ , messages = 0
+ , beat = false
+ , ws;
+
+ io.configure(function () {
+ io.disable('heartbeats');
+ io.set('heartbeat interval', .05);
+ io.set('heartbeat timeout', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ setTimeout(function () {
+ socket.disconnect();
+ }, io.get('heartbeat timeout') * 1000 + 100);
+
+ socket.on('disconnect', function (reason) {
+ beat.should.be.false;
+
+ cl.end();
+ ws.finishClose();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.get('/socket.io/{protocol}/', function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.match(/([^:]+)::[\.0-9]+:(.*)/);
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('message', function (packet) {
+ if (++messages == 1) {
+ packet.type.should.eql('connect');
+ } else if (packet.type == 'heartbeat'){
+ beat = true;
+ }
+ });
+ });
+ });
+ },
+
+ 'no duplicate room members': function (done) {
+ var port = ++ports
+ , io = sio.listen(port);
+
+ Object.keys(io.rooms).length.should.equal(0);
+
+ io.onJoin(123, 'foo');
+ io.rooms.foo.length.should.equal(1);
+
+ io.onJoin(123, 'foo');
+ io.rooms.foo.length.should.equal(1);
+
+ io.onJoin(124, 'foo');
+ io.rooms.foo.length.should.equal(2);
+
+ io.onJoin(124, 'foo');
+ io.rooms.foo.length.should.equal(2);
+
+ io.onJoin(123, 'bar');
+ io.rooms.foo.length.should.equal(2);
+ io.rooms.bar.length.should.equal(1);
+
+ io.onJoin(123, 'bar');
+ io.rooms.foo.length.should.equal(2);
+ io.rooms.bar.length.should.equal(1);
+
+ io.onJoin(124, 'bar');
+ io.rooms.foo.length.should.equal(2);
+ io.rooms.bar.length.should.equal(2);
+
+ io.onJoin(124, 'bar');
+ io.rooms.foo.length.should.equal(2);
+ io.rooms.bar.length.should.equal(2);
+
+ io.server.close();
+ done();
+ },
+
+ 'test passing options directly to the Manager through listen': function (done) {
+ var port = ++ports
+ , io = sio.listen(port, { resource: '/my resource', custom: 'opt' });
+
+ io.get('resource').should.equal('/my resource');
+ io.get('custom').should.equal('opt');
+ io.server.close();
+ done();
+ }
+};
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , should = require('./common')
+ , ports = 15700;
+
+/**
+ * Test.
+ */
+
+module.exports = {
+ 'namespace pass no authentication': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , ws;
+
+ io.of('/a')
+ .on('connection', function (socket) {
+ cl.end();
+ ws.finishClose();
+ io.server.close()
+ done();
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('open', function () {
+ ws.packet({
+ type: 'connect'
+ , endpoint: '/a'
+ });
+ })
+ });
+ },
+
+ 'namespace pass authentication': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , ws;
+
+ io.of('/a')
+ .authorization(function (data, fn) {
+ fn(null, true);
+ })
+ .on('connection', function (socket) {
+ cl.end();
+ ws.finishClose();
+ io.server.close()
+ done();
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('open', function () {
+ ws.packet({
+ type: 'connect'
+ , endpoint: '/a'
+ });
+ })
+ });
+ },
+
+ 'namespace authentication handshake data': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , ws;
+
+ io.of('/a')
+ .authorization(function (data, fn) {
+ data.foo = 'bar';
+ fn(null, true);
+ })
+ .on('connection', function (socket) {
+ (!!socket.handshake.address.address).should.be.true;
+ (!!socket.handshake.address.port).should.be.true;
+ socket.handshake.headers.host.should.equal('localhost');
+ socket.handshake.headers.connection.should.equal('keep-alive');
+ socket.handshake.time.should.match(/GMT/);
+ socket.handshake.foo.should.equal('bar');
+
+ cl.end();
+ ws.finishClose();
+ io.server.close()
+ done();
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('open', function () {
+ ws.packet({
+ type: 'connect'
+ , endpoint: '/a'
+ });
+ })
+ });
+ },
+
+ 'namespace fail authentication': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , calls = 0
+ , ws;
+
+ io.of('/a')
+ .authorization(function (data, fn) {
+ fn(null, false);
+ })
+ .on('connection', function (socket) {
+ throw new Error('Should not be called');
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('open', function () {
+ ws.packet({
+ type: 'connect'
+ , endpoint: '/a'
+ });
+ });
+
+ ws.on('message', function (data) {
+ if (data.endpoint == '/a') {
+ data.type.should.eql('error');
+ data.reason.should.eql('unauthorized')
+
+ cl.end();
+ ws.finishClose();
+ io.server.close()
+ done();
+ }
+ })
+ });
+ },
+
+ 'broadcasting sends and emits on a namespace': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , calls = 0
+ , connect = 0
+ , message = 0
+ , events = 0
+ , expected = 5
+ , ws1
+ , ws2;
+
+ io.of('a')
+ .on('connection', function (socket){
+ socket.broadcast.emit('b', 'test');
+ socket.broadcast.json.emit('json', {foo:'bar'});
+ socket.broadcast.send('foo');
+ });
+
+ function finish () {
+ connect.should.equal(2);
+ message.should.equal(1);
+ events.should.equal(2);
+
+ cl.end();
+ ws1.finishClose();
+ ws2.finishClose();
+ io.server.close();
+ done();
+ }
+
+ cl.handshake(function (sid) {
+ ws1 = websocket(cl, sid);
+
+ ws1.on('open', function() {
+ ws1.packet({
+ type: 'connect'
+ , endpoint: 'a'
+ });
+ });
+
+ ws1.on('message', function (data) {
+ if (data.type === 'connect') {
+ ++connect;
+ if (++calls === expected) finish();
+ }
+
+ if (data.type === 'message') {
+ ++message;
+ if (++calls === expected) finish();
+ }
+
+ if (data.type === 'event') {
+ if (data.name === 'b' || data.name === 'json') ++events;
+ if (++calls === expected) finish();
+ }
+ });
+
+ cl.handshake(function (sid) {
+ ws2 = websocket(cl, sid);
+
+ ws2.on('open', function () {
+ ws2.packet({
+ type: 'connect'
+ , endpoint: 'a'
+ });
+ });
+ })
+ })
+ },
+
+ 'joining rooms inside a namespace': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , calls = 0
+ , ws;
+
+ io.of('/foo').on('connection', function (socket) {
+ socket.join('foo.bar');
+ this.in('foo.bar').emit('baz', 'pewpew');
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+
+ ws.on('open', function (){
+ ws.packet({
+ type: 'connect'
+ , endpoint: '/foo'
+ });
+ });
+
+ ws.on('message', function (data) {
+ if (data.type === 'event') {
+ data.name.should.equal('baz');
+
+ cl.end();
+ ws.finishClose();
+ io.server.close();
+ done();
+ }
+ });
+ })
+ }
+};
--- /dev/null
+
+/**
+ * Test dependencies.
+ */
+
+var parser = require('socket.io').parser
+ , decode = parser.decode
+ , should = require('./common');
+
+/**
+ * Test.
+ */
+
+module.exports = {
+
+ 'decoding error packet': function () {
+ parser.decodePacket('7:::').should.eql({
+ type: 'error'
+ , reason: ''
+ , advice: ''
+ , endpoint: ''
+ });
+ },
+
+ 'decoding error packet with reason': function () {
+ parser.decodePacket('7:::0').should.eql({
+ type: 'error'
+ , reason: 'transport not supported'
+ , advice: ''
+ , endpoint: ''
+ });
+ },
+
+ 'decoding error packet with reason and advice': function () {
+ parser.decodePacket('7:::2+0').should.eql({
+ type: 'error'
+ , reason: 'unauthorized'
+ , advice: 'reconnect'
+ , endpoint: ''
+ });
+ },
+
+ 'decoding error packet with endpoint': function () {
+ parser.decodePacket('7::/woot').should.eql({
+ type: 'error'
+ , reason: ''
+ , advice: ''
+ , endpoint: '/woot'
+ });
+ },
+
+ 'decoding ack packet': function () {
+ parser.decodePacket('6:::140').should.eql({
+ type: 'ack'
+ , ackId: '140'
+ , endpoint: ''
+ , args: []
+ });
+ },
+
+ 'decoding ack packet with args': function () {
+ parser.decodePacket('6:::12+["woot","wa"]').should.eql({
+ type: 'ack'
+ , ackId: '12'
+ , endpoint: ''
+ , args: ['woot', 'wa']
+ });
+ },
+
+ 'decoding ack packet with bad json': function () {
+ var thrown = false;
+
+ try {
+ parser.decodePacket('6:::1+{"++]').should.eql({
+ type: 'ack'
+ , ackId: '1'
+ , endpoint: ''
+ , args: []
+ });
+ } catch (e) {
+ thrown = true;
+ }
+
+ thrown.should.be.false;
+ },
+
+ 'decoding json packet': function () {
+ parser.decodePacket('4:::"2"').should.eql({
+ type: 'json'
+ , endpoint: ''
+ , data: '2'
+ });
+ },
+
+ 'decoding json packet with message id and ack data': function () {
+ parser.decodePacket('4:1+::{"a":"b"}').should.eql({
+ type: 'json'
+ , id: 1
+ , ack: 'data'
+ , endpoint: ''
+ , data: { a: 'b' }
+ });
+ },
+
+ 'decoding an event packet': function () {
+ parser.decodePacket('5:::{"name":"woot"}').should.eql({
+ type: 'event'
+ , name: 'woot'
+ , endpoint: ''
+ , args: []
+ });
+ },
+
+ 'decoding an event packet with message id and ack': function () {
+ parser.decodePacket('5:1+::{"name":"tobi"}').should.eql({
+ type: 'event'
+ , id: 1
+ , ack: 'data'
+ , endpoint: ''
+ , name: 'tobi'
+ , args: []
+ });
+ },
+
+ 'decoding an event packet with data': function () {
+ parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}')
+ .should.eql({
+ type: 'event'
+ , name: 'edwald'
+ , endpoint: ''
+ , args: [{a: 'b'}, 2, '3']
+ });
+ },
+
+ 'decoding a message packet': function () {
+ parser.decodePacket('3:::woot').should.eql({
+ type: 'message'
+ , endpoint: ''
+ , data: 'woot'
+ });
+ },
+
+ 'decoding a message packet with id and endpoint': function () {
+ parser.decodePacket('3:5:/tobi').should.eql({
+ type: 'message'
+ , id: 5
+ , ack: true
+ , endpoint: '/tobi'
+ , data: ''
+ });
+ },
+
+ 'decoding a heartbeat packet': function () {
+ parser.decodePacket('2:::').should.eql({
+ type: 'heartbeat'
+ , endpoint: ''
+ });
+ },
+
+ 'decoding a connection packet': function () {
+ parser.decodePacket('1::/tobi').should.eql({
+ type: 'connect'
+ , endpoint: '/tobi'
+ , qs: ''
+ });
+ },
+
+ 'decoding a connection packet with query string': function () {
+ parser.decodePacket('1::/test:?test=1').should.eql({
+ type: 'connect'
+ , endpoint: '/test'
+ , qs: '?test=1'
+ });
+ },
+
+ 'decoding a disconnection packet': function () {
+ parser.decodePacket('0::/woot').should.eql({
+ type: 'disconnect'
+ , endpoint: '/woot'
+ });
+ },
+
+ 'encoding error packet': function () {
+ parser.encodePacket({
+ type: 'error'
+ , reason: ''
+ , advice: ''
+ , endpoint: ''
+ }).should.eql('7::');
+ },
+
+ 'encoding error packet with reason': function () {
+ parser.encodePacket({
+ type: 'error'
+ , reason: 'transport not supported'
+ , advice: ''
+ , endpoint: ''
+ }).should.eql('7:::0');
+ },
+
+ 'encoding error packet with reason and advice': function () {
+ parser.encodePacket({
+ type: 'error'
+ , reason: 'unauthorized'
+ , advice: 'reconnect'
+ , endpoint: ''
+ }).should.eql('7:::2+0');
+ },
+
+ 'encoding error packet with endpoint': function () {
+ parser.encodePacket({
+ type: 'error'
+ , reason: ''
+ , advice: ''
+ , endpoint: '/woot'
+ }).should.eql('7::/woot');
+ },
+
+ 'encoding ack packet': function () {
+ parser.encodePacket({
+ type: 'ack'
+ , ackId: '140'
+ , endpoint: ''
+ , args: []
+ }).should.eql('6:::140');
+ },
+
+ 'encoding ack packet with args': function () {
+ parser.encodePacket({
+ type: 'ack'
+ , ackId: '12'
+ , endpoint: ''
+ , args: ['woot', 'wa']
+ }).should.eql('6:::12+["woot","wa"]');
+ },
+
+ 'encoding json packet': function () {
+ parser.encodePacket({
+ type: 'json'
+ , endpoint: ''
+ , data: '2'
+ }).should.eql('4:::"2"');
+ },
+
+ 'encoding json packet with message id and ack data': function () {
+ parser.encodePacket({
+ type: 'json'
+ , id: 1
+ , ack: 'data'
+ , endpoint: ''
+ , data: { a: 'b' }
+ }).should.eql('4:1+::{"a":"b"}');
+ },
+
+ 'encoding an event packet': function () {
+ parser.encodePacket({
+ type: 'event'
+ , name: 'woot'
+ , endpoint: ''
+ , args: []
+ }).should.eql('5:::{"name":"woot"}');
+ },
+
+ 'encoding an event packet with message id and ack': function () {
+ parser.encodePacket({
+ type: 'event'
+ , id: 1
+ , ack: 'data'
+ , endpoint: ''
+ , name: 'tobi'
+ , args: []
+ }).should.eql('5:1+::{"name":"tobi"}');
+ },
+
+ 'encoding an event packet with data': function () {
+ parser.encodePacket({
+ type: 'event'
+ , name: 'edwald'
+ , endpoint: ''
+ , args: [{a: 'b'}, 2, '3']
+ }).should.eql('5:::{"name":"edwald","args":[{"a":"b"},2,"3"]}');
+ },
+
+ 'encoding a message packet': function () {
+ parser.encodePacket({
+ type: 'message'
+ , endpoint: ''
+ , data: 'woot'
+ }).should.eql('3:::woot');
+ },
+
+ 'encoding a message packet with id and endpoint': function () {
+ parser.encodePacket({
+ type: 'message'
+ , id: 5
+ , ack: true
+ , endpoint: '/tobi'
+ , data: ''
+ }).should.eql('3:5:/tobi');
+ },
+
+ 'encoding a heartbeat packet': function () {
+ parser.encodePacket({
+ type: 'heartbeat'
+ , endpoint: ''
+ }).should.eql('2::');
+ },
+
+ 'encoding a connection packet': function () {
+ parser.encodePacket({
+ type: 'connect'
+ , endpoint: '/tobi'
+ , qs: ''
+ }).should.eql('1::/tobi');
+ },
+
+ 'encoding a connection packet with query string': function () {
+ parser.encodePacket({
+ type: 'connect'
+ , endpoint: '/test'
+ , qs: '?test=1'
+ }).should.eql('1::/test:?test=1');
+ },
+
+ 'encoding a disconnection packet': function () {
+ parser.encodePacket({
+ type: 'disconnect'
+ , endpoint: '/woot'
+ }).should.eql('0::/woot');
+ },
+
+ 'test decoding a payload': function () {
+ parser.decodePayload('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d'
+ + '\ufffd3\ufffd0::').should.eql([
+ { type: 'message', data: '5', endpoint: '' }
+ , { type: 'message', data: '53d', endpoint: '' }
+ , { type: 'disconnect', endpoint: '' }
+ ]);
+ },
+
+ 'test encoding a payload': function () {
+ parser.encodePayload([
+ parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
+ , parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
+ ]).should.eql('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d')
+ },
+
+ 'test decoding newline': function () {
+ parser.decodePacket('3:::\n').should.eql({
+ type: 'message'
+ , endpoint: ''
+ , data: '\n'
+ });
+ }
+
+};
--- /dev/null
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , should = require('./common');
+
+/**
+ * Test.
+ */
+
+module.exports = {
+
+};
--- /dev/null
+
+/**
+ * Test dependencies
+ *
+ * @api private
+ */
+
+var sio = require('socket.io')
+ , should = require('should')
+ , MemoryStore = sio.MemoryStore;
+
+/**
+ * Test.
+ */
+
+module.exports = {
+
+ 'test storing data for a client': function (done) {
+ var store = new MemoryStore
+ , client = store.client('test');
+
+ client.id.should.equal('test');
+
+ client.set('a', 'b', function (err) {
+ should.strictEqual(err, null);
+
+ client.get('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.eql('b');
+
+ client.has('a', function (err, has) {
+ should.strictEqual(err, null);
+ has.should.be.true;
+
+ client.has('b', function (err, has) {
+ should.strictEqual(err, null);
+ has.should.be.false;
+
+ client.del('a', function (err) {
+ should.strictEqual(err, null);
+
+ client.has('a', function (err, has) {
+ should.strictEqual(err, null);
+ has.should.be.false;
+
+ client.set('b', 'c', function (err) {
+ should.strictEqual(err, null);
+
+ client.set('c', 'd', function (err) {
+ should.strictEqual(err, null);
+
+ client.get('b', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal('c');
+
+ client.get('c', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal('d');
+
+ store.destroy();
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test cleaning up clients data': function (done) {
+ var rand1 = Math.abs(Math.random() * Date.now() | 0)
+ , rand2 = Math.abs(Math.random() * Date.now() | 0);
+
+ var store = new MemoryStore()
+ , client1 = store.client(rand1)
+ , client2 = store.client(rand2);
+
+ client1.set('a', 'b', function (err) {
+ should.strictEqual(err, null);
+
+ client2.set('c', 'd', function (err) {
+ should.strictEqual(err, null);
+
+ client1.has('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.true;
+
+ client2.has('c', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.true;
+
+ store.destroy();
+
+ var newstore = new MemoryStore()
+ , newclient1 = newstore.client(rand1)
+ , newclient2 = newstore.client(rand2);
+
+ newclient1.has('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.false;
+
+ newclient2.has('c', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.false;
+
+ newstore.destroy();
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test cleaning up a particular client': function (done) {
+ var rand1 = Math.abs(Math.random() * Date.now() | 0)
+ , rand2 = Math.abs(Math.random() * Date.now() | 0);
+
+ var store = new MemoryStore()
+ , client1 = store.client(rand1)
+ , client2 = store.client(rand2);
+
+ client1.set('a', 'b', function (err) {
+ should.strictEqual(err, null);
+
+ client2.set('c', 'd', function (err) {
+ should.strictEqual(err, null);
+
+ client1.has('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.true;
+
+ client2.has('c', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.true;
+
+ store.clients.should.have.property(rand1);
+ store.clients.should.have.property(rand2);
+ store.destroyClient(rand1);
+
+ store.clients.should.not.have.property(rand1);
+ store.clients.should.have.property(rand2);
+
+ client1.has('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal(false);
+
+ store.destroy();
+ done();
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test destroy expiration': function (done) {
+ var store = new MemoryStore()
+ , id = Math.abs(Math.random() * Date.now() | 0)
+ , client = store.client(id);
+
+ client.set('a', 'b', function (err) {
+ should.strictEqual(err, null);
+ store.destroyClient(id, 1);
+
+ setTimeout(function () {
+ client.get('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal('b');
+ });
+ }, 500);
+
+ setTimeout(function () {
+ client.get('a', function (err, val) {
+ should.strictEqual(err, null);
+ should.strictEqual(val, null);
+
+ store.destroy();
+ done();
+ });
+ }, 1900);
+ });
+ }
+
+};
--- /dev/null
+
+/**
+ * Test dependencies
+ *
+ * @api private
+ */
+
+var sio = require('socket.io')
+ , redis = require('redis')
+ , should = require('should')
+ , RedisStore = sio.RedisStore;
+
+/**
+ * Test.
+ */
+
+module.exports = {
+
+ 'test publishing doesnt get caught by the own store subscriber': function (done) {
+ var a = new RedisStore
+ , b = new RedisStore;
+
+ a.subscribe('woot', function (arg) {
+ arg.should.equal('bb');
+ a.destroy();
+ b.destroy();
+ done();
+ }, function () {
+ a.publish('woot', 'aa');
+ b.publish('woot', 'bb');
+ });
+ },
+
+ 'test publishing to multiple subscribers': function (done) {
+ var a = new RedisStore
+ , b = new RedisStore
+ , c = new RedisStore
+ , subscriptions = 3
+ , messages = 2;
+
+ a.subscribe('tobi', function () {
+ throw new Error('Shouldnt publish to itself');
+ }, publish);
+
+ function subscription (arg1, arg2, arg3) {
+ arg1.should.equal(1);
+ arg2.should.equal(2);
+ arg3.should.equal(3);
+ --messages || finish();
+ }
+
+ b.subscribe('tobi', subscription, publish);
+ c.subscribe('tobi', subscription, publish);
+
+ function publish () {
+ --subscriptions || a.publish('tobi', 1, 2, 3);
+ }
+
+ function finish () {
+ a.destroy();
+ b.destroy();
+ c.destroy();
+ done();
+ }
+ },
+
+ 'test storing data for a client': function (done) {
+ var store = new RedisStore
+ , rand = 'test-' + Date.now()
+ , client = store.client(rand);
+
+ client.id.should.equal(rand);
+
+ client.set('a', 'b', function (err) {
+ should.strictEqual(err, null);
+
+ client.get('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal('b');
+
+ client.has('a', function (err, has) {
+ should.strictEqual(err, null);
+ has.should.be.true;
+
+ client.has('b', function (err, has) {
+ should.strictEqual(err, null);
+ has.should.be.false;
+
+ client.del('a', function (err) {
+ should.strictEqual(err, null);
+
+ client.has('a', function (err, has) {
+ should.strictEqual(err, null);
+ has.should.be.false;
+
+ client.set('b', 'c', function (err) {
+ should.strictEqual(err, null);
+
+ client.set('c', 'd', function (err) {
+ should.strictEqual(err, null);
+
+ client.get('b', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal('c');
+
+ client.get('c', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal('d');
+
+ store.destroy();
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test cleaning up clients data': function (done) {
+ var rand1 = Math.abs(Math.random() * Date.now() | 0)
+ , rand2 = Math.abs(Math.random() * Date.now() | 0);
+
+ var store = new RedisStore()
+ , client1 = store.client(rand1)
+ , client2 = store.client(rand2);
+
+ client1.set('a', 'b', function (err) {
+ should.strictEqual(err, null);
+
+ client2.set('c', 'd', function (err) {
+ should.strictEqual(err, null);
+
+ client1.has('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.true;
+
+ client2.has('c', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.true;
+
+ store.destroy();
+
+ var newstore = new RedisStore()
+ , newclient1 = newstore.client(rand1)
+ , newclient2 = newstore.client(rand2);
+
+ newclient1.has('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.false;
+
+ newclient2.has('c', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.false;
+
+ newstore.destroy();
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test cleaning up a particular client': function (done) {
+ var rand1 = Math.abs(Math.random() * Date.now() | 0)
+ , rand2 = Math.abs(Math.random() * Date.now() | 0);
+
+ var store = new RedisStore()
+ , client1 = store.client(rand1)
+ , client2 = store.client(rand2);
+
+ client1.set('a', 'b', function (err) {
+ should.strictEqual(err, null);
+
+ client2.set('c', 'd', function (err) {
+ should.strictEqual(err, null);
+
+ client1.has('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.true;
+
+ client2.has('c', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.be.true;
+
+ store.clients.should.have.property(rand1);
+ store.clients.should.have.property(rand2);
+ store.destroyClient(rand1);
+
+ store.clients.should.not.have.property(rand1);
+ store.clients.should.have.property(rand2);
+
+ client1.has('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal(false);
+
+ store.destroy();
+ done();
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test destroy expiration': function (done) {
+ var store = new RedisStore()
+ , id = Math.abs(Math.random() * Date.now() | 0)
+ , client = store.client(id);
+
+ client.set('a', 'b', function (err) {
+ should.strictEqual(err, null);
+ store.destroyClient(id, 1);
+
+ setTimeout(function () {
+ client.get('a', function (err, val) {
+ should.strictEqual(err, null);
+ val.should.equal('b');
+ });
+ }, 500);
+
+ setTimeout(function () {
+ client.get('a', function (err, val) {
+ should.strictEqual(err, null);
+ should.strictEqual(val, null);
+
+ store.destroy();
+ done();
+ });
+ }, 2000);
+ });
+ }
+
+};
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , net = require('net')
+ , http = require('http')
+ , should = require('./common')
+ , WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket
+ , WSClient = require('./transports.websocket.test')
+ , parser = sio.parser
+ , ports = 15600;
+
+/**
+ * FlashSocket client constructor.
+ *
+ * @api private
+ */
+
+function FlashSocket (port, sid) {
+ this.sid = sid;
+ this.port = port;
+
+ WebSocket.call(
+ this
+ , 'ws://localhost:' + port + '/socket.io/'
+ + sio.protocol + '/flashsocket/' + sid
+ );
+};
+
+/**
+ * Inherits from WSClient.
+ */
+
+FlashSocket.prototype.__proto__ = WebSocket.prototype;
+
+/**
+ * Creates a TCP connection to a port.
+ *
+ * @api public
+ */
+
+function netConnection (port, callback){
+ var nclient = net.createConnection(port);
+
+ nclient.on('data', function (data) {
+ callback.call(nclient, null, data);
+ });
+
+ nclient.on('error', function (e){
+ callback.call(nclient, e);
+ });
+
+ nclient.write('<policy-file-request/>\0');
+}
+
+/**
+ * Tests.
+ */
+
+module.exports = {
+
+ 'flashsocket disabled by default': function (done) {
+ var io = sio.listen(http.createServer());
+ io.get('transports').should.not.contain('flashsocket');
+ done();
+ },
+
+ 'flash policy port': function (done) {
+ var io = sio.listen(http.createServer())
+ , port = ++ports;
+
+ io.get('flash policy port').should.eql(10843);
+ io.set('flash policy port', port);
+ io.get('flash policy port').should.eql(port);
+
+ should.strictEqual(io.flashPolicyServer, undefined);
+
+ netConnection(port, function (err, data){
+ err.should.be.an.instanceof(Error);
+ err.code.should.eql('ECONNREFUSED');
+
+ this.destroy();
+ done();
+ })
+ },
+
+ 'start flash policy': function (done) {
+ var io = sio.listen(http.createServer())
+ , port = ++ports;
+
+ io.set('flash policy port', port);
+ io.set('transports', ['flashsocket']);
+
+ io.flashPolicyServer.should.be.a('object');
+
+ netConnection(port, function (err, data){
+ should.strictEqual(err, null);
+
+ data.toString().should.include.string('<cross-domain-policy>');
+
+ this.destroy();
+ io.flashPolicyServer.close();
+ done();
+ })
+
+ },
+
+ 'change running flash server port': function (done) {
+ var io = sio.listen(http.createServer())
+ , port = ++ports
+ , next = ++ports;
+
+ io.set('flash policy port', port);
+ io.set('transports', ['flashsocket']);
+ io.set('flash policy port', next);
+ io.flashPolicyServer.port.should.eql(next);
+
+ netConnection(port, function (err, data){
+ err.should.be.an.instanceof(Error);
+ err.code.should.eql('ECONNREFUSED');
+
+ this.destroy();
+
+ // should work
+ netConnection(next, function (err, data){
+ should.strictEqual(err, null);
+
+ data.toString().should.include.string('<cross-domain-policy>');
+
+ this.destroy();
+ io.flashPolicyServer.close();
+ done();
+ });
+ });
+ },
+
+ 'different origins': function(done) {
+ var io = sio.listen(http.createServer())
+ , port = ++ports;
+
+ io.set('flash policy port', port);
+ io.set('transports', ['flashsocket']);
+ io.set('origins', 'google.com:80');
+
+ var server = io.flashPolicyServer;
+
+ server.origins.should.contain('google.com:80');
+ server.origins.should.not.contain('*.*');
+
+ io.set('origins', ['foo.bar:80', 'socket.io:1337']);
+ server.origins.should.not.contain('google.com:80');
+ server.origins.should.contain('foo.bar:80');
+ server.origins.should.contain('socket.io:1337');
+ server.buffer.toString('utf8').should.include.string('socket.io');
+
+ io.flashPolicyServer.close();
+ done();
+ }
+
+};
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , should = require('./common')
+ , HTTPClient = should.HTTPClient
+ , parser = sio.parser
+ , ports = 15300;
+
+/**
+ * HTTPClient for htmlfile transport.
+ */
+
+function HTMLFile (port) {
+ HTTPClient.call(this, port);
+};
+
+/**
+ * Inhertis from HTTPClient.
+ */
+
+HTMLFile.prototype.__proto__ = HTTPClient.prototype;
+
+/**
+ * Override GET request with streaming parser.
+ *
+ * @api public
+ */
+
+var head = '<script>_('
+ , foot = ');</script>'
+ , initial = '<html><body>'
+ + '<script>var _ = function (msg) { parent.s._(msg, document); };</script>'
+ + new Array(174).join(' ')
+
+HTMLFile.prototype.data = function (path, opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ opts.buffer = false;
+
+ return this.request(path, opts, function (res) {
+ var buf = ''
+ , messages = 0
+ , state = 0;
+
+ res.on('data', function (chunk) {
+ buf += chunk;
+
+ function parse () {
+ switch (state) {
+ case 0:
+ if (buf.indexOf(initial) === 0) {
+ buf = buf.substr(initial.length);
+ state = 1;
+ } else {
+ break;
+ }
+
+ case 1:
+ if (buf.indexOf(head) === 0) {
+ buf = buf.substr(head.length);
+ state = 2;
+ } else {
+ break;
+ }
+
+ case 2:
+ if (buf.indexOf(foot) != -1) {
+ var data = buf.slice(0, buf.indexOf(foot))
+ , obj = JSON.parse(data);
+
+ fn(obj === '' ? obj : parser.decodePayload(obj), ++messages);
+
+ buf = buf.substr(data.length + foot.length);
+ state = 1;
+
+ parse();
+ }
+ };
+ };
+
+ parse();
+ });
+ });
+};
+
+/**
+ * Create client for this transport.
+ *
+ * @api public
+ */
+
+function client (port) {
+ return new HTMLFile(port);
+};
+
+/**
+ * Tests.
+ */
+
+module.exports = {
+
+ 'test that not responding to a heartbeat drops client': function (done) {
+ var port = ++ports
+ , cl = client(port)
+ , io = create(cl)
+ , beat = false;
+
+ io.configure(function () {
+ io.set('heartbeat interval', .05);
+ io.set('heartbeat timeout', .05);
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function (reason) {
+ beat.should.be.true;
+ reason.should.eql('heartbeat timeout');
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
+ switch (i) {
+ case 1:
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ msgs[0].endpoint.should.eql('');
+ break;
+
+ case 2:
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('heartbeat');
+ beat = true;
+ };
+ });
+ });
+ },
+
+ 'test that responding to a heartbeat maintains session': function (done) {
+ var port = ++ports
+ , cl = client(port)
+ , io = create(cl)
+ , heartbeats = 0;
+
+ io.configure(function () {
+ io.set('heartbeat interval', .05);
+ io.set('heartbeat timeout', .05);
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function (reason) {
+ heartbeats.should.eql(2);
+ reason.should.eql('heartbeat timeout');
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
+ switch (i) {
+ case 1:
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ msgs[0].endpoint.should.eql('');
+ break;
+
+ default:
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('heartbeat');
+
+ heartbeats++;
+
+ if (heartbeats == 1) {
+ cl.post('/socket.io/{protocol}/htmlfile/' + sid, parser.encodePacket({
+ type: 'heartbeat'
+ }));
+ }
+ }
+ });
+ });
+ },
+
+ 'test sending undeliverable volatile messages': function (done) {
+ var port = ++ports
+ , cl = client(port)
+ , io = create(cl)
+ , messaged = false
+ , s;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
+
+ setTimeout(function () {
+ cl.end();
+
+ setTimeout(function () {
+ s.volatile.send('wooooot');
+ cl = client(port);
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
+ if (msgs && msgs.length)
+ messaged = true;
+ });
+
+ setTimeout(function () {
+ cl.end();
+ }, 20);
+ }, 20);
+ }, 20);
+ });
+ },
+
+ 'test sending undeliverable volatile json': function (done) {
+ var port = ++ports
+ , cl = client(port)
+ , io = create(cl)
+ , messaged = false
+ , s;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
+
+ setTimeout(function () {
+ cl.end();
+
+ setTimeout(function () {
+ s.volatile.json.send(123);
+
+ cl = client(port);
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
+ if (msgs && msgs.length)
+ messaged = true;
+ });
+
+ setTimeout(function () {
+ cl.end();
+ }, 20);
+ }, 20);
+ }, 20);
+ });
+ },
+
+ 'test sending undeliverable volatile events': function (done) {
+ var port = ++ports
+ , cl = client(port)
+ , io = create(cl)
+ , messaged = false
+ , s;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function () { });
+
+ setTimeout(function () {
+ cl.end();
+
+ setTimeout(function () {
+ s.volatile.emit('tobi');
+
+ cl = client(port);
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs) {
+ if (msgs && msgs.length)
+ messaged = true;
+ });
+
+ setTimeout(function () {
+ cl.end();
+ }, 20);
+ }, 20);
+ }, 20);
+ });
+ },
+
+ 'test sending deliverable volatile messages': function (done) {
+ var port = ++ports
+ , cl = client(port)
+ , io = create(cl)
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.volatile.send('woot');
+
+ socket.on('disconnect', function () {
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
+ switch (i) {
+ case 1:
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ msgs[0].endpoint.should.eql('');
+ break;
+
+ case 2:
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'message'
+ , data: 'woot'
+ , endpoint: ''
+ });
+ cl.end();
+ }
+ });
+ });
+ },
+
+ 'test sending deliverable volatile json': function (done) {
+ var port = ++ports
+ , cl = client(port)
+ , io = create(cl)
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.volatile.json.send(['woot']);
+
+ socket.on('disconnect', function () {
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
+ switch (i) {
+ case 1:
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ msgs[0].endpoint.should.eql('');
+ break;
+
+ case 2:
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'json'
+ , data: ['woot']
+ , endpoint: ''
+ });
+ cl.end();
+ }
+ });
+ });
+ },
+
+ 'test sending deliverable volatile events': function (done) {
+ var port = ++ports
+ , cl = client(port)
+ , io = create(cl)
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.volatile.emit('aaa');
+
+ socket.on('disconnect', function () {
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.data('/socket.io/{protocol}/htmlfile/' + sid, function (msgs, i) {
+ switch (i) {
+ case 1:
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ msgs[0].endpoint.should.eql('');
+ break;
+
+ case 2:
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'event'
+ , name: 'aaa'
+ , endpoint: ''
+ , args: []
+ });
+ cl.end();
+ }
+ });
+ });
+ }
+
+};
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , should = require('./common')
+ , qs = require('querystring')
+ , HTTPClient = should.HTTPClient
+ , parser = sio.parser
+ , ports = 15500;
+
+/**
+ * HTTPClient for jsonp-polling transport.
+ */
+
+function JSONPPolling (port) {
+ HTTPClient.call(this, port);
+};
+
+/**
+ * Inhertis from HTTPClient.
+ */
+
+JSONPPolling.prototype.__proto__ = HTTPClient.prototype;
+
+/**
+ * Performs a json-p (cross domain) handshake
+ *
+ * @api public
+ */
+
+JSONPPolling.prototype.handshake = function (opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ var self = this;
+
+ return this.get(
+ '/socket.io/{protocol}?jsonp=0'
+ , opts
+ , function (res, data) {
+ var head = 'io.j[0]('
+ , foot = ');';
+
+ data.substr(0, head.length).should.eql(head);
+ data.substr(-foot.length).should.eql(foot);
+ data = data.slice(head.length, data.length - foot.length);
+
+ var parts = JSON.parse(data).split(':');
+
+ if (opts.ignoreConnect) {
+ return fn && fn.apply(null, parts);
+ }
+
+ // expect connect packet right after handshake
+ self.get(
+ '/socket.io/{protocol}/jsonp-polling/' + parts[0]
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'connect', endpoint: '', qs: '' });
+
+ fn && fn.apply(null, parts);
+ }
+ );
+ }
+ );
+};
+
+/**
+ * Override GET requests.
+ *
+ * @api public
+ */
+
+JSONPPolling.prototype.get = function (path, opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ opts = opts || {};
+
+ opts.parse = function (data) {
+ var head = 'io.j[0]('
+ , foot = ');';
+
+ if (~path.indexOf('?i=1')) {
+ head = 'io.j[1](';
+ }
+
+ data.substr(0, head.length).should.eql(head);
+ data.substr(-foot.length).should.eql(foot);
+
+ data = data.substr(head.length, data.length - head.length - foot.length);
+
+ return JSON.parse(data);
+ };
+
+ return HTTPClient.prototype.get.call(this, path, opts, fn);
+};
+
+/**
+ * Issue an encoded POST request
+ *
+ * @api private
+ */
+
+JSONPPolling.prototype.post = function (path, data, opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ opts = opts || {};
+ opts.method = 'POST';
+ opts.data = qs.stringify({ d: data });
+
+ return this.request(path, opts, fn);
+};
+
+/**
+ * Create client for this transport.
+ *
+ * @api public
+ */
+
+function client (port) {
+ return new JSONPPolling(port);
+};
+
+/**
+ * Test.
+ */
+
+module.exports = {
+
+ 'test jsonp handshake': function (done) {
+ var cl = client(++ports)
+ , io = create(cl);
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ io.set('polling duration', 0);
+ });
+
+ function finish () {
+ cl.end();
+ io.server.close();
+ done();
+ };
+
+ cl.handshake(function (sid) {
+ var total = 2;
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/tobi', function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'error'
+ , reason: 'client not handshaken'
+ , endpoint: ''
+ , advice: 'reconnect'
+ });
+
+ --total || finish();
+ });
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+ --total || finish();
+ });
+ });
+ },
+
+ 'test the connection event': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , sid;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.id.should.eql(sid);
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ });
+ });
+ },
+
+ 'test the disconnection event after a close timeout': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , sid;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.id.should.eql(sid);
+
+ socket.on('disconnect', function () {
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+
+ setTimeout(function () {
+ cl.end();
+ }, 10);
+ });
+ });
+ },
+
+ 'test the disconnection event when the client sends ?disconnect req':
+ function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , disconnected = false
+ , sid;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ disconnected = true;
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
+ disconnected.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid + '/?disconnect');
+ });
+ });
+ },
+
+ 'test the disconnection event booting a client': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , forced = false;
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ io.server.close();
+ done();
+ });
+
+ cl.end();
+ socket.disconnect();
+ forced = true;
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
+
+ forced.should.be.true;
+ });
+ });
+ },
+
+ 'test the disconnection event with client disconnect packet': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , sid;
+
+ io.sockets.on('connection', function (client) {
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'disconnect' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+
+ client.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ });
+ });
+ },
+
+ 'test sending back data': function (done) {
+ var cl = client(++ports)
+ , io = create(cl);
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.send('woot');
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, packs) {
+ packs.should.have.length(1);
+ packs[0].type.should.eql('message');
+ packs[0].data.should.eql('woot');
+ });
+ });
+ },
+
+ 'test sending a batch of messages': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , sid;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ var messages = 0;
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePayload([
+ parser.encodePacket({ type: 'message', data: 'a' })
+ , parser.encodePacket({ type: 'message', data: 'b' })
+ , parser.encodePacket({ type: 'disconnect' })
+ ])
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+
+ socket.on('message', function (data) {
+ messages++;
+
+ if (messages == 1)
+ data.should.eql('a');
+
+ if (messages == 2)
+ data.should.eql('b');
+ });
+
+ socket.on('disconnect', function () {
+ messages.should.eql(2);
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ });
+ });
+ },
+
+ 'test message buffering between a response and a request': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messages = false
+ , tobi;
+
+ io.configure(function () {
+ io.set('polling duration', .1);
+ io.set('close timeout', .2);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ tobi = function () {
+ socket.send('a');
+ socket.send('b');
+ socket.send('c');
+ };
+
+ socket.on('disconnect', function () {
+ messages.should.be.true;
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ tobi();
+
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ msgs.should.have.length(3);
+ msgs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
+ msgs[1].should.eql({ type: 'message', endpoint: '', data: 'b' });
+ msgs[2].should.eql({ type: 'message', endpoint: '', data: 'c' });
+ messages = true;
+ });
+ })
+ });
+ },
+
+ 'test connecting to a specific endpoint': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , connectMessage = false
+ , sid;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/woot').on('connection', function (socket) {
+ connectMessage.should.be.true;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid);
+
+ connectMessage = true;
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/woot' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ });
+ });
+ },
+
+ 'test that connecting doesnt connect to defined endpoints': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , tobiConnected = false
+ , mainConnected = false
+ , sid;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ mainConnected = true;
+
+ socket.on('disconnect', function () {
+ tobiConnected.should.be.false;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/tobi').on('connection', function () {
+ tobiConnected = true;
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid);
+ });
+ },
+
+ 'test disconnecting a specific endpoint': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , wootDisconnected = false
+ , mainDisconnected = false
+ , checked = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (data) {
+ data.should.eql('ferret');
+ mainDisconnected.should.be.false;
+ wootDisconnected.should.be.true;
+ checked = true;
+ });
+
+ socket.on('disconnect', function () {
+ mainDisconnected = true;
+ checked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/woot').on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ wootDisconnected = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function () {
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/woot' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'disconnect', endpoint: '/woot' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'message', data: 'ferret' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test that disconnecting disconnects all endpoints': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , aDisconnected = false
+ , bDisconnected = false;
+
+ io.configure(function () {
+ io.set('polling duration', .2);
+ io.set('close timeout', .2);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ setTimeout(function () {
+ aDisconnected.should.be.true;
+ bDisconnected.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ }, 50);
+ });
+ });
+
+ io.of('/a').on('connection', function (socket) {
+ socket.on('disconnect', function (msg) {
+ aDisconnected = true;
+ });
+ });
+
+ io.of('/b').on('connection', function (socket) {
+ socket.on('disconnect', function (msg) {
+ bDisconnected = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/b' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ });
+ });
+ },
+
+ 'test messaging a specific endpoint': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = true
+ , aMessaged = false
+ , bMessaged = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql('');
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ messaged.should.be.true;
+ aMessaged.should.be.true;
+ bMessaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/a').on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql('a');
+ aMessaged = true;
+ });
+ });
+
+ io.of('/b').on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql('b');
+ bMessaged = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'message', data: '' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/b' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/jsonp-polling/' + sid
+ , parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ });
+ });
+ }
+
+};
--- /dev/null
+/**
+ * Test dependencies.
+ */
+
+var assert = require('assert');
+var Parser = require('../lib/transports/websocket/hybi-07-12.js').Parser;
+
+/**
+ * Returns a Buffer from a "ff 00 ff"-type hex string.
+ */
+
+function makeBufferFromHexString(byteStr) {
+ var bytes = byteStr.split(' ');
+ var buf = new Buffer(bytes.length);
+ for (var i = 0; i < bytes.length; ++i) {
+ buf[i] = parseInt(bytes[i], 16);
+ }
+ return buf;
+}
+
+/**
+ * Splits a buffer in two parts.
+ */
+
+function splitBuffer(buffer) {
+ var b1 = new Buffer(Math.ceil(buffer.length / 2));
+ buffer.copy(b1, 0, 0, b1.length);
+ var b2 = new Buffer(Math.floor(buffer.length / 2));
+ buffer.copy(b2, 0, b1.length, b1.length + b2.length);
+ return [b1, b2];
+}
+
+/**
+ * Performs hybi07+ type masking on a hex string.
+ */
+
+function mask(str, maskString) {
+ var buf = new Buffer(str);
+ var mask = makeBufferFromHexString(maskString || '34 83 a8 68');
+ for (var i = 0; i < buf.length; ++i) {
+ buf[i] ^= mask[i % 4];
+ }
+ return buf;
+}
+
+/**
+ * Unpacks a Buffer into a number.
+ */
+
+function unpack(buffer) {
+ var n = 0;
+ for (var i = 0; i < buffer.length; ++i) {
+ n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
+ }
+ return n;
+}
+
+/**
+ * Returns a hex string, representing a specific byte count 'length', from a number.
+ */
+
+function pack(length, number) {
+ return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
+}
+
+/**
+ * Left pads the string 's' to a total length of 'n' with char 'c'.
+ */
+
+function padl(s, n, c) {
+ return new Array(1 + n - s.length).join(c) + s;
+}
+
+/**
+ * Returns a hex string from a Buffer.
+ */
+
+function dump(data) {
+ var s = '';
+ for (var i = 0; i < data.length; ++i) {
+ s += padl(data[i].toString(16), 2, '0') + ' ';
+ }
+ return s.trim();
+}
+
+/**
+ * Tests.
+ */
+
+module.exports = {
+ 'can parse unmasked text message': function() {
+ var p = new Parser();
+ var packet = '81 05 48 65 6c 6c 6f';
+
+ var gotData = false;
+ p.on('data', function(data) {
+ gotData = true;
+ assert.equal('Hello', data);
+ });
+
+ p.add(makeBufferFromHexString(packet));
+ assert.ok(gotData);
+ },
+ 'can parse close message': function() {
+ var p = new Parser();
+ var packet = '88 00';
+
+ var gotClose = false;
+ p.on('close', function(data) {
+ gotClose = true;
+ });
+
+ p.add(makeBufferFromHexString(packet));
+ assert.ok(gotClose);
+ },
+ 'can parse masked text message': function() {
+ var p = new Parser();
+ var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5';
+
+ var gotData = false;
+ p.on('data', function(data) {
+ gotData = true;
+ assert.equal('5:::{"name":"echo"}', data);
+ });
+
+ p.add(makeBufferFromHexString(packet));
+ assert.ok(gotData);
+ },
+ 'can parse a masked text message longer than 125 bytes': function() {
+ var p = new Parser();
+ var message = 'A';
+ for (var i = 0; i < 300; ++i) message += (i % 5).toString();
+ var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
+
+ var gotData = false;
+ p.on('data', function(data) {
+ gotData = true;
+ assert.equal(message, data);
+ });
+
+ p.add(makeBufferFromHexString(packet));
+ assert.ok(gotData);
+ },
+ 'can parse a really long masked text message': function() {
+ var p = new Parser();
+ var message = 'A';
+ for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
+ var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
+
+ var gotData = false;
+ p.on('data', function(data) {
+ gotData = true;
+ assert.equal(message, data);
+ });
+
+ p.add(makeBufferFromHexString(packet));
+ assert.ok(gotData);
+ },
+ 'can parse a fragmented masked text message of 300 bytes': function() {
+ var p = new Parser();
+ var message = 'A';
+ for (var i = 0; i < 300; ++i) message += (i % 5).toString();
+ var msgpiece1 = message.substr(0, 150);
+ var msgpiece2 = message.substr(150);
+ var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
+ var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
+
+ var gotData = false;
+ p.on('data', function(data) {
+ gotData = true;
+ assert.equal(message, data);
+ });
+
+ p.add(makeBufferFromHexString(packet1));
+ p.add(makeBufferFromHexString(packet2));
+ assert.ok(gotData);
+ },
+ 'can parse a ping message': function() {
+ var p = new Parser();
+ var message = 'Hello';
+ var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
+
+ var gotPing = false;
+ p.on('ping', function(data) {
+ gotPing = true;
+ assert.equal(message, data);
+ });
+
+ p.add(makeBufferFromHexString(packet));
+ assert.ok(gotPing);
+ },
+ 'can parse a ping with no data': function() {
+ var p = new Parser();
+ var packet = '89 00';
+
+ var gotPing = false;
+ p.on('ping', function(data) {
+ gotPing = true;
+ });
+
+ p.add(makeBufferFromHexString(packet));
+ assert.ok(gotPing);
+ },
+ 'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
+ var p = new Parser();
+ var message = 'A';
+ for (var i = 0; i < 300; ++i) message += (i % 5).toString();
+
+ var msgpiece1 = message.substr(0, 150);
+ var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
+
+ var pingMessage = 'Hello';
+ var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
+
+ var msgpiece2 = message.substr(150);
+ var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
+
+ var gotData = false;
+ p.on('data', function(data) {
+ gotData = true;
+ assert.equal(message, data);
+ });
+ var gotPing = false;
+ p.on('ping', function(data) {
+ gotPing = true;
+ assert.equal(pingMessage, data);
+ });
+
+ p.add(makeBufferFromHexString(packet1));
+ p.add(makeBufferFromHexString(pingPacket));
+ p.add(makeBufferFromHexString(packet2));
+ assert.ok(gotData);
+ assert.ok(gotPing);
+ },
+ 'can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets': function() {
+ var p = new Parser();
+ var message = 'A';
+ for (var i = 0; i < 300; ++i) message += (i % 5).toString();
+
+ var msgpiece1 = message.substr(0, 150);
+ var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
+
+ var pingMessage = 'Hello';
+ var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
+
+ var msgpiece2 = message.substr(150);
+ var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
+
+ var gotData = false;
+ p.on('data', function(data) {
+ gotData = true;
+ assert.equal(message, data);
+ });
+ var gotPing = false;
+ p.on('ping', function(data) {
+ gotPing = true;
+ assert.equal(pingMessage, data);
+ });
+
+ var buffers = [];
+ buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet1)));
+ buffers = buffers.concat(splitBuffer(makeBufferFromHexString(pingPacket)));
+ buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet2)));
+ for (var i = 0; i < buffers.length; ++i) {
+ p.add(buffers[i]);
+ }
+ assert.ok(gotData);
+ assert.ok(gotPing);
+ },
+};
+
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , should = require('./common')
+ , parser = sio.parser
+ , ports = 15800;
+
+/**
+ * Tests.
+ */
+
+module.exports = {
+
+ 'test that not responding to a heartbeat drops client': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messages = 0
+ , ws;
+
+ io.configure(function () {
+ io.set('heartbeat interval', .05);
+ io.set('heartbeat timeout', .05);
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function (reason) {
+ beat.should.be.true;
+ reason.should.eql('heartbeat timeout');
+
+ cl.end();
+ ws.finishClose();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('message', function (packet) {
+ if (++messages == 1) {
+ packet.type.should.eql('connect');
+ } else {
+ packet.type.should.eql('heartbeat');
+ beat = true;
+ }
+ });
+ });
+ },
+
+ 'test that responding to a heartbeat maintains session': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messages = 0
+ , heartbeats = 0
+ , ws;
+
+ io.configure(function () {
+ io.set('heartbeat interval', .05);
+ io.set('heartbeat timeout', .05);
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function (reason) {
+ heartbeats.should.eql(2);
+ reason.should.eql('heartbeat timeout');
+
+ cl.end();
+ ws.finishClose();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('message', function (packet) {
+ if (++messages == 1) {
+ packet.type.should.eql('connect');
+ } else {
+ packet.type.should.eql('heartbeat');
+ heartbeats++;
+
+ if (heartbeats == 1) {
+ ws.packet({ type: 'heartbeat' });
+ }
+ }
+ });
+ });
+ },
+
+ 'test sending undeliverable volatile messages': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messages = 0
+ , messaged = false
+ , s;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ var ws = websocket(cl, sid);
+ ws.on('message', function (msg) {
+ msg.type.should.eql('connect');
+ ws.finishClose();
+
+ setTimeout(function () {
+ s.volatile.send('ah wha wha');
+
+ ws = websocket(cl, sid);
+ ws.on('message', function () {
+ messaged = true;
+ });
+
+ setTimeout(function () {
+ ws.finishClose();
+ }, 10);
+ }, 10);
+ });
+ });
+ },
+
+ 'test sending undeliverable volatile json': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false
+ , s;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ var ws = websocket(cl, sid);
+ ws.on('message', function () {
+ ws.finishClose();
+
+ setTimeout(function () {
+ s.volatile.json.send({ a: 'b' });
+
+ ws = websocket(cl, sid);
+ ws.on('message', function () {
+ messaged = true;
+ });
+
+ setTimeout(function () {
+ ws.finishClose();
+ }, 10);
+ }, 10);
+ });
+ });
+ },
+
+ 'test sending undeliverable volatile events': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false
+ , s;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ var ws = websocket(cl, sid);
+ ws.on('message', function () {
+ ws.finishClose();
+
+ setTimeout(function () {
+ s.volatile.emit({ a: 'b' });
+
+ ws = websocket(cl, sid);
+ ws.on('message', function () {
+ messaged = true;
+ });
+
+ setTimeout(function () {
+ ws.finishClose();
+ }, 10);
+ }, 10);
+ });
+ });
+ },
+
+ 'test sending deliverable volatile messages': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messages = 0
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.volatile.send('tobi');
+
+ socket.on('disconnect', function () {
+ messaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ var ws = websocket(cl, sid);
+ ws.on('message', function (msg) {
+ if (++messages == 1) {
+ msg.type.should.eql('connect');
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'tobi'
+ , endpoint: ''
+ });
+ messaged = true;
+ ws.finishClose();
+ }
+ });
+ });
+ },
+
+ 'test sending deliverable volatile json': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.volatile.json.send([1, 2, 3]);
+
+ socket.on('disconnect', function () {
+ messaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ var ws = websocket(cl, sid);
+ ws.on('message', function (msg) {
+ if (!ws.connected) {
+ msg.type.should.eql('connect');
+ ws.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: [1, 2, 3]
+ , endpoint: ''
+ });
+ messaged = true;
+ ws.finishClose();
+ }
+ });
+ });
+ },
+
+ 'test sending deliverable volatile events': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.volatile.emit('tobi');
+
+ socket.on('disconnect', function () {
+ messaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ var ws = websocket(cl, sid);
+ ws.on('message', function (msg) {
+ if (!ws.connected) {
+ msg.type.should.eql('connect');
+ ws.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'tobi'
+ , endpoint: ''
+ , args: []
+ });
+ messaged = true;
+ ws.finishClose();
+ }
+ });
+ });
+ },
+
+ 'test sending to all clients in a namespace': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections == 2) {
+ io.sockets.send('yup');
+ }
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 2) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'yup'
+ , endpoint: ''
+ });
+
+ messages++;
+ ws1.finishClose();
+ }
+ });
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'yup'
+ , endpoint: ''
+ });
+
+ messages++;
+ ws2.finishClose();
+ }
+ });
+ });
+ },
+
+ 'test sending json to all clients in a namespace': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections == 2) {
+ io.sockets.json.send({ a: 'b' });
+ }
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 2) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: { a: 'b' }
+ , endpoint: ''
+ });
+
+ messages++;
+ ws1.finishClose();
+ }
+ });
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: { a: 'b' }
+ , endpoint: ''
+ });
+
+ messages++;
+ ws2.finishClose();
+ }
+ });
+ });
+ },
+
+ 'test emitting to all clients in a namespace': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections == 2) {
+ io.sockets.emit('tobi', 'rapture');
+ }
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 2) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'tobi'
+ , args: ['rapture']
+ , endpoint: ''
+ });
+
+ messages++;
+ ws1.finishClose();
+ }
+ });
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'tobi'
+ , args: ['rapture']
+ , endpoint: ''
+ });
+
+ messages++;
+ ws2.finishClose();
+ }
+ });
+ });
+ },
+
+ 'test sending to all clients in a room': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , joins = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections != 3) {
+ socket.join('woot');
+ joins++;
+
+ if (joins == 2) {
+ setTimeout(function () {
+ connections.should.eql(3);
+ io.sockets.in('woot').send('hahaha');
+ }, 20);
+ }
+ }
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'hahaha'
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws1.finishClose();
+ }, 50);
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'hahaha'
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws2.finishClose();
+ }, 50);
+ });
+
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'hahaha'
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws3.finishClose();
+ }, 50);
+ });
+ },
+
+ 'test sending json to all clients in a room': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , joins = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections != 3) {
+ socket.join('woot');
+ joins++;
+
+ if (joins == 2) {
+ setTimeout(function () {
+ connections.should.eql(3);
+ io.sockets.in('woot').json.send(123);
+ }, 20);
+ }
+ }
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: 123
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws1.finishClose();
+ }, 50);
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: 123
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws2.finishClose();
+ }, 50);
+ });
+
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: 123
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws3.finishClose();
+ }, 50);
+ });
+ },
+
+ 'test emitting to all clients in a room': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , joins = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections != 3) {
+ socket.join('woot');
+ joins++;
+
+ if (joins == 2) {
+ setTimeout(function () {
+ connections.should.eql(3);
+ io.sockets.in('woot').emit('locki');
+ }, 20);
+ }
+ }
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'locki'
+ , args: []
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws1.finishClose();
+ }, 50);
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'locki'
+ , args: []
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws2.finishClose();
+ }, 50);
+ });
+
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'locki'
+ , args: []
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ setTimeout(function () {
+ ws3.finishClose();
+ }, 50);
+ });
+ },
+
+ 'test leaving a room': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , io = create(cl1)
+ , joins = 0
+ , disconnects = 0;
+
+ io.set('close timeout', 0);
+
+ io.sockets.on('connection', function (socket) {
+ socket.join('foo');
+ io.sockets.clients('foo').should.have.length(++joins);
+
+ socket.on('disconnect', function () {
+ socket.leave('foo');
+ socket.leave('foo');
+ socket.leave('foo');
+
+ io.sockets.clients('foo').should.have.length(--joins);
+
+ if (++disconnects == 2) {
+ io.server.close();
+ cl1.end();
+ cl2.end();
+ done();
+ }
+ })
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ ws1.finishClose();
+ }
+ });
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ ws2.finishClose();
+ }
+ });
+ });
+ },
+
+ 'test message with broadcast flag': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('trigger broadcast', function () {
+ socket.broadcast.send('boom');
+ });
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'boom'
+ , endpoint: ''
+ });
+
+ messages++;
+ ws1.finishClose();
+ }
+ });
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'boom'
+ , endpoint: ''
+ });
+
+ messages++;
+ ws2.finishClose();
+ }
+ });
+ });
+
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('open', function () {
+ ws3.packet({
+ type: 'event'
+ , name: 'trigger broadcast'
+ , endpoint: ''
+ });
+
+ setTimeout(function () {
+ ws3.finishClose();
+ }, 50);
+ });
+
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ throw new Error('we shouldnt get a message here');
+ }
+ });
+ });
+ },
+
+ 'test json with broadcast flag': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('trigger broadcast', function () {
+ socket.broadcast.json.send([1, 2, 3]);
+ });
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: [1, 2, 3]
+ , endpoint: ''
+ });
+
+ messages++;
+ ws1.finishClose();
+ }
+ });
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: [1, 2, 3]
+ , endpoint: ''
+ });
+
+ messages++;
+ ws2.finishClose();
+ }
+ });
+ });
+
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('open', function () {
+ ws3.packet({
+ type: 'event'
+ , name: 'trigger broadcast'
+ , endpoint: ''
+ });
+
+ setTimeout(function () {
+ ws3.finishClose();
+ }, 50);
+ });
+
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ throw new Error('we shouldnt get a message here');
+ }
+ });
+ });
+ },
+
+ 'test event with broadcast flag': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('trigger broadcast', function () {
+ socket.broadcast.emit('hey', 'arnold');
+ });
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(2);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'hey'
+ , args: ['arnold']
+ , endpoint: ''
+ });
+
+ messages++;
+ ws1.finishClose();
+ }
+ });
+ });
+
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'hey'
+ , args: ['arnold']
+ , endpoint: ''
+ });
+
+ messages++;
+ ws2.finishClose();
+ }
+ });
+ });
+
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('open', function () {
+ ws3.packet({
+ type: 'event'
+ , name: 'trigger broadcast'
+ , endpoint: ''
+ });
+
+ setTimeout(function () {
+ ws3.finishClose();
+ }, 50);
+ });
+
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ throw new Error('we shouldnt get a message here');
+ }
+ });
+ });
+ },
+
+ 'test message with broadcast flag and to()': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections == 1) {
+ socket.join('losers');
+ }
+
+ socket.on('trigger broadcast', function () {
+ socket.broadcast.to('losers').send('boom');
+ });
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(1);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'message'
+ , data: 'boom'
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ ws1.on('open', function () {
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ throw new Error('This socket shouldnt get a message');
+ }
+ });
+
+ ws2.on('open', function () {
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('open', function () {
+ ws3.packet({
+ type: 'event'
+ , name: 'trigger broadcast'
+ , endpoint: ''
+ });
+
+ setTimeout(function () {
+ ws1.finishClose();
+ ws2.finishClose();
+ ws3.finishClose();
+ }, 50);
+ });
+
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ throw new Error('we shouldnt get a message here');
+ }
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test json with broadcast flag and to()': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections == 1) {
+ socket.join('losers');
+ }
+
+ socket.on('trigger broadcast', function () {
+ socket.broadcast.json.to('losers').send({ hello: 'world' });
+ });
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(1);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'json'
+ , data: { hello: 'world' }
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ ws1.on('open', function () {
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ throw new Error('This socket shouldnt get a message');
+ }
+ });
+
+ ws2.on('open', function () {
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('open', function () {
+ ws3.packet({
+ type: 'event'
+ , name: 'trigger broadcast'
+ , endpoint: ''
+ });
+
+ setTimeout(function () {
+ ws1.finishClose();
+ ws2.finishClose();
+ ws3.finishClose();
+ }, 50);
+ });
+
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ throw new Error('we shouldnt get a message here');
+ }
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test event with broadcast flag and to()': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , cl3 = client(port)
+ , io = create(cl1)
+ , messages = 0
+ , connections = 0
+ , disconnections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', 0);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ connections++;
+
+ if (connections == 1) {
+ socket.join('losers');
+ }
+
+ socket.on('trigger broadcast', function () {
+ socket.broadcast.to('losers').emit('victory');
+ });
+
+ socket.on('disconnect', function () {
+ disconnections++;
+
+ if (disconnections == 3) {
+ messages.should.eql(1);
+ cl1.end();
+ cl2.end();
+ cl3.end();
+ io.server.close();
+ done();
+ }
+ });
+ });
+
+ cl1.handshake(function (sid) {
+ var ws1 = websocket(cl1, sid);
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ } else {
+ msg.should.eql({
+ type: 'event'
+ , name: 'victory'
+ , args: []
+ , endpoint: ''
+ });
+
+ messages++;
+ }
+ });
+
+ ws1.on('open', function () {
+ cl2.handshake(function (sid) {
+ var ws2 = websocket(cl2, sid);
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ } else {
+ throw new Error('This socket shouldnt get a message');
+ };
+ });
+
+ ws2.on('open', function () {
+ cl3.handshake(function (sid) {
+ var ws3 = websocket(cl3, sid);
+ ws3.on('open', function () {
+ ws3.packet({
+ type: 'event'
+ , name: 'trigger broadcast'
+ , endpoint: ''
+ });
+
+ setTimeout(function () {
+ ws1.finishClose();
+ ws2.finishClose();
+ ws3.finishClose();
+ }, 50);
+ });
+
+ ws3.on('message', function (msg) {
+ if (!ws3.connected) {
+ msg.type.should.eql('connect');
+ ws3.connected = true;
+ } else {
+ throw new Error('we shouldnt get a message here');
+ }
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test accessing handshake data from sockets': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , ws;
+
+ io.sockets.on('connection', function (socket) {
+ (!!socket.handshake.address.address).should.be.true;
+ (!!socket.handshake.address.port).should.be.true;
+ socket.handshake.headers.host.should.equal('localhost');
+ socket.handshake.headers.connection.should.equal('keep-alive');
+ socket.handshake.time.should.match(/GMT/);
+
+ socket.on('disconnect', function () {
+ setTimeout(function () {
+ ws.finishClose();
+ cl.end();
+ io.server.close();
+ done();
+ }, 10);
+ });
+
+ socket.disconnect();
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('message', function (msg) {
+ if (!ws.connected) {
+ msg.type.should.eql('connect');
+ ws.connected = true;
+ }
+ });
+ });
+ },
+
+ 'test accessing the array of clients': function (done) {
+ var port = ++ports
+ , cl1 = client(port)
+ , cl2 = client(port)
+ , io = create(cl1)
+ , total = 2
+ , ws1, ws2;
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('join ferrets', function () {
+ socket.join('ferrets');
+ socket.send('done');
+ });
+ });
+
+ function check() {
+ io.sockets.clients('ferrets').should.have.length(1);
+ io.sockets.clients('ferrets')[0].should.be.an.instanceof(sio.Socket);
+ io.sockets.clients('ferrets')[0].id.should.equal(ws1.sid);
+ io.sockets.clients().should.have.length(2);
+ io.sockets.clients()[0].should.be.an.instanceof(sio.Socket);
+ io.sockets.clients()[0].id.should.equal(ws1.sid);
+ io.sockets.clients()[1].should.be.an.instanceof(sio.Socket);
+ io.sockets.clients()[1].id.should.equal(ws2.sid);
+
+ ws1.finishClose();
+ ws2.finishClose();
+ cl1.end();
+ cl2.end();
+ io.server.close();
+ done();
+ };
+
+ cl1.handshake(function (sid) {
+ ws1 = websocket(cl1, sid);
+ ws1.sid = sid;
+ ws1.on('message', function (msg) {
+ if (!ws1.connected) {
+ msg.type.should.eql('connect');
+ ws1.connected = true;
+ ws1.packet({
+ type: 'event'
+ , name: 'join ferrets'
+ , endpoint: ''
+ });
+ } else {
+ cl2.handshake(function (sid) {
+ ws2 = websocket(cl2, sid);
+ ws2.sid = sid;
+ ws2.on('message', function (msg) {
+ if (!ws2.connected) {
+ msg.type.should.eql('connect');
+ ws2.connected = true;
+ check();
+ }
+ });
+ });
+ }
+ });
+ });
+ },
+
+ 'test accessing handshake data from sockets on disconnect': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , ws;
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+
+ (!!socket.handshake.address.address).should.be.true;
+ (!!socket.handshake.address.port).should.be.true;
+ socket.handshake.headers.host.should.equal('localhost');
+ socket.handshake.headers.connection.should.equal('keep-alive');
+ socket.handshake.time.should.match(/GMT/);
+
+ setTimeout(function () {
+ ws.finishClose();
+ cl.end();
+ io.server.close();
+ done();
+ }, 10);
+ });
+
+ socket.disconnect();
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('message', function (msg) {
+ if (!ws.connected) {
+ msg.type.should.eql('connect');
+ ws.connected = true;
+ }
+ });
+ });
+ },
+
+ 'test for intentional and unintentional disconnects': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , calls = 0
+ , ws;
+
+ function close () {
+ cl.end();
+ io.server.close();
+ ws.finishClose();
+ done();
+ }
+
+ io.configure(function () {
+ io.set('heartbeat interval', .05);
+ io.set('heartbeat timeout', .05);
+ io.set('close timeout', 0);
+ });
+
+ io.of('/foo').on('connection', function (socket) {
+ socket.on('disconnect', function (reason) {
+ reason.should.equal('packet');
+
+ if (++calls == 2) close();
+ });
+ });
+
+ io.of('/bar').on('connection', function (socket) {
+ socket.on('disconnect', function (reason) {
+ reason.should.equal('socket end');
+
+ if (++calls == 2) close();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ var messages = 0;
+ ws = websocket(cl, sid);
+ ws.on('open', function () {
+ ws.packet({
+ type: 'connect'
+ , endpoint: '/foo'
+ });
+ ws.packet({
+ type: 'connect'
+ , endpoint: '/bar'
+ });
+ });
+
+ ws.on('message', function (packet) {
+ if (packet.type == 'connect') {
+ if (++messages === 3) {
+ ws.packet({ type: 'disconnect', endpoint:'/foo' });
+ ws.finishClose();
+ }
+ }
+ });
+ });
+ },
+
+ 'test socket clean up': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , ws;
+
+ io.sockets.on('connection', function (socket) {
+ var self = this
+ , id = socket.id;
+
+ socket.on('disconnect', function () {
+ setTimeout(function () {
+ var available = !!self.sockets[id];
+
+ available.should.be.false;
+ ws.finishClose();
+ cl.end();
+ io.server.close();
+ done();
+ }, 10);
+ });
+
+ socket.disconnect();
+ });
+
+ cl.handshake(function (sid) {
+ ws = websocket(cl, sid);
+ ws.on('message', function (msg) {
+ if (!ws.connected) {
+ msg.type.should.eql('connect');
+ ws.connected = true;
+ }
+ });
+ });
+ },
+
+};
--- /dev/null
+
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Test dependencies.
+ */
+
+var sio = require('socket.io')
+ , should = require('./common')
+ , HTTPClient = should.HTTPClient
+ , parser = sio.parser
+ , ports = 15200;
+
+/**
+ * HTTPClient for xhr-polling transport.
+ */
+
+function XHRPolling (port) {
+ HTTPClient.call(this, port);
+};
+
+/**
+ * Inhertis from HTTPClient.
+ */
+
+XHRPolling.prototype.__proto__ = HTTPClient.prototype;
+
+/**
+ * Performs the handshake and expects the connect echo packet.
+ *
+ * @api public
+ */
+
+XHRPolling.prototype.handshake = function (opts, fn) {
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+
+ var self = this;
+
+ return this.get('/socket.io/{protocol}', opts, function (res, data) {
+ var parts = data.split(':');
+
+ if (opts.ignoreConnect) {
+ return fn && fn.apply(null, parts);
+ }
+
+ // expect connect packet right after handshake
+ self.get(
+ '/socket.io/{protocol}/xhr-polling/' + parts[0]
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'connect', endpoint: '', qs: '' });
+
+ fn && fn.apply(null, parts);
+ }
+ );
+ });
+};
+
+/**
+ * Create client for this transport.
+ *
+ * @api public
+ */
+
+function client (port) {
+ return new XHRPolling(port);
+};
+
+/**
+ * Test.
+ */
+
+module.exports = {
+
+ 'test handshake': function (done) {
+ var cl = client(++ports)
+ , io = create(cl);
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ io.set('polling duration', 0);
+ });
+
+ function finish () {
+ cl.end();
+ io.server.close();
+ done();
+ };
+
+ cl.handshake(function (sid) {
+ var total = 2;
+
+ cl.get('/socket.io/{protocol}/xhr-polling/tobi', function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'error'
+ , reason: 'client not handshaken'
+ , endpoint: ''
+ , advice: 'reconnect'
+ });
+
+ --total || finish();
+ });
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+ --total || finish();
+ });
+ });
+ },
+
+ 'test the connection event': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , sid;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.id.should.eql(sid);
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ });
+ });
+ },
+
+ 'test the disconnection event after a close timeout': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , sid;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.id.should.eql(sid);
+
+ socket.on('disconnect', function () {
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+
+ setTimeout(function () {
+ cl.end();
+ }, 10);
+ });
+ });
+ },
+
+ 'test the disconnection event when the client sends ?disconnect req':
+ function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , disconnected = false
+ , sid;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ disconnected = true;
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
+ disconnected.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid + '/?disconnect');
+ });
+ });
+ },
+
+ 'test the disconnection event booting a client': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , forced = false;
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ io.server.close();
+ done();
+ });
+
+ cl.end();
+ socket.disconnect();
+ forced = true;
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'disconnect', endpoint: '' });
+
+ forced.should.be.true;
+ });
+ });
+ },
+
+ 'test the disconnection event with client disconnect packet': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , sid;
+
+ io.sockets.on('connection', function (client) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'disconnect' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+
+ client.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ });
+ });
+ },
+
+ 'test sending back data': function (done) {
+ var cl = client(++ports)
+ , io = create(cl);
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.send('woot');
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, packs) {
+ packs.should.have.length(1);
+ packs[0].type.should.eql('message');
+ packs[0].data.should.eql('woot');
+ });
+ });
+ },
+
+ 'test sending a batch of messages': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , sid;
+
+ io.configure(function () {
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ var messages = 0;
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePayload([
+ parser.encodePacket({ type: 'message', data: 'a' })
+ , parser.encodePacket({ type: 'message', data: 'b' })
+ , parser.encodePacket({ type: 'disconnect' })
+ ])
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+
+ socket.on('message', function (data) {
+ messages++;
+
+ if (messages == 1)
+ data.should.eql('a');
+
+ if (messages == 2)
+ data.should.eql('b');
+ });
+
+ socket.on('disconnect', function () {
+ messages.should.eql(2);
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake({ ignoreConnect: true }, function (sessid) {
+ sid = sessid;
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].type.should.eql('connect');
+ });
+ });
+ },
+
+ 'test message buffering between a response and a request': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messages = false
+ , tobi;
+
+ io.configure(function () {
+ io.set('polling duration', .1);
+ io.set('close timeout', .2);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ tobi = function () {
+ socket.send('a');
+ socket.send('b');
+ socket.send('c');
+ };
+
+ socket.on('disconnect', function () {
+ messages.should.be.true;
+
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ tobi();
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ msgs.should.have.length(3);
+ msgs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
+ msgs[1].should.eql({ type: 'message', endpoint: '', data: 'b' });
+ msgs[2].should.eql({ type: 'message', endpoint: '', data: 'c' });
+ messages = true;
+ });
+ })
+ });
+ },
+
+ 'test connecting to a specific endpoint': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , connectMessage = false
+ , sid;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/woot').on('connection', function (socket) {
+ connectMessage.should.be.true;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid);
+
+ connectMessage = true;
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/woot' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ });
+ });
+ },
+
+ 'test that connecting doesnt connect to defined endpoints': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , tobiConnected = false
+ , mainConnected = false
+ , sid;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ mainConnected = true;
+
+ socket.on('disconnect', function () {
+ tobiConnected.should.be.false;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/tobi').on('connection', function () {
+ tobiConnected = true;
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid);
+ });
+ },
+
+ 'test disconnecting a specific endpoint': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , wootDisconnected = false
+ , mainDisconnected = false
+ , checked = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (data) {
+ data.should.eql('ferret');
+ mainDisconnected.should.be.false;
+ wootDisconnected.should.be.true;
+ checked = true;
+ });
+
+ socket.on('disconnect', function () {
+ mainDisconnected = true;
+ checked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/woot').on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ wootDisconnected = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function () {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/woot' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'disconnect', endpoint: '/woot' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'message', data: 'ferret' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test that disconnecting disconnects all endpoints': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , aDisconnected = false
+ , bDisconnected = false;
+
+ io.configure(function () {
+ io.set('polling duration', .2);
+ io.set('close timeout', .2);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ setTimeout(function () {
+ aDisconnected.should.be.true;
+ bDisconnected.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ }, 50);
+ });
+ });
+
+ io.of('/a').on('connection', function (socket) {
+ socket.on('disconnect', function (msg) {
+ aDisconnected = true;
+ });
+ });
+
+ io.of('/b').on('connection', function (socket) {
+ socket.on('disconnect', function (msg) {
+ bDisconnected = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/b' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ });
+ });
+ },
+
+ 'test messaging a specific endpoint': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = true
+ , aMessaged = false
+ , bMessaged = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql('');
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ messaged.should.be.true;
+ aMessaged.should.be.true;
+ bMessaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/a').on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql('a');
+ aMessaged = true;
+ });
+ });
+
+ io.of('/b').on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql('b');
+ bMessaged = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'message', data: '' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/b' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test sending json from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messages = 0
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ s.json.send(['a', 'b', 'c']);
+ s.json.send({
+ a: 'b'
+ , c: 'd'
+ });
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'json'
+ , data: ['a', 'b', 'c']
+ , endpoint: ''
+ });
+ msgs[1].should.eql({
+ type: 'json'
+ , data: {
+ a: 'b'
+ , c: 'd'
+ }
+ , endpoint: ''
+ });
+ });
+ })
+ });
+ },
+
+ 'test sending json to the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messages = 0;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .1);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ messages++;
+
+ if (messages == 1) {
+ msg.should.eql({ tobi: 'rocks' });
+ } else if (messages == 2) {
+ msg.should.eql(5000);
+ }
+ });
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'json'
+ , data: { tobi: 'rocks' }
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.equal('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'json'
+ , data: 5000
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.equal('1');
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test emitting an event from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ s.emit('tobi is playing');
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'event'
+ , name: 'tobi is playing'
+ , endpoint: ''
+ , args: []
+ });
+ });
+ });
+ });
+ },
+
+ 'test emitting an event with data from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ s.emit('edwald', { woot: 'woot' }, [1, 2, 3]);
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'event'
+ , name: 'edwald'
+ , endpoint: ''
+ , args: [{ woot: 'woot' }, [1, 2, 3]]
+ });
+ });
+ });
+ });
+ },
+
+ 'test emitting an event to the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('jane', function (a, b, c) {
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ messaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'event'
+ , name: 'jane'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.equal('1');
+ }
+ );
+ });
+ });
+ },
+
+ 'test that emitting an error event doesnt throw': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'event'
+ , name: 'error'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.equal('1');
+ }
+ );
+ });
+ });
+ },
+
+ 'test emitting an event to the server with data': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('woot', function (a, b, c) {
+ a.should.eql('a');
+ b.should.eql(2);
+ c.should.eql([1, 2]);
+
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ messaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'event'
+ , name: 'woot'
+ , args: ['a', 2, [1, 2]]
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.equal('1');
+ }
+ );
+ });
+ });
+ },
+
+ 'test sending undeliverable volatile messages': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ s.volatile.send('woooot');
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+ });
+ });
+ });
+ },
+
+ 'test sending undeliverable volatile json': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ s.volatile.json.send('woooot');
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+ });
+ });
+ });
+ },
+
+ 'test sending undeliverable volatile events': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ s.volatile.emit('woooot');
+
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+ });
+ });
+ });
+ },
+
+ 'test sending deliverable volatile messages': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'message'
+ , data: 'woooot'
+ , endpoint: ''
+ });
+ });
+
+ setTimeout(function () {
+ s.volatile.send('woooot');
+ }, 10);
+ });
+ },
+
+ 'test sending deliverable volatile json': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'json'
+ , data: 5
+ , endpoint: ''
+ });
+ });
+
+ setTimeout(function () {
+ s.volatile.json.send(5);
+ }, 10);
+ });
+ },
+
+ 'test sending deliverable volatile events': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'event'
+ , name: 'tobi'
+ , args: []
+ , endpoint: ''
+ });
+ });
+
+ setTimeout(function () {
+ s.volatile.json.emit('tobi');
+ }, 10);
+ });
+ },
+
+ 'test automatic acknowledgements sent from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , received = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql('woot');
+ received = true;
+ });
+
+ socket.on('disconnect', function () {
+ received.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'message'
+ , data: 'woot'
+ , id: 1
+ , endpoint: ''
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'ack'
+ , ackId: 1
+ , endpoint: ''
+ , args: []
+ });
+ }
+ );
+ }
+ );
+ });
+
+ });
+ },
+
+ 'test manual data acknowledgement sent from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , acknowledged = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (data, fn) {
+ data.should.eql('tobi');
+ fn('woot');
+ acknowledged = true;
+ });
+
+ socket.on('disconnect', function () {
+ acknowledged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'ack'
+ , args: ['woot']
+ , endpoint: ''
+ , ackId: '3'
+ });
+ });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'message'
+ , data: 'tobi'
+ , ack: 'data'
+ , id: '3'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ });
+ },
+
+ 'test automatic acknowledgements sent from the client': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , acknowledged = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.send('aaaa', function () {
+ acknowledged = true;
+ });
+
+ socket.on('disconnect', function () {
+ acknowledged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'message'
+ , id: '1'
+ , data: 'aaaa'
+ , ack: true
+ , endpoint: ''
+ });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'ack'
+ , ackId: '1'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ });
+ });
+ },
+
+ 'test automatic ack with event sent from the client': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , acked = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.emit('woot', 1, 2, '3', function () {
+ acked = true;
+ });
+
+ socket.on('disconnect', function () {
+ acked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'event'
+ , name: 'woot'
+ , args: [1, 2, '3']
+ , id: '1'
+ , ack: true
+ , endpoint: ''
+ });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'ack'
+ , ackId: '1'
+ , args: []
+ , endpoint: ''
+ })
+ );
+ });
+ });
+ },
+
+ 'test manual data ack with event sent from the client': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , acked = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.emit('woot', 1, 2, '3', function (a) {
+ a.should.eql('1');
+ acked = true;
+ });
+
+ socket.on('disconnect', function () {
+ acked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'event'
+ , name: 'woot'
+ , args: [1, 2, '3']
+ , id: '1'
+ , ack: 'data'
+ , endpoint: ''
+ });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'ack'
+ , ackId: '1'
+ , args: ['1']
+ , endpoint: ''
+ })
+ );
+ });
+ });
+ },
+
+ 'test endpoint sending json from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , received = false;;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ received.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ socket.json.send([1, 2, { 3: 4 }]);
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+ msgs[1].should.eql({
+ type: 'json'
+ , data: [1, 2, { 3: 4 }]
+ , endpoint: '/chrislee'
+ });
+
+ received = true;
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint sending json to the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false
+ , subMessaged = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ subMessaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/a').on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql(['a', 'b', { c: 'd' }]);
+ subMessaged = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'json'
+ , endpoint: '/a'
+ , data: ['a', 'b', { c: 'd' }]
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint emitting an event from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , received = false;;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ received.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ socket.emit('tj');
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+ msgs[1].should.eql({
+ type: 'event'
+ , name: 'tj'
+ , args: []
+ , endpoint: '/chrislee'
+ });
+
+ received = true;
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint emitting an event with data from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , received = false;;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('disconnect', function () {
+ received.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ socket.emit('tj', 1, 2, 3, 4);
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+ msgs[1].should.eql({
+ type: 'event'
+ , name: 'tj'
+ , args: [1, 2, 3, 4]
+ , endpoint: '/chrislee'
+ });
+
+ received = true;
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint emitting an event to the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false
+ , subMessaged = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ subMessaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/a').on('connection', function (socket) {
+ socket.on('tj', function () {
+ subMessaged = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'event'
+ , name: 'tj'
+ , endpoint: '/a'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint emitting an event to the server with data': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false
+ , subMessaged = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ messaged.should.be.false;
+ subMessaged.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ io.of('/a').on('connection', function (socket) {
+ socket.on('tj', function (ferret, age) {
+ ferret.should.eql('tobi');
+ age.should.eql(23);
+ subMessaged = true;
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/a' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'event'
+ , name: 'tj'
+ , endpoint: '/a'
+ , args: ['tobi', 23]
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint sending undeliverable volatile messages': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , empty = false
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ empty.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ s.volatile.send('woot');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.length.should.eql(1);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+ empty = true;
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint sending undeliverable volatile json': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , empty = false
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ empty.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ s.volatile.json.send(15);
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.length.should.eql(1);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+ empty = true;
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint sending undeliverable volatile events': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , empty = false
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ empty.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ s.volatile.json.emit('woot');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.length.should.eql(1);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+ empty = true;
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint sending deliverable volatile messages': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , received = false
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ received.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'message'
+ , data: 'edwald'
+ , endpoint: '/chrislee'
+ });
+
+ received = true;
+ }
+ );
+
+ setTimeout(function () {
+ s.volatile.send('edwald');
+ }, 20);
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint sending deliverable volatile json': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , received = false
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ received.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'json'
+ , data: 152
+ , endpoint: '/chrislee'
+ });
+
+ received = true;
+ }
+ );
+
+ setTimeout(function () {
+ s.volatile.json.send(152);
+ }, 20);
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint sending deliverable volatile events': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , received = false
+ , s;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/chrislee').on('connection', function (socket) {
+ s = socket;
+
+ socket.on('disconnect', function () {
+ received.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/chrislee' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/chrislee'
+ , qs: ''
+ });
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ msgs.should.have.length(1);
+ msgs[0].should.eql({
+ type: 'event'
+ , name: 'woooo'
+ , args: [[1, 2]]
+ , endpoint: '/chrislee'
+ });
+
+ received = true;
+ }
+ );
+
+ setTimeout(function () {
+ s.volatile.emit('woooo', [1, 2]);
+ }, 20);
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint automatic acks sent from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false
+ , acked = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/tobi').on('connection', function (socket) {
+ socket.on('message', function (msg) {
+ msg.should.eql('woot');
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ acked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/tobi' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'message'
+ , id: '3'
+ , data: 'woot'
+ , endpoint: '/tobi'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/tobi'
+ , qs: ''
+ });
+ msgs[1].should.eql({
+ type: 'ack'
+ , ackId: '3'
+ , endpoint: '/tobi'
+ , args: []
+ });
+
+ acked = true;
+ }
+ );
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint manual data ack sent from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false
+ , acked = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/tobi').on('connection', function (socket) {
+ socket.on('message', function (msg, fn) {
+ msg.should.eql('woot');
+ fn();
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ acked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/tobi' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'message'
+ , id: '3'
+ , data: 'woot'
+ , ack: 'data'
+ , endpoint: '/tobi'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/tobi'
+ , qs: ''
+ });
+ msgs[1].should.eql({
+ type: 'ack'
+ , ackId: '3'
+ , args: []
+ , endpoint: '/tobi'
+ });
+
+ acked = true;
+ }
+ );
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint manual data event ack sent from the server': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false
+ , acked = false;
+
+ io.configure(function () {
+ io.set('polling duration', 0);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/tobi').on('connection', function (socket) {
+ socket.on('woot', function (msg, fn) {
+ msg.should.eql(1);
+ fn('aaaa');
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ acked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/tobi' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'event'
+ , id: '3'
+ , name: 'woot'
+ , ack: 'data'
+ , args: [1]
+ , endpoint: '/tobi'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/tobi'
+ , qs: ''
+ });
+ msgs[1].should.eql({
+ type: 'ack'
+ , ackId: '3'
+ , args: ['aaaa']
+ , endpoint: '/tobi'
+ });
+
+ acked = true;
+ }
+ );
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint acknowledgements sent from the client': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , acked = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/woot').on('connection', function (socket) {
+ socket.send('aaa', function () {
+ acked = true;
+ });
+
+ socket.on('disconnect', function () {
+ acked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.eql(200);
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/woot' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/woot'
+ , qs: ''
+ });
+ msgs[1].should.eql({
+ type: 'message'
+ , data: 'aaa'
+ , endpoint: '/woot'
+ , id: '1'
+ , ack: true
+ });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'ack'
+ , ackId: '1'
+ , endpoint: '/woot'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test endpoint manual data event acks sent from the client': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , acked = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.of('/rapture').on('connection', function (socket) {
+ socket.emit('woot', 'a', function (a, b, c) {
+ a.should.eql(5);
+ b.should.eql('hello');
+ c.should.eql([1, 2, 3]);
+
+ acked = true;
+ });
+
+ socket.on('disconnect', function () {
+ acked.should.be.true;
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
+ res.statusCode.should.equal(200);
+ msgs.should.have.length(1);
+ msgs[0].should.eql({ type: 'noop', endpoint: '' });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({ type: 'connect', endpoint: '/rapture' })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, msgs) {
+ res.statusCode.should.eql(200);
+ msgs.should.have.length(2);
+ msgs[0].should.eql({
+ type: 'connect'
+ , endpoint: '/rapture'
+ , qs: ''
+ });
+ msgs[1].should.eql({
+ type: 'event'
+ , id: '1'
+ , name: 'woot'
+ , args: ['a']
+ , ack: 'data'
+ , endpoint: '/rapture'
+ });
+
+ cl.post(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , parser.encodePacket({
+ type: 'ack'
+ , ackId: '1'
+ , args: [5, 'hello', [1, 2, 3]]
+ , endpoint: '/rapture'
+ })
+ , function (res, data) {
+ res.statusCode.should.eql(200);
+ data.should.eql('1');
+ }
+ );
+ }
+ );
+ }
+ );
+ });
+ });
+ },
+
+ 'test CORS': function (done) {
+ var cl = client(++ports)
+ , io = create(cl)
+ , messaged = false;
+
+ io.configure(function () {
+ io.set('polling duration', .05);
+ io.set('close timeout', .05);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.send('woot');
+
+ socket.on('message', function (msg) {
+ msg.should.equal('woot');
+ messaged = true;
+ });
+
+ socket.on('disconnect', function () {
+ cl.end();
+ io.server.close();
+ done();
+ });
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, {
+ headers: {
+ Origin: 'http://localhost:3500'
+ }
+ }, function (res, packs) {
+ var headers = res.headers;
+
+ headers['access-control-allow-origin'].should.equal('*');
+ should.strictEqual(headers['access-control-allow-credentials'], undefined);
+
+ packs.should.have.length(1);
+ packs[0].type.should.eql('message');
+ packs[0].data.should.eql('woot');
+
+ cl.post('/socket.io/{protocol}/xhr-polling/' + sid, parser.encodePacket({
+ type: 'message'
+ , data: 'woot'
+ }), {
+ headers: {
+ Origin: 'http://localhost:3500'
+ , Cookie: 'woot=woot'
+ }
+ }, function (res, data) {
+ var headers = res.headers;
+ headers['access-control-allow-origin'].should.equal('*');
+ headers['access-control-allow-credentials'].should.equal('true');
+
+ data.should.equal('1');
+ });
+ });
+ });
+ },
+
+ 'test emitting to closed clients': function (done) {
+ var cl = client(++ports)
+ , cl2 = client(ports)
+ , io = create(cl)
+ , connections = 0;
+
+ io.configure(function () {
+ io.set('close timeout', .1);
+ });
+
+ io.sockets.on('connection', function (socket) {
+ socket.send('a');
+ });
+
+ cl.handshake(function (sid) {
+ cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, packs) {
+ res.statusCode.should.equal(200);
+ packs.should.have.length(1);
+ packs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
+
+ cl2.handshake(function (sid2) {
+ cl2.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid2
+ , function (res, packs) {
+ res.statusCode.should.equal(200);
+ packs.should.have.length(1);
+ packs[0].should.eql({ type: 'message', endpoint: '', data: 'a' });
+
+ io.sockets.emit('woot', 'b');
+
+ var total = 2;
+
+ cl.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid
+ , function (res, packs) {
+ res.statusCode.should.equal(200);
+ packs.should.have.length(1);
+ packs[0].should.eql({
+ type: 'event'
+ , endpoint: ''
+ , name: 'woot'
+ , args: ['b']
+ });
+
+ --total || finish();
+ }
+ );
+
+ cl2.get(
+ '/socket.io/{protocol}/xhr-polling/' + sid2
+ , function (res, packs) {
+ res.statusCode.should.equal(200);
+ packs.should.have.length(1);
+ packs[0].should.eql({
+ type: 'event'
+ , endpoint: ''
+ , name: 'woot'
+ , args: ['b']
+ });
+
+ --total || finish();
+ }
+ );
+
+ function finish () {
+ cl.end();
+ cl2.end();
+ io.server.close();
+ done();
+ };
+ }
+ );
+ });
+
+ });
+ });
+ }
+
+};
<title>UglifyJS -- a JavaScript parser/compressor/beautifier</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta name="generator" content="Org-mode"/>
-<meta name="generated" content="2011-07-14 12:50:31 EEST"/>
+<meta name="generated" content="2011-08-20 10:08:28 EEST"/>
<meta name="author" content="Mihai Bazon"/>
<meta name="description" content="a JavaScript parser/compressor/beautifier in JavaScript"/>
<meta name="keywords" content="javascript, js, parser, compiler, compressor, mangle, minify, minifier"/>
<li><a href="#sec-1_1">1.1 Unsafe transformations </a>
<ul>
<li><a href="#sec-1_1_1">1.1.1 Calls involving the global Array constructor </a></li>
+<li><a href="#sec-1_1_2">1.1.2 <code>obj.toString()</code> ==> <code>obj+“”</code> </a></li>
</ul>
</li>
<li><a href="#sec-1_2">1.2 Install (NPM) </a></li>
<p>
-UglifyJS tries its best to achieve great compression while leaving the
-semantics of the code intact. In general, if your code logic is broken by
-UglifyJS then it's a bug in UglifyJS and you should report it and I should
-fix it. :-)
-</p>
-<p>
-However, I opted to include the following potentially unsafe transformations
-as default behavior. Discussion is welcome, if you have ideas of how to
-handle this better, or any objections to these optimizations, please let me
-know.
+The following transformations can in theory break code, although they're
+probably safe in most practical cases. To enable them you need to pass the
+<code>--unsafe</code> flag.
</p>
</div>
-<pre class="src src-js"><span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3, 4) => [1,2,3,4]
+<pre class="src src-js"><span style="color: #a020f0;">new</span> <span style="color: #228b22;">Array</span>(1, 2, 3, 4) => [1,2,3,4]
Array(a, b, c) => [a,b,c]
-<span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(5) => Array(5)
-<span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(a) => Array(a)
+<span style="color: #a020f0;">new</span> <span style="color: #228b22;">Array</span>(5) => Array(5)
+<span style="color: #a020f0;">new</span> <span style="color: #228b22;">Array</span>(a) => Array(a)
</pre>
-<pre class="src src-js"><span style="color: #00008b;">// </span><span style="color: #00008b;">case 1. globally declared variable
-</span> <span style="color: #8b0000;">var</span> <span style="color: #8b008b;">Array</span>;
- <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3);
+<pre class="src src-js"><span style="color: #b22222;">// </span><span style="color: #b22222;">case 1. globally declared variable
+</span> <span style="color: #a020f0;">var</span> <span style="color: #b8860b;">Array</span>;
+ <span style="color: #a020f0;">new</span> <span style="color: #228b22;">Array</span>(1, 2, 3);
Array(a, b);
- <span style="color: #00008b;">// </span><span style="color: #00008b;">or (can be declared later)
-</span> <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3);
- <span style="color: #8b0000;">var</span> <span style="color: #8b008b;">Array</span>;
+ <span style="color: #b22222;">// </span><span style="color: #b22222;">or (can be declared later)
+</span> <span style="color: #a020f0;">new</span> <span style="color: #228b22;">Array</span>(1, 2, 3);
+ <span style="color: #a020f0;">var</span> <span style="color: #b8860b;">Array</span>;
- <span style="color: #00008b;">// </span><span style="color: #00008b;">or (can be a function)
-</span> <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3);
- <span style="color: #8b0000;">function</span> <span style="color: #8b2323;">Array</span>() { ... }
+ <span style="color: #b22222;">// </span><span style="color: #b22222;">or (can be a function)
+</span> <span style="color: #a020f0;">new</span> <span style="color: #228b22;">Array</span>(1, 2, 3);
+ <span style="color: #a020f0;">function</span> <span style="color: #0000ff;">Array</span>() { ... }
-<span style="color: #00008b;">// </span><span style="color: #00008b;">case 2. declared in a function
-</span> (<span style="color: #8b0000;">function</span>(){
- a = <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3);
+<span style="color: #b22222;">// </span><span style="color: #b22222;">case 2. declared in a function
+</span> (<span style="color: #a020f0;">function</span>(){
+ a = <span style="color: #a020f0;">new</span> <span style="color: #228b22;">Array</span>(1, 2, 3);
b = Array(5, 6);
- <span style="color: #8b0000;">var</span> <span style="color: #8b008b;">Array</span>;
+ <span style="color: #a020f0;">var</span> <span style="color: #b8860b;">Array</span>;
})();
- <span style="color: #00008b;">// </span><span style="color: #00008b;">or
-</span> (<span style="color: #8b0000;">function</span>(<span style="color: #8b008b;">Array</span>){
- <span style="color: #8b0000;">return</span> Array(5, 6, 7);
+ <span style="color: #b22222;">// </span><span style="color: #b22222;">or
+</span> (<span style="color: #a020f0;">function</span>(<span style="color: #b8860b;">Array</span>){
+ <span style="color: #a020f0;">return</span> Array(5, 6, 7);
})();
- <span style="color: #00008b;">// </span><span style="color: #00008b;">or
-</span> (<span style="color: #8b0000;">function</span>(){
- <span style="color: #8b0000;">return</span> <span style="color: #8b0000;">new</span> <span style="color: #4682b4;">Array</span>(1, 2, 3, 4);
- <span style="color: #8b0000;">function</span> <span style="color: #8b2323;">Array</span>() { ... }
+ <span style="color: #b22222;">// </span><span style="color: #b22222;">or
+</span> (<span style="color: #a020f0;">function</span>(){
+ <span style="color: #a020f0;">return</span> <span style="color: #a020f0;">new</span> <span style="color: #228b22;">Array</span>(1, 2, 3, 4);
+ <span style="color: #a020f0;">function</span> <span style="color: #0000ff;">Array</span>() { ... }
})();
- <span style="color: #00008b;">// </span><span style="color: #00008b;">etc.
+ <span style="color: #b22222;">// </span><span style="color: #b22222;">etc.
</span></pre>
+</div>
+
+</div>
+
+<div id="outline-container-1_1_2" class="outline-4">
+<h4 id="sec-1_1_2"><span class="section-number-4">1.1.2</span> <code>obj.toString()</code> ==> <code>obj+“”</code> </h4>
+<div class="outline-text-4" id="text-1_1_2">
+
+
</div>
</div>
-<pre class="src src-sh"><span style="color: #00008b;">## </span><span style="color: #00008b;">clone the repository
+<pre class="src src-sh"><span style="color: #b22222;">## </span><span style="color: #b22222;">clone the repository
</span>mkdir -p /where/you/wanna/put/it
-<span style="color: #cd0000;">cd</span> /where/you/wanna/put/it
+<span style="color: #da70d6;">cd</span> /where/you/wanna/put/it
git clone git://github.com/mishoo/UglifyJS.git
-<span style="color: #00008b;">## </span><span style="color: #00008b;">make the module available to Node
+<span style="color: #b22222;">## </span><span style="color: #b22222;">make the module available to Node
</span>mkdir -p ~/.node_libraries/
-<span style="color: #cd0000;">cd</span> ~/.node_libraries/
+<span style="color: #da70d6;">cd</span> ~/.node_libraries/
ln -s /where/you/wanna/put/it/UglifyJS/uglify-js.js
-<span style="color: #00008b;">## </span><span style="color: #00008b;">and if you want the CLI script too:
+<span style="color: #b22222;">## </span><span style="color: #b22222;">and if you want the CLI script too:
</span>mkdir -p ~/bin
-<span style="color: #cd0000;">cd</span> ~/bin
+<span style="color: #da70d6;">cd</span> ~/bin
ln -s /where/you/wanna/put/it/UglifyJS/bin/uglifyjs
- <span style="color: #00008b;"># </span><span style="color: #00008b;">(then add ~/bin to your $PATH if it's not there already)
+ <span style="color: #b22222;"># </span><span style="color: #b22222;">(then add ~/bin to your $PATH if it's not there already)
</span></pre>
<code>-v</code> or <code>--verbose</code> — output some notes on STDERR (for now just how long
each operation takes).
-</li>
-<li>
-<code>--extra</code> — enable additional optimizations that have not yet been
-extensively tested. These might, or might not, break your code. If you
-find a bug using this option, please report a test case.
-
</li>
<li>
<code>--unsafe</code> — enable other additional optimizations that are known to be
<code>--inline-script</code> – when you want to include the output literally in an
HTML <code><script></code> tag you can use this option to prevent <code></script</code> from
showing up in the output.
+
+</li>
+<li>
+<code>--lift-vars</code> – when you pass this, UglifyJS will apply the following
+transformations (see the notes in API, <code>ast_lift_variables</code>):
+
+<ul>
+<li>
+put all <code>var</code> declarations at the start of the scope
+</li>
+<li>
+make sure a variable is declared only once
+</li>
+<li>
+discard unused function arguments
+</li>
+<li>
+discard unused inner (named) functions
+</li>
+<li>
+finally, try to merge assignments into that one <code>var</code> declaration, if
+possible.
+</li>
+</ul>
</li>
</ul>
-<pre class="src src-js"><span style="color: #8b0000;">var</span> <span style="color: #8b008b;">jsp</span> = require(<span style="color: #008b00;">"uglify-js"</span>).parser;
-<span style="color: #8b0000;">var</span> <span style="color: #8b008b;">pro</span> = require(<span style="color: #008b00;">"uglify-js"</span>).uglify;
+<pre class="src src-js"><span style="color: #a020f0;">var</span> <span style="color: #b8860b;">jsp</span> = require(<span style="color: #bc8f8f;">"uglify-js"</span>).parser;
+<span style="color: #a020f0;">var</span> <span style="color: #b8860b;">pro</span> = require(<span style="color: #bc8f8f;">"uglify-js"</span>).uglify;
-<span style="color: #8b0000;">var</span> <span style="color: #8b008b;">orig_code</span> = <span style="color: #008b00;">"... JS code here"</span>;
-<span style="color: #8b0000;">var</span> <span style="color: #8b008b;">ast</span> = jsp.parse(orig_code); <span style="color: #00008b;">// </span><span style="color: #00008b;">parse code and get the initial AST
-</span>ast = pro.ast_mangle(ast); <span style="color: #00008b;">// </span><span style="color: #00008b;">get a new AST with mangled names
-</span>ast = pro.ast_squeeze(ast); <span style="color: #00008b;">// </span><span style="color: #00008b;">get an AST with compression optimizations
-</span><span style="color: #8b0000;">var</span> <span style="color: #8b008b;">final_code</span> = pro.gen_code(ast); <span style="color: #00008b;">// </span><span style="color: #00008b;">compressed code here
+<span style="color: #a020f0;">var</span> <span style="color: #b8860b;">orig_code</span> = <span style="color: #bc8f8f;">"... JS code here"</span>;
+<span style="color: #a020f0;">var</span> <span style="color: #b8860b;">ast</span> = jsp.parse(orig_code); <span style="color: #b22222;">// </span><span style="color: #b22222;">parse code and get the initial AST
+</span>ast = pro.ast_mangle(ast); <span style="color: #b22222;">// </span><span style="color: #b22222;">get a new AST with mangled names
+</span>ast = pro.ast_squeeze(ast); <span style="color: #b22222;">// </span><span style="color: #b22222;">get an AST with compression optimizations
+</span><span style="color: #a020f0;">var</span> <span style="color: #b8860b;">final_code</span> = pro.gen_code(ast); <span style="color: #b22222;">// </span><span style="color: #b22222;">compressed code here
</span></pre>
</li>
<li>
+<code>pro.ast_lift_variables(ast)</code> – merge and move <code>var</code> declarations to the
+scop of the scope; discard unused function arguments or variables; discard
+unused (named) inner functions. It also tries to merge assignments
+following the <code>var</code> declaration into it.
+
+<p>
+If your code is very hand-optimized concerning <code>var</code> declarations, this
+lifting variable declarations might actually increase size. For me it
+helps out. On jQuery it adds 865 bytes (243 after gzip). YMMV. Also
+note that (since it's not enabled by default) this operation isn't yet
+heavily tested (please report if you find issues!).
+</p>
+<p>
+Note that although it might increase the image size (on jQuery it gains
+865 bytes, 243 after gzip) it's technically more correct: in certain
+situations, dead code removal might drop variable declarations, which
+would not happen if the variables are lifted in advance.
+</p>
+<p>
+Here's an example of what it does:
+</p>
+</li>
+</ul>
+
+
+
+
+<pre class="src src-js"><span style="color: #a020f0;">function</span> <span style="color: #0000ff;">f</span>(<span style="color: #b8860b;">a</span>, <span style="color: #b8860b;">b</span>, <span style="color: #b8860b;">c</span>, <span style="color: #b8860b;">d</span>, <span style="color: #b8860b;">e</span>) {
+ <span style="color: #a020f0;">var</span> <span style="color: #b8860b;">q</span>;
+ <span style="color: #a020f0;">var</span> <span style="color: #b8860b;">w</span>;
+ w = 10;
+ q = 20;
+ <span style="color: #a020f0;">for</span> (<span style="color: #a020f0;">var</span> <span style="color: #b8860b;">i</span> = 1; i < 10; ++i) {
+ <span style="color: #a020f0;">var</span> <span style="color: #b8860b;">boo</span> = foo(a);
+ }
+ <span style="color: #a020f0;">for</span> (<span style="color: #a020f0;">var</span> <span style="color: #b8860b;">i</span> = 0; i < 1; ++i) {
+ <span style="color: #a020f0;">var</span> <span style="color: #b8860b;">boo</span> = bar(c);
+ }
+ <span style="color: #a020f0;">function</span> <span style="color: #0000ff;">foo</span>(){ ... }
+ <span style="color: #a020f0;">function</span> <span style="color: #0000ff;">bar</span>(){ ... }
+ <span style="color: #a020f0;">function</span> <span style="color: #0000ff;">baz</span>(){ ... }
+}
+
+<span style="color: #b22222;">// </span><span style="color: #b22222;">transforms into ==>
+</span>
+<span style="color: #a020f0;">function</span> <span style="color: #0000ff;">f</span>(<span style="color: #b8860b;">a</span>, <span style="color: #b8860b;">b</span>, <span style="color: #b8860b;">c</span>) {
+ <span style="color: #a020f0;">var</span> <span style="color: #b8860b;">i</span>, <span style="color: #b8860b;">boo</span>, <span style="color: #b8860b;">w</span> = 10, <span style="color: #b8860b;">q</span> = 20;
+ <span style="color: #a020f0;">for</span> (i = 1; i < 10; ++i) {
+ boo = foo(a);
+ }
+ <span style="color: #a020f0;">for</span> (i = 0; i < 1; ++i) {
+ boo = bar(c);
+ }
+ <span style="color: #a020f0;">function</span> <span style="color: #0000ff;">foo</span>() { ... }
+ <span style="color: #a020f0;">function</span> <span style="color: #0000ff;">bar</span>() { ... }
+}
+</pre>
+
+
+
+<ul>
+<li>
<code>pro.ast_mangle(ast, options)</code> – generates a new AST containing mangled
(compressed) variable and function names. It supports the following
options:
<p>
-(XXX: this is somewhat outdated. On the jQuery source code we beat Closure
-by 168 bytes (560 after gzip) and by many seconds.)
-</p>
-<p>
-There are a few popular JS minifiers nowadays – the two most well known
-being the GoogleClosure (GCL) compiler and the YUI compressor. For some
-reason they are both written in Java. I didn't really hope to beat any of
-them, but finally I did – UglifyJS compresses better than the YUI
-compressor, and safer than GoogleClosure.
-</p>
-<p>
-I tested it on two big libraries. <a href="http://www.dynarchlib.com/">DynarchLIB</a> is my own, and it's big enough
-to contain probably all the JavaScript tricks known to mankind. <a href="http://jquery.com/">jQuery</a> is
-definitely the most popular JavaScript library (to some people, it's a
-synonym to JavaScript itself).
-</p>
-<p>
-I cannot swear that there are no bugs in the generated codes, but they
-appear to work fine.
+Here are updated statistics. (I also updated my Google Closure and YUI
+installations).
</p>
<p>
-Compression results:
+We're still a lot better than YUI in terms of compression, though slightly
+slower. We're still a lot faster than Closure, and compression after gzip
+is comparable.
</p>
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
<caption></caption>
-<colgroup><col align="left" /><col align="right" /><col align="right" /><col align="left" /><col align="left" />
+<colgroup><col align="left" /><col align="left" /><col align="right" /><col align="left" /><col align="right" /><col align="left" /><col align="right" />
</colgroup>
<thead>
-<tr><th scope="col">Library</th><th scope="col">Orig. size</th><th scope="col">UglifyJS</th><th scope="col">YUI</th><th scope="col">GCL</th></tr>
+<tr><th scope="col">File</th><th scope="col">UglifyJS</th><th scope="col">UglifyJS+gzip</th><th scope="col">Closure</th><th scope="col">Closure+gzip</th><th scope="col">YUI</th><th scope="col">YUI+gzip</th></tr>
</thead>
<tbody>
-<tr><td>DynarchLIB</td><td>636896</td><td>241441</td><td>246452 (+5011)</td><td>240439 (-1002) (buggy)</td></tr>
-<tr><td>jQuery</td><td>163855</td><td>72006</td><td>79702 (+7696)</td><td>71858 (-148)</td></tr>
+<tr><td>jquery-1.6.2.js</td><td>91001 (0:01.59)</td><td>31896</td><td>90678 (0:07.40)</td><td>31979</td><td>101527 (0:01.82)</td><td>34646</td></tr>
+<tr><td>paper.js</td><td>142023 (0:01.65)</td><td>43334</td><td>134301 (0:07.42)</td><td>42495</td><td>173383 (0:01.58)</td><td>48785</td></tr>
+<tr><td>prototype.js</td><td>88544 (0:01.09)</td><td>26680</td><td>86955 (0:06.97)</td><td>26326</td><td>92130 (0:00.79)</td><td>28624</td></tr>
+<tr><td>thelib-full.js (DynarchLIB)</td><td>251939 (0:02.55)</td><td>72535</td><td>249911 (0:09.05)</td><td>72696</td><td>258869 (0:01.94)</td><td>76584</td></tr>
</tbody>
</table>
-<p>
-UglifyJS is the fastest to run. On my laptop UglifyJS takes 1.35s for
-DynarchLIB, while YUI takes 2.7s and GCL takes 6.5s.
-</p>
-<p>
-GoogleClosure does a lot of smart ass optimizations. I had to strive really
-hard to get close to it. It should be possible to even beat it, but then
-again, GCL has a gazillion lines of code and runs terribly slow, so I'm not
-sure it worths spending the effort to save a few bytes. Also, GCL doesn't
-cope with <code>eval()</code> or <code>with{}</code> – it just dumps a warning and proceeds to
-mangle names anyway; my DynarchLIB compiled with it is buggy because of
-this.
-</p>
-<p>
-UglifyJS consists of ~1100 lines of code for the tokenizer/parser, and ~1100
-lines for the compressor and code generator. That should make it very
-maintainable and easily extensible, so I would say it has a good place in
-this field and it's bound to become the de-facto standard JS minifier. And
-I shall rule the world. :-) Use it, and <b>spread the word</b>!
-</p>
</div>
</div>
<ul>
<li>
+Twitter: <a href="http://twitter.com/UglifyJS">@UglifyJS</a>
+</li>
+<li>
Project at GitHub: <a href="http://github.com/mishoo/UglifyJS">http://github.com/mishoo/UglifyJS</a>
</li>
<li>
<div id="postamble">
<p class="author"> Author: Mihai Bazon
</p>
-<p class="date"> Date: 2011-07-14 12:50:31 EEST</p>
+<p class="date"> Date: 2011-08-20 10:08:28 EEST</p>
<p class="creator">HTML generated by org-mode 7.01trans in emacs 23</p>
</div>
</div>
** <<Unsafe transformations>>
-UglifyJS tries its best to achieve great compression while leaving the
-semantics of the code intact. In general, if your code logic is broken by
-UglifyJS then it's a bug in UglifyJS and you should report it and I should
-fix it. :-)
-
-However, I opted to include the following potentially unsafe transformations
-as default behavior. Discussion is welcome, if you have ideas of how to
-handle this better, or any objections to these optimizations, please let me
-know.
+The following transformations can in theory break code, although they're
+probably safe in most practical cases. To enable them you need to pass the
+=--unsafe= flag.
*** Calls involving the global Array constructor
// etc.
#+END_SRC
+*** =obj.toString()= ==> =obj+“”=
+
** Install (NPM)
UglifyJS is now available through NPM --- =npm install uglify-js= should do
- =-v= or =--verbose= --- output some notes on STDERR (for now just how long
each operation takes).
-- =--extra= --- enable additional optimizations that have not yet been
- extensively tested. These might, or might not, break your code. If you
- find a bug using this option, please report a test case.
-
- =--unsafe= --- enable other additional optimizations that are known to be
unsafe in some contrived situations, but could still be generally useful.
For now only this:
HTML =<script>= tag you can use this option to prevent =</script= from
showing up in the output.
+- =--lift-vars= -- when you pass this, UglifyJS will apply the following
+ transformations (see the notes in API, =ast_lift_variables=):
+
+ - put all =var= declarations at the start of the scope
+ - make sure a variable is declared only once
+ - discard unused function arguments
+ - discard unused inner (named) functions
+ - finally, try to merge assignments into that one =var= declaration, if
+ possible.
+
*** API
To use the library from JavaScript, you'd do the following (example for
it doesn't find it. For most JS code you don't want that, but it's useful
if you want to strictly sanitize your code.
+- =pro.ast_lift_variables(ast)= -- merge and move =var= declarations to the
+ scop of the scope; discard unused function arguments or variables; discard
+ unused (named) inner functions. It also tries to merge assignments
+ following the =var= declaration into it.
+
+ If your code is very hand-optimized concerning =var= declarations, this
+ lifting variable declarations might actually increase size. For me it
+ helps out. On jQuery it adds 865 bytes (243 after gzip). YMMV. Also
+ note that (since it's not enabled by default) this operation isn't yet
+ heavily tested (please report if you find issues!).
+
+ Note that although it might increase the image size (on jQuery it gains
+ 865 bytes, 243 after gzip) it's technically more correct: in certain
+ situations, dead code removal might drop variable declarations, which
+ would not happen if the variables are lifted in advance.
+
+ Here's an example of what it does:
+
+#+BEGIN_SRC js
+function f(a, b, c, d, e) {
+ var q;
+ var w;
+ w = 10;
+ q = 20;
+ for (var i = 1; i < 10; ++i) {
+ var boo = foo(a);
+ }
+ for (var i = 0; i < 1; ++i) {
+ var boo = bar(c);
+ }
+ function foo(){ ... }
+ function bar(){ ... }
+ function baz(){ ... }
+}
+
+// transforms into ==>
+
+function f(a, b, c) {
+ var i, boo, w = 10, q = 20;
+ for (i = 1; i < 10; ++i) {
+ boo = foo(a);
+ }
+ for (i = 0; i < 1; ++i) {
+ boo = bar(c);
+ }
+ function foo() { ... }
+ function bar() { ... }
+}
+#+END_SRC
+
- =pro.ast_mangle(ast, options)= -- generates a new AST containing mangled
(compressed) variable and function names. It supports the following
options:
** Compression -- how good is it?
-(XXX: this is somewhat outdated. On the jQuery source code we beat Closure
-by 168 bytes (560 after gzip) and by many seconds.)
-
-There are a few popular JS minifiers nowadays -- the two most well known
-being the GoogleClosure (GCL) compiler and the YUI compressor. For some
-reason they are both written in Java. I didn't really hope to beat any of
-them, but finally I did -- UglifyJS compresses better than the YUI
-compressor, and safer than GoogleClosure.
-
-I tested it on two big libraries. [[http://www.dynarchlib.com/][DynarchLIB]] is my own, and it's big enough
-to contain probably all the JavaScript tricks known to mankind. [[http://jquery.com/][jQuery]] is
-definitely the most popular JavaScript library (to some people, it's a
-synonym to JavaScript itself).
-
-I cannot swear that there are no bugs in the generated codes, but they
-appear to work fine.
-
-Compression results:
-
-| Library | Orig. size | UglifyJS | YUI | GCL |
-|------------+------------+----------+----------------+------------------------|
-| DynarchLIB | 636896 | 241441 | 246452 (+5011) | 240439 (-1002) (buggy) |
-| jQuery | 163855 | 72006 | 79702 (+7696) | 71858 (-148) |
-
-UglifyJS is the fastest to run. On my laptop UglifyJS takes 1.35s for
-DynarchLIB, while YUI takes 2.7s and GCL takes 6.5s.
+Here are updated statistics. (I also updated my Google Closure and YUI
+installations).
-GoogleClosure does a lot of smart ass optimizations. I had to strive really
-hard to get close to it. It should be possible to even beat it, but then
-again, GCL has a gazillion lines of code and runs terribly slow, so I'm not
-sure it worths spending the effort to save a few bytes. Also, GCL doesn't
-cope with =eval()= or =with{}= -- it just dumps a warning and proceeds to
-mangle names anyway; my DynarchLIB compiled with it is buggy because of
-this.
+We're still a lot better than YUI in terms of compression, though slightly
+slower. We're still a lot faster than Closure, and compression after gzip
+is comparable.
-UglifyJS consists of ~1100 lines of code for the tokenizer/parser, and ~1100
-lines for the compressor and code generator. That should make it very
-maintainable and easily extensible, so I would say it has a good place in
-this field and it's bound to become the de-facto standard JS minifier. And
-I shall rule the world. :-) Use it, and *spread the word*!
+| File | UglifyJS | UglifyJS+gzip | Closure | Closure+gzip | YUI | YUI+gzip |
+|-----------------------------+------------------+---------------+------------------+--------------+------------------+----------|
+| jquery-1.6.2.js | 91001 (0:01.59) | 31896 | 90678 (0:07.40) | 31979 | 101527 (0:01.82) | 34646 |
+| paper.js | 142023 (0:01.65) | 43334 | 134301 (0:07.42) | 42495 | 173383 (0:01.58) | 48785 |
+| prototype.js | 88544 (0:01.09) | 26680 | 86955 (0:06.97) | 26326 | 92130 (0:00.79) | 28624 |
+| thelib-full.js (DynarchLIB) | 251939 (0:02.55) | 72535 | 249911 (0:09.05) | 72696 | 258869 (0:01.94) | 76584 |
** Bugs?
** Links
+- Twitter: [[http://twitter.com/UglifyJS][@UglifyJS]]
- Project at GitHub: [[http://github.com/mishoo/UglifyJS][http://github.com/mishoo/UglifyJS]]
- Google Group: [[http://groups.google.com/group/uglifyjs][http://groups.google.com/group/uglifyjs]]
- Common Lisp JS parser: [[http://marijn.haverbeke.nl/parse-js/][http://marijn.haverbeke.nl/parse-js/]]
unsafe: false,
reserved_names: null,
defines: { },
+ lift_vars: false,
codegen_options: {
ascii_only: false,
beautify: false,
case "--reserved-names":
options.reserved_names = args.shift().split(",");
break;
+ case "--lift-vars":
+ options.lift_vars = true;
+ break;
case "-d":
case "--define":
var defarg = args.shift();
}
try {
var ast = time_it("parse", function(){ return jsp.parse(code); });
+ if (options.lift_vars) {
+ ast = time_it("lift", function(){ return pro.ast_lift_variables(ast); });
+ }
if (options.mangle) ast = time_it("mangle", function(){
return pro.ast_mangle(ast, {
toplevel: options.mangle_toplevel,
sys.debug(ex.stack);
sys.debug(sys.inspect(ex));
sys.debug(JSON.stringify(ex));
+ process.exit(1);
}
};
--- /dev/null
+var jsp = require("./parse-js"),
+ pro = require("./process");
+
+var BY_TYPE = {};
+
+function HOP(obj, prop) {
+ return Object.prototype.hasOwnProperty.call(obj, prop);
+};
+
+function AST_Node(parent) {
+ this.parent = parent;
+};
+
+AST_Node.prototype.init = function(){};
+
+function DEFINE_NODE_CLASS(type, props, methods) {
+ var base = methods && methods.BASE || AST_Node;
+ if (!base) base = AST_Node;
+ function D(parent, data) {
+ base.apply(this, arguments);
+ if (props) props.forEach(function(name, i){
+ this["_" + name] = data[i];
+ });
+ this.init();
+ };
+ var P = D.prototype = new AST_Node;
+ P.node_type = function(){ return type };
+ if (props) props.forEach(function(name){
+ var propname = "_" + name;
+ P["set_" + name] = function(val) {
+ this[propname] = val;
+ return this;
+ };
+ P["get_" + name] = function() {
+ return this[propname];
+ };
+ });
+ if (type != null) BY_TYPE[type] = D;
+ if (methods) for (var i in methods) if (HOP(methods, i)) {
+ P[i] = methods[i];
+ }
+ return D;
+};
+
+var AST_String_Node = DEFINE_NODE_CLASS("string", ["value"]);
+var AST_Number_Node = DEFINE_NODE_CLASS("num", ["value"]);
+var AST_Name_Node = DEFINE_NODE_CLASS("name", ["value"]);
+
+var AST_Statlist_Node = DEFINE_NODE_CLASS(null, ["body"]);
+var AST_Root_Node = DEFINE_NODE_CLASS("toplevel", null, { BASE: AST_Statlist_Node });
+var AST_Block_Node = DEFINE_NODE_CLASS("block", null, { BASE: AST_Statlist_Node });
+var AST_Splice_Node = DEFINE_NODE_CLASS("splice", null, { BASE: AST_Statlist_Node });
+
+var AST_Var_Node = DEFINE_NODE_CLASS("var", ["definitions"]);
+var AST_Const_Node = DEFINE_NODE_CLASS("const", ["definitions"]);
+
+var AST_Try_Node = DEFINE_NODE_CLASS("try", ["body", "catch", "finally"]);
+var AST_Throw_Node = DEFINE_NODE_CLASS("throw", ["exception"]);
+
+var AST_New_Node = DEFINE_NODE_CLASS("new", ["constructor", "arguments"]);
+
+var AST_Switch_Node = DEFINE_NODE_CLASS("switch", ["expression", "branches"]);
+var AST_Switch_Branch_Node = DEFINE_NODE_CLASS(null, ["expression", "body"]);
+
+var AST_Break_Node = DEFINE_NODE_CLASS("break", ["label"]);
+var AST_Continue_Node = DEFINE_NODE_CLASS("continue", ["label"]);
+var AST_Assign_Node = DEFINE_NODE_CLASS("assign", ["operator", "lvalue", "rvalue"]);
+var AST_Dot_Node = DEFINE_NODE_CLASS("dot", ["expression", "name"]);
+var AST_Call_Node = DEFINE_NODE_CLASS("call", ["function", "arguments"]);
+
+var AST_Lambda_Node = DEFINE_NODE_CLASS(null, ["name", "arguments", "body"])
+var AST_Function_Node = DEFINE_NODE_CLASS("function", null, AST_Lambda_Node);
+var AST_Defun_Node = DEFINE_NODE_CLASS("defun", null, AST_Lambda_Node);
+
+var AST_If_Node = DEFINE_NODE_CLASS("if", ["condition", "then", "else"]);
"||"
]);
-var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\v\u200b"));
+var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\u000b\u200b"));
var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
case "r" : return "\r";
case "t" : return "\t";
case "b" : return "\b";
- case "v" : return "\v";
+ case "v" : return "\u000b";
case "f" : return "\f";
case "0" : return "\0";
case "x" : return String.fromCharCode(hex_bytes(2));
case "u" : return String.fromCharCode(hex_bytes(4));
+ case "\n": return "";
default : return ch;
}
};
};
function slice(a, start) {
- return Array.prototype.slice.call(a, start == null ? 0 : start);
+ return Array.prototype.slice.call(a, start || 0);
};
function characters(str) {
/* -----[ helper for AST traversal ]----- */
-function ast_walker(ast) {
+function ast_walker() {
function _vardefs(defs) {
return [ this[0], MAP(defs, function(def){
var a = [ def[0] ];
if (!newMangle) return name; // not found and no mangling requested
return s.set_mangle(name, s.next_mangled());
},
- define: function(name) {
- if (name != null)
- return this.names[name] = name;
+ references: function(name) {
+ return name && !this.parent || this.uses_with || this.uses_eval || this.refs[name];
+ },
+ define: function(name, type) {
+ if (name != null) {
+ if (type == "var" || !HOP(this.names, name))
+ this.names[name] = type || "var";
+ return name;
+ }
}
};
return ret;
};
- function define(name) {
- return current_scope.define(name);
+ function define(name, type) {
+ return current_scope.define(name, type);
};
function reference(name) {
function _lambda(name, args, body) {
var is_defun = this[0] == "defun";
- return [ this[0], is_defun ? define(name) : name, args, with_new_scope(function(){
- if (!is_defun) define(name);
- MAP(args, define);
+ return [ this[0], is_defun ? define(name, "defun") : name, args, with_new_scope(function(){
+ if (!is_defun) define(name, "lambda");
+ MAP(args, function(name){ define(name, "arg") });
return MAP(body, walk);
})];
};
+ function _vardefs(type) {
+ return function(defs) {
+ MAP(defs, function(d){
+ define(d[0], type);
+ if (d[1]) reference(d[0]);
+ });
+ };
+ };
+
return with_new_scope(function(){
// process AST
var ret = w.with_walkers({
"function": _lambda,
"defun": _lambda,
+ "label": function(name, stat) { define(name, "label") },
+ "break": function(label) { if (label) reference(label) },
+ "continue": function(label) { if (label) reference(label) },
"with": function(expr, block) {
for (var s = current_scope; s; s = s.parent)
s.uses_with = true;
},
- "var": function(defs) {
- MAP(defs, function(d){ define(d[0]) });
- },
- "const": function(defs) {
- MAP(defs, function(d){ define(d[0]) });
- },
+ "var": _vardefs("var"),
+ "const": _vardefs("const"),
"try": function(t, c, f) {
if (c != null) return [
this[0],
MAP(t, walk),
- [ define(c[0]), MAP(c[1], walk) ],
+ [ define(c[0], "catch"), MAP(c[1], walk) ],
f != null ? MAP(f, walk) : null
];
},
}
return ast;
},
+ "label": function(label, stat) { return [ this[0], get_mangled(label), walk(stat) ] },
+ "break": function(label) { if (label) return [ this[0], get_mangled(label) ] },
+ "continue": function(label) { if (label) return [ this[0], get_mangled(label) ] },
"var": _vardefs,
"const": _vardefs,
"name": function(name) {
}
function aborts(t) {
- if (t) {
- t = last_stat(t);
- if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
- return true;
+ if (t) switch (last_stat(t)[0]) {
+ case "return":
+ case "break":
+ case "continue":
+ case "throw":
+ return true;
}
};
var conditional = walk(fi[1]);
var e_body = statements.slice(i + 1);
- var e;
- if (e_body.length == 1) e = e_body[0];
- else e = [ "block", e_body ];
+ var e = e_body.length == 1 ? e_body[0] : [ "block", e_body ];
var ret = statements.slice(0, i).concat([ [
fi[0], // "if"
function redo_if_lambda(name, args, body) {
body = redo_if(body);
- return [ this[0], name, args.slice(), body ];
+ return [ this[0], name, args, body ];
};
function redo_if_block(statements) {
- var out = [ this[0] ];
- if (statements != null)
- out.push(redo_if(statements));
- return out;
+ return [ this[0], statements != null ? redo_if(statements) : null ];
};
return w.with_walkers({
});
};
+function for_side_effects(ast, handler) {
+ var w = ast_walker(), walk = w.walk;
+ var $stop = {}, $restart = {};
+ function stop() { throw $stop };
+ function restart() { throw $restart };
+ function found(){ return handler.call(this, this, w, stop, restart) };
+ function unary(op) {
+ if (op == "++" || op == "--")
+ return found.apply(this, arguments);
+ };
+ return w.with_walkers({
+ "try": found,
+ "throw": found,
+ "return": found,
+ "new": found,
+ "switch": found,
+ "break": found,
+ "continue": found,
+ "assign": found,
+ "call": found,
+ "if": found,
+ "for": found,
+ "for-in": found,
+ "while": found,
+ "do": found,
+ "return": found,
+ "unary-prefix": unary,
+ "unary-postfix": unary,
+ "defun": found
+ }, function(){
+ while (true) try {
+ walk(ast);
+ break;
+ } catch(ex) {
+ if (ex === $stop) break;
+ if (ex === $restart) continue;
+ throw ex;
+ }
+ });
+};
+
+function ast_lift_variables(ast) {
+ var w = ast_walker(), walk = w.walk, scope;
+ function do_body(body, env) {
+ var _scope = scope;
+ scope = env;
+ body = MAP(body, walk);
+ var hash = {}, names = MAP(env.names, function(type, name){
+ if (type != "var") return MAP.skip;
+ if (!env.references(name)) return MAP.skip;
+ hash[name] = true;
+ return [ name ];
+ });
+ if (names.length > 0) {
+ // looking for assignments to any of these variables.
+ // we can save considerable space by moving the definitions
+ // in the var declaration.
+ for_side_effects([ "block", body ], function(ast, walker, stop, restart) {
+ if (ast[0] == "assign"
+ && ast[1] === true
+ && ast[2][0] == "name"
+ && HOP(hash, ast[2][1])) {
+ // insert the definition into the var declaration
+ for (var i = names.length; --i >= 0;) {
+ if (names[i][0] == ast[2][1]) {
+ if (names[i][1]) // this name already defined, we must stop
+ stop();
+ names[i][1] = ast[3]; // definition
+ names.push(names.splice(i, 1)[0]);
+ break;
+ }
+ }
+ // remove this assignment from the AST.
+ var p = walker.parent();
+ if (p[0] == "seq") {
+ var a = p[2];
+ a.unshift(0, p.length);
+ p.splice.apply(p, a);
+ }
+ else if (p[0] == "stat") {
+ p.splice(0, p.length, "block"); // empty statement
+ }
+ else {
+ stop();
+ }
+ restart();
+ }
+ stop();
+ });
+ body.unshift([ "var", names ]);
+ }
+ scope = _scope;
+ return body;
+ };
+ function _vardefs(defs) {
+ var ret = null;
+ for (var i = defs.length; --i >= 0;) {
+ var d = defs[i];
+ if (!d[1]) continue;
+ d = [ "assign", true, [ "name", d[0] ], d[1] ];
+ if (ret == null) ret = d;
+ else ret = [ "seq", d, ret ];
+ }
+ if (ret == null) {
+ if (w.parent()[0] == "for-in")
+ return [ "name", defs[0][0] ];
+ return MAP.skip;
+ }
+ return [ "stat", ret ];
+ };
+ function _toplevel(body) {
+ return [ this[0], do_body(body, this.scope) ];
+ };
+ return w.with_walkers({
+ "function": function(name, args, body){
+ for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
+ args.pop();
+ if (!body.scope.references(name)) name = null;
+ return [ this[0], name, args, do_body(body, body.scope) ];
+ },
+ "defun": function(name, args, body){
+ if (!scope.references(name)) return MAP.skip;
+ for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
+ args.pop();
+ return [ this[0], name, args, do_body(body, body.scope) ];
+ },
+ "var": _vardefs,
+ "toplevel": _toplevel
+ }, function(){
+ return walk(ast_add_scope(ast));
+ });
+};
+
function ast_squeeze(ast, options) {
options = defaults(options, {
make_seqs : true,
function _lambda(name, args, body) {
var is_defun = this[0] == "defun";
body = with_scope(body.scope, function(){
- var ret = tighten(MAP(body, walk), "lambda");
- if (!is_defun && name && !HOP(scope.refs, name))
+ var ret = tighten(body, "lambda");
+ if (!is_defun && name && !scope.references(name))
name = null;
return ret;
});
return [ this[0], name, args, body ];
};
- // we get here for blocks that have been already transformed.
// this function does a few things:
// 1. discard useless blocks
// 2. join consecutive var declarations
// 4. transform consecutive statements using the comma operator
// 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
function tighten(statements, block_type) {
+ statements = MAP(statements, walk);
+
statements = statements.reduce(function(a, stat){
if (stat[0] == "block") {
if (stat[1]) {
if (options.dead_code) statements = (function(a, has_quit){
statements.forEach(function(st){
if (has_quit) {
- if (member(st[0], [ "function", "defun" , "var", "const" ])) {
+ if (st[0] == "function" || st[0] == "defun") {
+ a.push(st);
+ }
+ else if (st[0] == "var" || st[0] == "const") {
+ if (!options.no_warnings)
+ warn("Variables declared in unreachable code");
+ st[1] = MAP(st[1], function(def){
+ if (def[1] && !options.no_warnings)
+ warn_unreachable([ "assign", true, [ "name", def[0] ], def[1] ]);
+ return [ def[0] ];
+ });
a.push(st);
}
else if (!options.no_warnings)
prev = cur;
}
});
+ if (a.length >= 2
+ && a[a.length-2][0] == "stat"
+ && (a[a.length-1][0] == "return" || a[a.length-1][0] == "throw")
+ && a[a.length-1][1])
+ {
+ a.splice(a.length - 2, 2,
+ [ a[a.length-1][0],
+ [ "seq", a[a.length-2][1], a[a.length-1][1] ]]);
+ }
return a;
})([]);
- if (block_type == "lambda") statements = (function(i, a, stat){
- while (i < statements.length) {
- stat = statements[i++];
- if (stat[0] == "if" && !stat[3]) {
- if (stat[2][0] == "return" && stat[2][1] == null) {
- a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
- break;
- }
- var last = last_stat(stat[2]);
- if (last[0] == "return" && last[1] == null) {
- a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
- break;
- }
- }
- a.push(stat);
- }
- return a;
- })(0, []);
+ // this increases jQuery by 1K. Probably not such a good idea after all..
+ // part of this is done in prepare_ifs anyway.
+ // if (block_type == "lambda") statements = (function(i, a, stat){
+ // while (i < statements.length) {
+ // stat = statements[i++];
+ // if (stat[0] == "if" && !stat[3]) {
+ // if (stat[2][0] == "return" && stat[2][1] == null) {
+ // a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
+ // break;
+ // }
+ // var last = last_stat(stat[2]);
+ // if (last[0] == "return" && last[1] == null) {
+ // a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
+ // break;
+ // }
+ // }
+ // a.push(stat);
+ // }
+ // return a;
+ // })(0, []);
return statements;
};
});
};
- ast = prepare_ifs(ast);
- ast = ast_add_scope(ast);
-
return w.with_walkers({
"sub": function(expr, subscript) {
if (subscript[0] == "string") {
"if": make_if,
"toplevel": function(body) {
return [ "toplevel", with_scope(this.scope, function(){
- return tighten(MAP(body, walk));
+ return tighten(body);
}) ];
},
"switch": function(expr, body) {
var last = body.length - 1;
return [ "switch", walk(expr), MAP(body, function(branch, i){
- var block = tighten(MAP(branch[1], walk));
+ var block = tighten(branch[1]);
if (i == last && block.length > 0) {
var node = block[block.length - 1];
if (node[0] == "break" && !node[1])
"function": _lambda,
"defun": _lambda,
"block": function(body) {
- if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]);
+ if (body) return rmblock([ "block", tighten(body) ]);
},
"binary": function(op, left, right) {
return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
"try": function(t, c, f) {
return [
"try",
- tighten(MAP(t, walk)),
- c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null,
- f != null ? tighten(MAP(f, walk)) : null
+ tighten(t),
+ c != null ? [ c[0], tighten(c[1]) ] : null,
+ f != null ? tighten(f) : null
];
},
"unary-prefix": function(op, expr) {
case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
}
},
- "new": function(ctor, args) {
- if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) {
- if (args.length != 1) {
- return [ "array", args ];
- } else {
- return [ "call", [ "name", "Array" ], args ];
- }
- }
- },
- "call": function(expr, args) {
- if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
- return [ "array", args ];
- }
- },
"while": _do_while
}, function() {
- return walk(ast);
+ for (var i = 0; i < 2; ++i) {
+ ast = prepare_ifs(ast);
+ ast = ast_add_scope(ast);
+ ast = walk(ast);
+ }
+ return ast;
});
};
// we're the first in this "seq" and the
// parent is "stat", and so on. Messy stuff,
// but it worths the trouble.
- var a = slice($stack), self = a.pop(), p = a.pop();
+ var a = slice(w.stack()), self = a.pop(), p = a.pop();
while (p) {
if (p[0] == "stat") return true;
if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
return best_of(a);
};
- var generators = {
+ var w = ast_walker();
+ var make = w.walk;
+ return w.with_walkers({
"string": encode_string,
"num": make_num,
"name": make_name,
.join(newline + newline);
},
"splice": function(statements) {
- var parent = $stack[$stack.length - 2][0];
+ var parent = w.parent();
if (HOP(SPLICE_NEEDS_BRACKETS, parent)) {
// we need block brackets in this case
return make_block.apply(this, arguments);
// body in p[1][3] and type ("get" / "set") in p[2].
return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
}
- var key = p[0], val = make(p[1]);
+ var key = p[0], val = parenthesize(p[1], "seq");
if (options.quote_keys) {
key = encode_string(key);
} else if ((typeof key == "number" || !beautify && +key + "" == key)
"atom": function(name) {
return make_name(name);
}
- };
+ }, function(){ return make(ast) });
// The squeezer replaces "block"-s that contain only a single
// statement with the statement itself; technically, the AST
// IE croaks with "syntax error" on code like this:
// if (foo) do ... while(cond); else ...
// we need block brackets around do/while
- return make([ "block", [ th ]]);
+ return make_block([ th ]);
}
var b = th;
while (true) {
return add_spaces([ out, make_block(body) ]);
};
+ function must_has_semicolon(node) {
+ switch (node[0]) {
+ case "with":
+ case "while":
+ return empty(node[2]); // `with' or `while' with empty body?
+ case "for":
+ case "for-in":
+ return empty(node[4]); // `for' with empty body?
+ case "if":
+ if (empty(node[2]) && !node[3]) return true; // `if' with empty `then' and no `else'
+ if (node[3]) {
+ if (empty(node[3])) return true; // `else' present but empty
+ return must_has_semicolon(node[3]); // dive into the `else' branch
+ }
+ return must_has_semicolon(node[2]); // dive into the `then' branch
+ }
+ };
+
function make_block_statements(statements, noindent) {
for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
var stat = statements[i];
var code = make(stat);
if (code != ";") {
- if (!beautify && i == last) {
- if ((stat[0] == "while" && empty(stat[2])) ||
- (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) ||
- (stat[0] == "if" && empty(stat[2]) && !stat[3]) ||
- (stat[0] == "if" && stat[3] && empty(stat[3]))) {
- code = code.replace(/;*\s*$/, ";");
- } else {
- code = code.replace(/;+\s*$/, "");
- }
+ if (!beautify && i == last && !must_has_semicolon(stat)) {
+ code = code.replace(/;+\s*$/, "");
}
a.push(code);
}
return name;
};
- var $stack = [];
-
- function make(node) {
- var type = node[0];
- var gen = generators[type];
- if (!gen)
- throw new Error("Can't find generator for \"" + type + "\"");
- $stack.push(node);
- var ret = gen.apply(type, node.slice(1));
- $stack.pop();
- return ret;
- };
-
- return make(ast);
};
function split_lines(code, max_line_length) {
(function(){
MAP = function(a, f, o) {
- var ret = [];
- for (var i = 0; i < a.length; ++i) {
+ var ret = [], top = [], i;
+ function doit() {
var val = f.call(o, a[i], i);
- if (val instanceof AtTop) ret.unshift(val.v);
- else ret.push(val);
- }
- return ret;
+ if (val instanceof AtTop) {
+ val = val.v;
+ if (val instanceof Splice) {
+ top.push.apply(top, val.v);
+ } else {
+ top.push(val);
+ }
+ }
+ else if (val != skip) {
+ if (val instanceof Splice) {
+ ret.push.apply(ret, val.v);
+ } else {
+ ret.push(val);
+ }
+ }
+ };
+ if (a instanceof Array) for (i = 0; i < a.length; ++i) doit();
+ else for (i in a) if (HOP(a, i)) doit();
+ return top.concat(ret);
};
MAP.at_top = function(val) { return new AtTop(val) };
+ MAP.splice = function(val) { return new Splice(val) };
+ var skip = MAP.skip = {};
function AtTop(val) { this.v = val };
+ function Splice(val) { this.v = val };
})();
/* -----[ Exports ]----- */
exports.ast_walker = ast_walker;
exports.ast_mangle = ast_mangle;
exports.ast_squeeze = ast_squeeze;
+exports.ast_lift_variables = ast_lift_variables;
exports.gen_code = gen_code;
exports.ast_add_scope = ast_add_scope;
exports.set_logger = function(logger) { warn = logger };
pro = require("./process"),
slice = jsp.slice,
member = jsp.member,
+ curry = jsp.curry,
+ MAP = pro.MAP,
PRECEDENCE = jsp.PRECEDENCE,
OPERATORS = jsp.OPERATORS;
function ast_squeeze_more(ast) {
- var w = pro.ast_walker(), walk = w.walk;
+ var w = pro.ast_walker(), walk = w.walk, scope;
+ function with_scope(s, cont) {
+ var save = scope, ret;
+ scope = s;
+ ret = cont();
+ scope = save;
+ return ret;
+ };
+ function _lambda(name, args, body) {
+ return [ this[0], name, args, with_scope(body.scope, curry(MAP, body, walk)) ];
+ };
return w.with_walkers({
+ "toplevel": function(body) {
+ return [ this[0], with_scope(this.scope, curry(MAP, body, walk)) ];
+ },
+ "function": _lambda,
+ "defun": _lambda,
+ "new": function(ctor, args) {
+ if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) {
+ if (args.length != 1) {
+ return [ "array", args ];
+ } else {
+ return walk([ "call", [ "name", "Array" ], args ]);
+ }
+ }
+ },
"call": function(expr, args) {
if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
// foo.toString() ==> foo+""
return [ "binary", "+", expr[1], [ "string", "" ]];
}
+ if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
+ return [ "array", args ];
+ }
}
}, function() {
- return walk(ast);
+ return walk(pro.ast_add_scope(ast));
});
};
"url" : "http://mihai.bazon.net/blog"
},
- "version" : "1.0.6",
+ "version" : "1.0.7",
"main" : "./uglify-js.js",
-function mak(){for(;;);}function foo(){while(bar());}function bar(){return--x}var x=5
+function bar(){return--x}function foo(){while(bar());}function mak(){for(;;);}var x=5
-function y(a){return typeof a=="object"?a:null}function x(a){return typeof a=="object"?a:a===42?0:a*2}
+function x(a){return typeof a=="object"?a:a===42?0:a*2}function y(a){return typeof a=="object"?a:null}
-function f(){function b(){}a||b()}
+function f(){function b(){}if(a)return;b()}
function compress(code) {
var ast = jsp.parse(code);
ast = pro.ast_mangle(ast);
- ast = pro.ast_squeeze(ast, {no_warnings: true, extra: true});
+ ast = pro.ast_squeeze(ast, { no_warnings: true });
ast = pro.ast_squeeze_more(ast);
return pro.gen_code(ast);
};
--- /dev/null
+function foo(arg1, arg2, arg3, arg4, arg5, arg6) {
+ var a = 5;
+ {
+ var d = 10, mak = 20, buz = 30;
+ var q = buz * 2;
+ }
+ if (moo) {
+ var a, b, c;
+ }
+ for (var arg1 = 0, d = 20; arg1 < 10; ++arg1)
+ console.log(arg3);
+ for (var i in mak) {}
+ for (j in d) {}
+ var d;
+
+ function test() {
+
+ };
+
+ //test();
+
+ (function moo(first, second){
+ console.log(first);
+ })(1);
+
+ (function moo(first, second){
+ console.log(moo());
+ })(1);
+}
+
+
+var foo;
+var bar;
--- /dev/null
+#! /usr/bin/env node
+
+global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
+var fs = require("fs");
+var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js
+ jsp = uglify.parser,
+ pro = uglify.uglify;
+
+var code = fs.readFileSync("hoist.js", "utf8");
+var ast = jsp.parse(code);
+
+ast = pro.ast_lift_variables(ast);
+
+console.log(pro.gen_code(ast, {
+ beautify: true
+}));
+++ /dev/null
-info it worked if it ends with ok
-verbose cli [ 'node',
-verbose cli '/root/local/node/bin/npm',
-verbose cli 'install',
-verbose cli 'uglify-js' ]
-info using npm@1.0.15
-info using node@v0.4.10-pre
-verbose config file /root/.npmrc
-verbose config file /root/local/node/etc/npmrc
-silly testEngine { name: 'jade',
-silly testEngine description: 'Jade template engine',
-silly testEngine version: '0.13.0',
-silly testEngine author:
-silly testEngine { name: 'TJ Holowaychuk',
-silly testEngine email: 'tj@vision-media.ca' },
-silly testEngine repository:
-silly testEngine { type: 'git',
-silly testEngine url: 'git://github.com/visionmedia/jade.git' },
-silly testEngine main: './index.js',
-silly testEngine bin: { jade: './bin/jade' },
-silly testEngine devDependencies:
-silly testEngine { expresso: '0.6.4',
-silly testEngine 'coffee-script': '>= 0.0.1',
-silly testEngine sass: '>= 0.0.1',
-silly testEngine less: '>= 0.0.1',
-silly testEngine markdown: '>= 0.0.1',
-silly testEngine stylus: '>= 0.0.1' },
-silly testEngine scripts: { prepublish: 'npm prune' },
-silly testEngine engines: { node: '>= 0.1.98' },
-silly testEngine _npmJsonOpts:
-silly testEngine { file: '/var/www/kiwi/KiwiIRC/node/node_modules/jade/package.json',
-silly testEngine wscript: false,
-silly testEngine contributors: false,
-silly testEngine serverjs: false },
-silly testEngine _id: 'jade@0.13.0',
-silly testEngine dependencies: {},
-silly testEngine _engineSupported: true,
-silly testEngine _npmVersion: '1.0.15',
-silly testEngine _nodeVersion: 'v0.4.10-pre',
-silly testEngine _defaultsLoaded: true }
-verbose caching /var/www/kiwi/KiwiIRC/node/node_modules/jade/package.json
-verbose loadDefaults jade@0.13.0
-silly testEngine { name: 'node-static',
-silly testEngine description: 'simple, compliant file streaming module for node',
-silly testEngine url: 'http://github.com/cloudhead/node-static',
-silly testEngine keywords: [ 'http', 'static', 'file', 'server' ],
-silly testEngine author:
-silly testEngine { name: 'Alexis Sellier',
-silly testEngine email: 'self@cloudhead.net' },
-silly testEngine contributors: [],
-silly testEngine licenses: [ 'MIT' ],
-silly testEngine dependencies: {},
-silly testEngine lib: 'lib',
-silly testEngine main: './lib/node-static',
-silly testEngine version: '0.5.6',
-silly testEngine directories: { test: './test' },
-silly testEngine engines: { node: '>= 0.4.1' },
-silly testEngine _npmJsonOpts:
-silly testEngine { file: '/var/www/kiwi/KiwiIRC/node/node_modules/node-static/package.json',
-silly testEngine wscript: false,
-silly testEngine contributors: false,
-silly testEngine serverjs: false },
-silly testEngine _id: 'node-static@0.5.6',
-silly testEngine devDependencies: {},
-silly testEngine _engineSupported: true,
-silly testEngine _npmVersion: '1.0.15',
-silly testEngine _nodeVersion: 'v0.4.10-pre',
-silly testEngine _defaultsLoaded: true }
-verbose caching /var/www/kiwi/KiwiIRC/node/node_modules/node-static/package.json
-verbose loadDefaults node-static@0.5.6
-silly testEngine { name: 'socket.io',
-silly testEngine version: '0.7.7',
-silly testEngine description: 'Real-time apps made cross-browser & easy with a WebSocket-like API',
-silly testEngine homepage: 'http://socket.io',
-silly testEngine keywords:
-silly testEngine [ 'websocket',
-silly testEngine 'socket',
-silly testEngine 'realtime',
-silly testEngine 'socket.io',
-silly testEngine 'comet',
-silly testEngine 'ajax' ],
-silly testEngine author:
-silly testEngine { name: 'Guillermo Rauch',
-silly testEngine email: 'guillermo@learnboost.com' },
-silly testEngine contributors:
-silly testEngine [ { name: 'Guillermo Rauch', email: 'rauchg@gmail.com' },
-silly testEngine { name: 'Arnout Kazemier',
-silly testEngine email: 'info@3rd-eden.com' },
-silly testEngine { name: 'Vladimir Dronnikov',
-silly testEngine email: 'dronnikov@gmail.com' } ],
-silly testEngine repository:
-silly testEngine { type: 'git',
-silly testEngine url: 'git://github.com/LearnBoost/Socket.IO-node.git' },
-silly testEngine dependencies:
-silly testEngine { 'socket.io-client': '0.7.4',
-silly testEngine policyfile: '0.0.3',
-silly testEngine redis: '0.6.0' },
-silly testEngine devDependencies: { expresso: '0.7.7', should: '0.0.4' },
-silly testEngine main: 'index',
-silly testEngine engines: { node: '>= 0.4.0' },
-silly testEngine _npmJsonOpts:
-silly testEngine { file: '/var/www/kiwi/KiwiIRC/node/node_modules/socket.io/package.json',
-silly testEngine wscript: false,
-silly testEngine contributors: false,
-silly testEngine serverjs: false },
-silly testEngine _id: 'socket.io@0.7.7',
-silly testEngine _engineSupported: true,
-silly testEngine _npmVersion: '1.0.15',
-silly testEngine _nodeVersion: 'v0.4.10-pre',
-silly testEngine _defaultsLoaded: true }
-verbose caching /var/www/kiwi/KiwiIRC/node/node_modules/socket.io/package.json
-verbose loadDefaults socket.io@0.7.7
-silly testEngine { name: 'socket.io-client',
-silly testEngine description: 'Socket.IO client for the browser and node.js',
-silly testEngine version: '0.7.4',
-silly testEngine main: './lib/io.js',
-silly testEngine browserify: './dist/socket.io.js',
-silly testEngine homepage: 'http://socket.io',
-silly testEngine keywords:
-silly testEngine [ 'websocket',
-silly testEngine 'socket',
-silly testEngine 'realtime',
-silly testEngine 'socket.io',
-silly testEngine 'comet',
-silly testEngine 'ajax' ],
-silly testEngine author:
-silly testEngine { name: 'Guillermo Rauch',
-silly testEngine email: 'guillermo@learnboost.com' },
-silly testEngine contributors:
-silly testEngine [ { name: 'Guillermo Rauch', email: 'rauchg@gmail.com' },
-silly testEngine { name: 'Arnout Kazemier',
-silly testEngine email: 'info@3rd-eden.com' },
-silly testEngine { name: 'Vladimir Dronnikov',
-silly testEngine email: 'dronnikov@gmail.com' } ],
-silly testEngine repository:
-silly testEngine { type: 'git',
-silly testEngine url: 'git://github.com/LearnBoost/Socket.IO.git' },
-silly testEngine dependencies: { 'uglify-js': '1.0.3' },
-silly testEngine devDependencies:
-silly testEngine { expresso: '0.7.7',
-silly testEngine express: '2.3.11',
-silly testEngine jade: '0.12.1',
-silly testEngine stylus: '0.13.3',
-silly testEngine 'socket.io': '0.7.7',
-silly testEngine 'socket.io-client': '0.7.4' },
-silly testEngine engines: { node: '>= 0.4.0' },
-silly testEngine _npmJsonOpts:
-silly testEngine { file: '/var/www/kiwi/KiwiIRC/node/node_modules/socket.io-client/package.json',
-silly testEngine wscript: false,
-silly testEngine contributors: false,
-silly testEngine serverjs: false },
-silly testEngine _id: 'socket.io-client@0.7.4',
-silly testEngine _engineSupported: true,
-silly testEngine _npmVersion: '1.0.15',
-silly testEngine _nodeVersion: 'v0.4.10-pre',
-silly testEngine _defaultsLoaded: true }
-verbose caching /var/www/kiwi/KiwiIRC/node/node_modules/socket.io-client/package.json
-verbose loadDefaults socket.io-client@0.7.4
-verbose into /var/www/kiwi/KiwiIRC/node [ 'uglify-js' ]
-verbose cache add [ 'uglify-js', null ]
-info addNamed [ 'uglify-js', '' ]
-verbose addNamed [ null, '' ]
-verbose GET uglify-js
-verbose raw, before any munging uglify-js
-verbose url resolving [ 'http://registry.npmjs.org/', './uglify-js' ]
-verbose url resolved http://registry.npmjs.org/uglify-js
-verbose url parsed { protocol: 'http:',
-verbose url parsed slashes: true,
-verbose url parsed host: 'registry.npmjs.org',
-verbose url parsed hostname: 'registry.npmjs.org',
-verbose url parsed href: 'http://registry.npmjs.org/uglify-js',
-verbose url parsed pathname: '/uglify-js' }
-verbose response http://registry.npmjs.org/uglify-js
-silly chunk {"_id":"uglify-js","_rev":"26-8064181b8cb5259cdda963e8c227c8d7","name":"uglify-js","dist-tags":{"latest":"1.0.6"},"versions":{"0.0.1":{"name":"uglify-js","author":{"name":"Mihai Bazon - http://github.com/mishoo"},"version":"0.0.1","main":"index","bin":{"uglifyjs":"./bin/uglifyjs"},"_id":"uglify-js@0.0.1","engines":{"node":"*"},"_nodeSupported":true,"_npmVersion":"0.2.7-2","_nodeVersion":"v0.2.4","dist":{"tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-0.0.1.tgz"},"directories":{}},"0.0.2":{"name":"uglify-js","author":{"name":"Mihai Bazon - http://github.com/mishoo"},"version":"0.0.2","main":"index","bin":{"uglifyjs":"./bin/uglifyjs"},"_id":"uglify-js@0.0.2","engines":{"node":"*"},"_nodeSupported":true,"_npmVersion":"0.2.12-1","_nodeVersion":"v0.3.5-pre","dist":{"shasum":"baaf5c2223440d31f008bd248aaa728e8c771a8a","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-0.0.2.tgz"},"directories":{}},"0.0.3":{"name":"uglify-js","author":{"name":"Mihai Bazon - http://github.com/mishoo"},"version":"0.0.3","main":"index","bin":{"uglifyjs":"./bin/uglifyjs"},"_id":"uglify-js@0.0.3","engines":{"node":"*"},"_nodeSupported":true,"_npmVersion":"0.2.12-1","_nodeVersion":"v0.3.5-pre","dist":{"shasum":"04e48708cb7175fba8b23aba7596e39c849ccfab","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-0.0.3.tgz"},"directories":{}},"0.0.4":{"name":"uglify-js",
-silly chunk "author":{"name":"Mihai Bazon - http://github.com/mishoo"},"version":"0.0.4","main":"index","bin":{"uglifyjs":"./bin/uglifyjs"},"_id":"uglify-js@0.0.4","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"0.2.17","_nodeVersion":"v0.3.8-pre","directories":{"lib":"./lib","bin":"./bin"},"files":[""],"_defaultsLoaded":true,"dist":{"shasum":"48b2d19b65c284a82c4d6ccab6ed141b8e313a72","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-0.0.4.tgz"}},"0.0.5":{"name":"uglify-js","author":{"name":"Mihai Bazon - http://github.com/mishoo"},"version":"0.0.5","main":"index.js","bin":{"uglifyjs":"./bin/uglifyjs"},"_id":"uglify-js@0.0.5","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"0.3.3","_nodeVersion":"v0.4.1","directories":{"lib":"./lib","bin":"./bin"},"files":[""],"_defaultsLoaded":true,"dist":{"shasum":"c40d18e51784a230477bb0354fa415ec361dba5e","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-0.0.5.tgz"}},"1.0.1":{"name":"uglify-js","author":{"name":"Mihai Bazon","email":"mihai.bazon@gmail.com","url":"http://mihai.bazon.net/blog"},"version":"1.0.1","main":"./uglify-js.js","bin":{"uglifyjs":"./bin/uglifyjs"},"repository":{"type":"git","url":"git@github.com:mishoo/UglifyJS.git"},"dependencies":{},"devDependencies":{},"_id":"uglify-js@1.0.1","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"1.0.1rc5","_nodeVersion":"
-silly chunk v0.5.0-pre","_defaultsLoaded":true,"dist":{"shasum":"b34b3220e7d634401f388c8bd69e9663cec6ca94","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.1.tgz"},"directories":{}},"1.0.2":{"name":"uglify-js","author":{"name":"Mihai Bazon","email":"mihai.bazon@gmail.com","url":"http://mihai.bazon.net/blog"},"version":"1.0.2","main":"./uglify-js.js","bin":{"uglifyjs":"./bin/uglifyjs"},"repository":{"type":"git","url":"git@github.com:mishoo/UglifyJS.git"},"dependencies":{},"devDependencies":{},"_id":"uglify-js@1.0.2","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"1.0.1rc5","_nodeVersion":"v0.5.0-pre","_defaultsLoaded":true,"dist":{"shasum":"28494cc77c26042d4065d73736391d78417d680a","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.2.tgz"},"directories":{}},"1.0.3":{"name":"uglify-js","author":{"name":"Mihai Bazon","email":"mihai.bazon@gmail.com","url":"http://mihai.bazon.net/blog"},"version":"1.0.3","main":"./uglify-js.js","bin":{"uglifyjs":"./bin/uglifyjs"},"repository":{"type":"git","url":"git@github.com:mishoo/UglifyJS.git"},"dependencies":{},"devDependencies":{},"_id":"uglify-js@1.0.3","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"1.0.1rc5","_nodeVersion":"v0.5.0-pre","_defaultsLoaded":true,"dist":{"shasum":"73c4f09bcec47ec5e8669cb37c11b95b7014f945","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-
-silly chunk 1.0.3.tgz"},"directories":{}},"1.0.4":{"name":"uglify-js","author":{"name":"Mihai Bazon","email":"mihai.bazon@gmail.com","url":"http://mihai.bazon.net/blog"},"version":"1.0.4","main":"./uglify-js.js","bin":{"uglifyjs":"./bin/uglifyjs"},"repository":{"type":"git","url":"git@github.com:mishoo/UglifyJS.git"},"dependencies":{},"devDependencies":{},"_id":"uglify-js@1.0.4","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"1.0.1rc5","_nodeVersion":"v0.5.0-pre","_defaultsLoaded":true,"dist":{"shasum":"7512dbbfca85e749683800c65407e55491700778","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.4.tgz"},"directories":{}},"1.0.5":{"name":"uglify-js","author":{"name":"Mihai Bazon","email":"mihai.bazon@gmail.com","url":"http://mihai.bazon.net/blog"},"version":"1.0.5","main":"./uglify-js.js","bin":{"uglifyjs":"./bin/uglifyjs"},"repository":{"type":"git","url":"git@github.com:mishoo/UglifyJS.git"},"dependencies":{},"devDependencies":{},"_id":"uglify-js@1.0.5","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"1.0.1rc5","_nodeVersion":"v0.5.0-pre","_defaultsLoaded":true,"dist":{"shasum":"25679bdcff52f9500774a644cef3129b8ddb5cf2","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.5.tgz"},"directories":{}},"1.0.6":{"name":"uglify-js","author":{"name":"Mihai Bazon","email":"mihai.bazon@gmail.com","url":"http://mihai.bazon.net/blo
-silly chunk g"},"version":"1.0.6","main":"./uglify-js.js","bin":{"uglifyjs":"./bin/uglifyjs"},"repository":{"type":"git","url":"git@github.com:mishoo/UglifyJS.git"},"dependencies":{},"devDependencies":{},"_id":"uglify-js@1.0.6","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"1.0.1rc5","_nodeVersion":"v0.5.0-pre","_defaultsLoaded":true,"dist":{"shasum":"f0d3aafd463f26a437b9ebc19f4947ab7e8078aa","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.6.tgz"},"directories":{}}},"maintainers":[{"name":"caires","email":"cairesvs@gmail.com"},{"name":"mape","email":"mape@mape.me"},{"name":"mishoo","email":"mihai.bazon@gmail.com"}],"author":{"name":"Mihai Bazon","email":"mihai.bazon@gmail.com","url":"http://mihai.bazon.net/blog"},"time":{"0.0.1":"2011-01-05T17:56:48.593Z","0.0.2":"2011-01-09T13:47:55.729Z","0.0.3":"2011-01-25T05:53:18.848Z","0.0.4":"2011-02-05T13:28:37.926Z","0.0.5":"2011-02-20T16:37:04.786Z","1.0.1":"2011-04-03T22:03:32.396Z","1.0.2":"2011-05-19T16:13:13.281Z","1.0.3":"2011-06-27T16:52:42.404Z","1.0.4":"2011-07-01T10:26:41.824Z","1.0.5":"2011-07-14T09:54:00.537Z","1.0.6":"2011-07-14T20:36:30.484Z"},"repository":{"type":"git","url":"git@github.com:mishoo/UglifyJS.git"}}
-silly get cb [ 200,
-silly get cb { vary: 'Accept',
-silly get cb server: 'CouchDB/1.1.0 (Erlang OTP/R14B03)',
-silly get cb etag: '"2CZS2DKWM45Z3WMB02DQSV95J"',
-silly get cb date: 'Sat, 23 Jul 2011 12:25:08 GMT',
-silly get cb 'content-type': 'application/json',
-silly get cb 'content-length': '6718' } ]
-verbose GET uglify-js/1.0.6
-verbose raw, before any munging uglify-js/1.0.6
-verbose url resolving [ 'http://registry.npmjs.org/', './uglify-js/1.0.6' ]
-verbose url resolved http://registry.npmjs.org/uglify-js/1.0.6
-verbose url parsed { protocol: 'http:',
-verbose url parsed slashes: true,
-verbose url parsed host: 'registry.npmjs.org',
-verbose url parsed hostname: 'registry.npmjs.org',
-verbose url parsed href: 'http://registry.npmjs.org/uglify-js/1.0.6',
-verbose url parsed pathname: '/uglify-js/1.0.6' }
-verbose response http://registry.npmjs.org/uglify-js/1.0.6
-silly chunk {"name":"uglify-js","author":{"name":"Mihai Bazon","email":"mihai.bazon@gmail.com","url":"http://mihai.bazon.net/blog"},"version":"1.0.6","main":"./uglify-js.js","bin":{"uglifyjs":"./bin/uglifyjs"},"repository":{"type":"git","url":"git@github.com:mishoo/UglifyJS.git"},"dependencies":{},"devDependencies":{},"_id":"uglify-js@1.0.6","engines":{"node":"*"},"_engineSupported":true,"_npmVersion":"1.0.1rc5","_nodeVersion":"v0.5.0-pre","_defaultsLoaded":true,"dist":{"shasum":"f0d3aafd463f26a437b9ebc19f4947ab7e8078aa","tarball":"http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.6.tgz"},"directories":{}}
-silly get cb [ 200,
-silly get cb { vary: 'Accept',
-silly get cb server: 'CouchDB/1.1.0 (Erlang OTP/R14B03)',
-silly get cb etag: '"2CZS2DKWM45Z3WMB02DQSV95J"',
-silly get cb date: 'Sat, 23 Jul 2011 12:25:09 GMT',
-silly get cb 'content-type': 'application/json',
-silly get cb 'content-length': '603' } ]
-verbose mkdir done: /root/.npm/uglify-js/1.0.6 755
-verbose bin dist [ '0.4-ares1.7.4-ev4.4-openssl0.9.8o-v83.1.8.25-linux-2.6.35-30-generic',
-verbose bin dist { shasum: 'f0d3aafd463f26a437b9ebc19f4947ab7e8078aa',
-verbose bin dist tarball: 'http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.6.tgz' } ]
-verbose addRemoteTarball [ 'http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.6.tgz',
-verbose addRemoteTarball 'f0d3aafd463f26a437b9ebc19f4947ab7e8078aa' ]
-verbose mkdir done: /tmp/npm-1311423850738/1311423850738-0.8425169289112091 755
-info fetch http://registry.npmjs.org/uglify-js/-/uglify-js-1.0.6.tgz
-verbose fetch to /tmp/npm-1311423850738/1311423850738-0.8425169289112091/tmp.tgz
-silly updated sha bytes 40960
-silly updated sha bytes 12084
-info shasum f0d3aafd463f26a437b9ebc19f4947ab7e8078aa
-info shasum /tmp/npm-1311423850738/1311423850738-0.8425169289112091/tmp.tgz
-verbose mkdir done: /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm 755
-verbose unpack_ uid, gid [ 1000, 1000 ]
-verbose unpackTarget /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/package
-silly gunzTarPerm modes [ '755', '644' ]
-verbose success gzip "--decompress" "--stdout" "/tmp/npm-1311423850738/1311423850738-0.8425169289112091/tmp.tgz"
-verbose success tar "-mvxpf" "-" "--no-same-owner" "-C" "/tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm"
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/uglify-js.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/docstyle.css
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/package.json
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/README.html
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/README.org
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/.gitignore
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/bin/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/bin/uglifyjs
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/tmp/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/tmp/instrument2.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/tmp/instrument.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/lib/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/lib/parse-js.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/lib/squeeze-more.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/lib/process.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/testparser.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/beautify.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/scripts.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue68.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue10.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue30.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue21.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue13.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue28.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue50.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/array3.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/concatstring.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue17.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue11.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/assignment.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/var.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/if.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/empty-blocks.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue16.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue53.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/with.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/ifreturn2.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue14.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue48.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/mangle.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/array1.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue20.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/array4.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue25.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue54.1.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/const.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/strict-equals.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue27.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue69.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/forstatement.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue29.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/array2.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue9.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/ifreturn.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue34.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/expected/issue4.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue68.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue10.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue30.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue21.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue13.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue28.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue50.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/array3.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/concatstring.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue17.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue11.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/assignment.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/var.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/if.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/empty-blocks.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue16.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue53.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/with.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/ifreturn2.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue14.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue48.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/mangle.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/array1.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue20.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/array4.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue25.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue54.1.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/const.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/strict-equals.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue27.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue69.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/forstatement.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue29.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/array2.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue9.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/ifreturn.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue34.js
-silly asyncMap in gTP /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package/test/unit/compress/test/issue4.js
-verbose gunzed /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package
-verbose rm'ed /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/package
-verbose renamed [ '/tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/___package.npm/package',
-verbose renamed '/tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/package' ]
-silly testEngine { name: 'uglify-js',
-silly testEngine author:
-silly testEngine { name: 'Mihai Bazon',
-silly testEngine email: 'mihai.bazon@gmail.com',
-silly testEngine url: 'http://mihai.bazon.net/blog' },
-silly testEngine version: '1.0.6',
-silly testEngine main: './uglify-js.js',
-silly testEngine bin: { uglifyjs: './bin/uglifyjs' },
-silly testEngine repository:
-silly testEngine { type: 'git',
-silly testEngine url: 'git@github.com:mishoo/UglifyJS.git' },
-silly testEngine _npmJsonOpts:
-silly testEngine { file: '/tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/package/package.json',
-silly testEngine wscript: false,
-silly testEngine contributors: false,
-silly testEngine serverjs: false },
-silly testEngine _id: 'uglify-js@1.0.6',
-silly testEngine dependencies: {},
-silly testEngine devDependencies: {},
-silly testEngine engines: { node: '*' },
-silly testEngine _engineSupported: true,
-silly testEngine _npmVersion: '1.0.15',
-silly testEngine _nodeVersion: 'v0.4.10-pre',
-silly testEngine _defaultsLoaded: true,
-silly testEngine path: '/tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/package' }
-verbose caching /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/package/package.json
-verbose loadDefaults uglify-js@1.0.6
-verbose tarball contents [ 'package' ]
-verbose from cache /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/package/package.json
-verbose pack /tmp/npm-1311423850738/1311423850738-0.8425169289112091/contents/package /root/.npm/uglify-js/1.0.6/package.tgz
-verbose mkdir'ed /root/.npm/uglify-js/1.0.6
-verbose tar about to write tar and gzip it.
-silly tar args [ '-cvf',
-silly tar args '-',
-silly tar args 'package/uglify-js.js',
-silly tar args 'package/docstyle.css',
-silly tar args 'package/package.json',
-silly tar args 'package/README.html',
-silly tar args 'package/README.org',
-silly tar args 'package/.gitignore',
-silly tar args 'package/bin/uglifyjs',
-silly tar args 'package/tmp/instrument2.js',
-silly tar args 'package/tmp/instrument.js',
-silly tar args 'package/lib/parse-js.js',
-silly tar args 'package/lib/squeeze-more.js',
-silly tar args 'package/lib/process.js',
-silly tar args 'package/test/testparser.js',
-silly tar args 'package/test/beautify.js',
-silly tar args 'package/test/unit/scripts.js',
-silly tar args 'package/test/unit/compress/expected/issue68.js',
-silly tar args 'package/test/unit/compress/expected/issue10.js',
-silly tar args 'package/test/unit/compress/expected/issue30.js',
-silly tar args 'package/test/unit/compress/expected/issue21.js',
-silly tar args 'package/test/unit/compress/expected/issue13.js',
-silly tar args 'package/test/unit/compress/expected/issue28.js',
-silly tar args 'package/test/unit/compress/expected/issue50.js',
-silly tar args 'package/test/unit/compress/expected/array3.js',
-silly tar args 'package/test/unit/compress/expected/concatstring.js',
-silly tar args 'package/test/unit/compress/expected/issue17.js',
-silly tar args 'package/test/unit/compress/expected/issue11.js',
-silly tar args 'package/test/unit/compress/expected/assignment.js',
-silly tar args 'package/test/unit/compress/expected/var.js',
-silly tar args 'package/test/unit/compress/expected/if.js',
-silly tar args 'package/test/unit/compress/expected/empty-blocks.js',
-silly tar args 'package/test/unit/compress/expected/issue16.js',
-silly tar args 'package/test/unit/compress/expected/issue53.js',
-silly tar args 'package/test/unit/compress/expected/with.js',
-silly tar args 'package/test/unit/compress/expected/ifreturn2.js',
-silly tar args 'package/test/unit/compress/expected/issue14.js',
-silly tar args 'package/test/unit/compress/expected/issue48.js',
-silly tar args 'package/test/unit/compress/expected/mangle.js',
-silly tar args 'package/test/unit/compress/expected/array1.js',
-silly tar args 'package/test/unit/compress/expected/issue20.js',
-silly tar args 'package/test/unit/compress/expected/array4.js',
-silly tar args 'package/test/unit/compress/expected/issue25.js',
-silly tar args 'package/test/unit/compress/expected/issue54.1.js',
-silly tar args 'package/test/unit/compress/expected/const.js',
-silly tar args 'package/test/unit/compress/expected/strict-equals.js',
-silly tar args 'package/test/unit/compress/expected/issue27.js',
-silly tar args 'package/test/unit/compress/expected/issue69.js',
-silly tar args 'package/test/unit/compress/expected/forstatement.js',
-silly tar args 'package/test/unit/compress/expected/issue29.js',
-silly tar args 'package/test/unit/compress/expected/array2.js',
-silly tar args 'package/test/unit/compress/expected/issue9.js',
-silly tar args 'package/test/unit/compress/expected/ifreturn.js',
-silly tar args 'package/test/unit/compress/expected/issue34.js',
-silly tar args 'package/test/unit/compress/expected/issue4.js',
-silly tar args 'package/test/unit/compress/test/issue68.js',
-silly tar args 'package/test/unit/compress/test/issue10.js',
-silly tar args 'package/test/unit/compress/test/issue30.js',
-silly tar args 'package/test/unit/compress/test/issue21.js',
-silly tar args 'package/test/unit/compress/test/issue13.js',
-silly tar args 'package/test/unit/compress/test/issue28.js',
-silly tar args 'package/test/unit/compress/test/issue50.js',
-silly tar args 'package/test/unit/compress/test/array3.js',
-silly tar args 'package/test/unit/compress/test/concatstring.js',
-silly tar args 'package/test/unit/compress/test/issue17.js',
-silly tar args 'package/test/unit/compress/test/issue11.js',
-silly tar args 'package/test/unit/compress/test/assignment.js',
-silly tar args 'package/test/unit/compress/test/var.js',
-silly tar args 'package/test/unit/compress/test/if.js',
-silly tar args 'package/test/unit/compress/test/empty-blocks.js',
-silly tar args 'package/test/unit/compress/test/issue16.js',
-silly tar args 'package/test/unit/compress/test/issue53.js',
-silly tar args 'package/test/unit/compress/test/with.js',
-silly tar args 'package/test/unit/compress/test/ifreturn2.js',
-silly tar args 'package/test/unit/compress/test/issue14.js',
-silly tar args 'package/test/unit/compress/test/issue48.js',
-silly tar args 'package/test/unit/compress/test/mangle.js',
-silly tar args 'package/test/unit/compress/test/array1.js',
-silly tar args 'package/test/unit/compress/test/issue20.js',
-silly tar args 'package/test/unit/compress/test/array4.js',
-silly tar args 'package/test/unit/compress/test/issue25.js',
-silly tar args 'package/test/unit/compress/test/issue54.1.js',
-silly tar args 'package/test/unit/compress/test/const.js',
-silly tar args 'package/test/unit/compress/test/strict-equals.js',
-silly tar args 'package/test/unit/compress/test/issue27.js',
-silly tar args 'package/test/unit/compress/test/issue69.js',
-silly tar args 'package/test/unit/compress/test/forstatement.js',
-silly tar args 'package/test/unit/compress/test/issue29.js',
-silly tar args 'package/test/unit/compress/test/array2.js',
-silly tar args 'package/test/unit/compress/test/issue9.js',
-silly tar args 'package/test/unit/compress/test/ifreturn.js',
-silly tar args 'package/test/unit/compress/test/issue34.js',
-silly tar args 'package/test/unit/compress/test/issue4.js' ]
-verbose success tar -cvf - <file list elided>
-verbose success gzip "--stdout"
-verbose mkdir done: /root/.npm/uglify-js/1.0.6/___package.npm 755
-verbose unpack_ uid, gid [ 1000, 1000 ]
-verbose unpackTarget /root/.npm/uglify-js/1.0.6/package
-silly gunzTarPerm modes [ '755', '644' ]
-verbose success gzip "--decompress" "--stdout" "/root/.npm/uglify-js/1.0.6/package.tgz"
-verbose success tar "-mvxpf" "-" "--no-same-owner" "-C" "/root/.npm/uglify-js/1.0.6/___package.npm"
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/uglify-js.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/docstyle.css
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/package.json
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/README.html
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/README.org
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/.gitignore
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/bin/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/bin/uglifyjs
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/lib/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/lib/parse-js.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/lib/squeeze-more.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/lib/process.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/tmp/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/tmp/instrument2.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/tmp/instrument.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/testparser.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/beautify.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/scripts.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue68.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue10.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue30.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue21.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue13.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue28.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue50.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/array3.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/concatstring.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue17.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue11.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/assignment.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/var.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/if.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/empty-blocks.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue16.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue53.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/with.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/ifreturn2.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue14.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue48.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/mangle.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/array1.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue20.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/array4.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue25.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue54.1.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/const.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/strict-equals.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue27.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue69.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/forstatement.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue29.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/array2.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue9.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/ifreturn.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue34.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/expected/issue4.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue68.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue10.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue30.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue21.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue13.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue28.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue50.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/array3.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/concatstring.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue17.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue11.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/assignment.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/var.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/if.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/empty-blocks.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue16.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue53.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/with.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/ifreturn2.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue14.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue48.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/mangle.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/array1.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue20.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/array4.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue25.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue54.1.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/const.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/strict-equals.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue27.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue69.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/forstatement.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue29.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/array2.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue9.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/ifreturn.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue34.js
-silly asyncMap in gTP /root/.npm/uglify-js/1.0.6/___package.npm/package/test/unit/compress/test/issue4.js
-verbose gunzed /root/.npm/uglify-js/1.0.6/___package.npm/package
-verbose rm'ed /root/.npm/uglify-js/1.0.6/package
-verbose renamed [ '/root/.npm/uglify-js/1.0.6/___package.npm/package',
-verbose renamed '/root/.npm/uglify-js/1.0.6/package' ]
-silly testEngine { name: 'uglify-js',
-silly testEngine author:
-silly testEngine { name: 'Mihai Bazon',
-silly testEngine email: 'mihai.bazon@gmail.com',
-silly testEngine url: 'http://mihai.bazon.net/blog' },
-silly testEngine version: '1.0.6',
-silly testEngine main: './uglify-js.js',
-silly testEngine bin: { uglifyjs: './bin/uglifyjs' },
-silly testEngine repository:
-silly testEngine { type: 'git',
-silly testEngine url: 'git@github.com:mishoo/UglifyJS.git' },
-silly testEngine _npmJsonOpts:
-silly testEngine { file: '/root/.npm/uglify-js/1.0.6/package/package.json',
-silly testEngine wscript: false,
-silly testEngine contributors: false,
-silly testEngine serverjs: false },
-silly testEngine _id: 'uglify-js@1.0.6',
-silly testEngine dependencies: {},
-silly testEngine devDependencies: {},
-silly testEngine engines: { node: '*' },
-silly testEngine _engineSupported: true,
-silly testEngine _npmVersion: '1.0.15',
-silly testEngine _nodeVersion: 'v0.4.10-pre',
-silly testEngine _defaultsLoaded: true,
-silly testEngine dist: { shasum: 'eabbc5c1ccc65c6cb0f337ae43ecdf2be277cd8e' } }
-verbose caching /root/.npm/uglify-js/1.0.6/package/package.json
-verbose loadDefaults uglify-js@1.0.6
-silly updated sha bytes 40960
-silly updated sha bytes 12384
-info shasum eabbc5c1ccc65c6cb0f337ae43ecdf2be277cd8e
-info shasum /root/.npm/uglify-js/1.0.6/package.tgz
-verbose from cache /root/.npm/uglify-js/1.0.6/package/package.json
-verbose chmod /root/.npm/uglify-js/1.0.6/package.tgz 644
-silly resolved [ { name: 'uglify-js',
-silly resolved author:
-silly resolved { name: 'Mihai Bazon',
-silly resolved email: 'mihai.bazon@gmail.com',
-silly resolved url: 'http://mihai.bazon.net/blog' },
-silly resolved version: '1.0.6',
-silly resolved main: './uglify-js.js',
-silly resolved bin: { uglifyjs: './bin/uglifyjs' },
-silly resolved repository:
-silly resolved { type: 'git',
-silly resolved url: 'git@github.com:mishoo/UglifyJS.git' },
-silly resolved _npmJsonOpts:
-silly resolved { file: '/root/.npm/uglify-js/1.0.6/package/package.json',
-silly resolved wscript: false,
-silly resolved contributors: false,
-silly resolved serverjs: false },
-silly resolved _id: 'uglify-js@1.0.6',
-silly resolved dependencies: {},
-silly resolved devDependencies: {},
-silly resolved engines: { node: '*' },
-silly resolved _engineSupported: true,
-silly resolved _npmVersion: '1.0.15',
-silly resolved _nodeVersion: 'v0.4.10-pre',
-silly resolved _defaultsLoaded: true,
-silly resolved dist: { shasum: 'eabbc5c1ccc65c6cb0f337ae43ecdf2be277cd8e' } } ]
-info into /var/www/kiwi/KiwiIRC/node uglify-js@1.0.6
-info installOne uglify-js@1.0.6
-info unbuild /var/www/kiwi/KiwiIRC/node/node_modules/uglify-js
-verbose from cache /root/.npm/uglify-js/1.0.6/package/package.json
-verbose mkdir done: /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm 755
-verbose unpack_ uid, gid [ 1000, 1000 ]
-verbose unpackTarget /var/www/kiwi/KiwiIRC/node/node_modules/uglify-js
-silly gunzTarPerm modes [ '755', '644' ]
-verbose success gzip "--decompress" "--stdout" "/root/.npm/uglify-js/1.0.6/package.tgz"
-verbose success tar "-mvxpf" "-" "--no-same-owner" "-C" "/var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm"
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/.gitignore
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/docstyle.css
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/package.json
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/README.html
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/README.org
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/uglify-js.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/bin/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/bin/uglifyjs
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/tmp/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/tmp/instrument.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/tmp/instrument2.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/lib/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/lib/parse-js.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/lib/process.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/lib/squeeze-more.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/beautify.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/testparser.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/scripts.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/array1.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/array2.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/array3.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/array4.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/assignment.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/concatstring.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/const.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/empty-blocks.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/forstatement.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/if.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/ifreturn.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/ifreturn2.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue10.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue11.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue13.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue14.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue16.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue17.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue20.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue21.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue25.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue27.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue28.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue29.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue30.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue34.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue4.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue48.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue50.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue53.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue54.1.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue68.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue69.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/issue9.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/mangle.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/strict-equals.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/var.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/test/with.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/array1.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/array2.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/array3.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/array4.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/assignment.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/concatstring.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/const.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/empty-blocks.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/forstatement.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/if.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/ifreturn.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/ifreturn2.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue10.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue11.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue13.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue14.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue16.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue17.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue20.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue21.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue25.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue27.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue28.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue29.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue30.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue34.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue4.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue48.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue50.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue53.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue54.1.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue68.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue69.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/issue9.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/mangle.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/strict-equals.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/var.js
-silly asyncMap in gTP /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package/test/unit/compress/expected/with.js
-verbose gunzed /var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package
-verbose rm'ed /var/www/kiwi/KiwiIRC/node/node_modules/uglify-js
-ERR! error installing uglify-js@1.0.6 Error: EACCES, Permission denied '/var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package'
-info unbuild /var/www/kiwi/KiwiIRC/node/node_modules/uglify-js
-verbose installOne cb uglify-js@1.0.6
-ERR! Error: EACCES, Permission denied '/var/www/kiwi/KiwiIRC/node/node_modules/___uglify-js.npm/package'
-ERR!
-ERR! Please use 'sudo' or log in as root to run this command.
-ERR! sudo npm "install" "uglify-js"
-ERR! or set the 'unsafe-perm' config var to true.
-ERR! npm config set unsafe-perm true
-ERR!
-ERR! System Linux 2.6.35-30-generic
-ERR! command "node" "/root/local/node/bin/npm" "install" "uglify-js"
-verbose exit [ 13, true ]