From 4e8739ef8b4c399e80073ad01fcff93d7d1424f1 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Fri, 22 Jan 2016 17:51:22 +0100 Subject: [PATCH 01/11] original project --- Gruntfile.js | 40 + LICENSE | 177 + README.md | 134 + demos/c.html | 211 ++ demos/coffeescript.html | 75 + demos/csharp.html | 613 +++ demos/css.html | 65 + demos/d.html | 129 + demos/go.html | 83 + demos/haskell.html | 109 + demos/html.html | 50 + demos/index.html | 22 + demos/js.html | 88 + demos/lua.html | 115 + demos/php-long.html | 932 +++++ demos/php.html | 89 + demos/python.html | 92 + demos/r.html | 60 + demos/ruby-test.html | 153 + demos/ruby.html | 194 + demos/scheme.html | 48 + demos/shell.html | 433 +++ js/language/c.js | 69 + js/language/coffeescript.js | 125 + js/language/csharp.js | 87 + js/language/css.js | 72 + js/language/d.js | 81 + js/language/generic.js | 65 + js/language/go.js | 59 + js/language/haskell.js | 94 + js/language/html.js | 132 + js/language/java.js | 59 + js/language/javascript.js | 93 + js/language/lua.js | 59 + js/language/php.js | 123 + js/language/python.js | 84 + js/language/r.js | 89 + js/language/ruby.js | 227 ++ js/language/scheme.js | 52 + js/language/shell.js | 56 + js/language/smalltalk.js | 52 + js/rainbow.js | 798 ++++ js/rainbow.min.js | 8 + package.json | 30 + tests/api.html | 53 + tests/language/test.coffeescript.js | 260 ++ tests/language/test.csharp.js | 198 + tests/language/test.css.js | 222 ++ tests/language/test.d.js | 168 + tests/language/test.generic.js | 348 ++ tests/language/test.haskell.js | 157 + tests/language/test.html.js | 240 ++ tests/language/test.java.js | 109 + tests/language/test.javascript.js | 224 ++ tests/language/test.php.js | 407 ++ tests/language/test.python.js | 210 ++ tests/language/test.r.js | 126 + tests/language/test.ruby.js | 57 + tests/language/test.smalltalk.js | 304 ++ tests/libs/chai-1.6.0.js | 4251 +++++++++++++++++++++ tests/libs/mocha-1.9.0.css | 246 ++ tests/libs/mocha-1.9.0.js | 5338 +++++++++++++++++++++++++++ tests/libs/sinon-1.7.1.js | 4299 +++++++++++++++++++++ tests/libs/sinon-chai-2.4.0.js | 109 + tests/libs/sinon-ie-1.7.1.js | 82 + tests/rainbow.html | 72 + themes/all-hallows-eve.css | 49 + themes/blackboard.css | 62 + themes/dreamweaver.css | 109 + themes/espresso-libre.css | 67 + themes/github.css | 88 + themes/kimbie-dark.css | 48 + themes/kimbie-light.css | 48 + themes/monokai.css | 81 + themes/obsidian.css | 72 + themes/paraiso-dark.css | 48 + themes/paraiso-light.css | 48 + themes/pastie.css | 102 + themes/solarized-dark.css | 86 + themes/solarized-light.css | 86 + themes/sunburst.css | 94 + themes/tomorrow-night.css | 48 + themes/tricolore.css | 59 + themes/twilight.css | 82 + themes/zenburnesque.css | 59 + util/builder.py | 152 + util/compile.py | 38 + 87 files changed, 25432 insertions(+) create mode 100644 Gruntfile.js create mode 100644 LICENSE create mode 100644 README.md create mode 100644 demos/c.html create mode 100644 demos/coffeescript.html create mode 100644 demos/csharp.html create mode 100644 demos/css.html create mode 100644 demos/d.html create mode 100644 demos/go.html create mode 100644 demos/haskell.html create mode 100644 demos/html.html create mode 100644 demos/index.html create mode 100644 demos/js.html create mode 100644 demos/lua.html create mode 100644 demos/php-long.html create mode 100644 demos/php.html create mode 100644 demos/python.html create mode 100644 demos/r.html create mode 100644 demos/ruby-test.html create mode 100644 demos/ruby.html create mode 100644 demos/scheme.html create mode 100644 demos/shell.html create mode 100644 js/language/c.js create mode 100644 js/language/coffeescript.js create mode 100644 js/language/csharp.js create mode 100644 js/language/css.js create mode 100644 js/language/d.js create mode 100644 js/language/generic.js create mode 100644 js/language/go.js create mode 100644 js/language/haskell.js create mode 100644 js/language/html.js create mode 100644 js/language/java.js create mode 100644 js/language/javascript.js create mode 100644 js/language/lua.js create mode 100644 js/language/php.js create mode 100644 js/language/python.js create mode 100644 js/language/r.js create mode 100644 js/language/ruby.js create mode 100644 js/language/scheme.js create mode 100644 js/language/shell.js create mode 100644 js/language/smalltalk.js create mode 100644 js/rainbow.js create mode 100644 js/rainbow.min.js create mode 100644 package.json create mode 100644 tests/api.html create mode 100644 tests/language/test.coffeescript.js create mode 100644 tests/language/test.csharp.js create mode 100644 tests/language/test.css.js create mode 100644 tests/language/test.d.js create mode 100644 tests/language/test.generic.js create mode 100644 tests/language/test.haskell.js create mode 100644 tests/language/test.html.js create mode 100644 tests/language/test.java.js create mode 100644 tests/language/test.javascript.js create mode 100644 tests/language/test.php.js create mode 100644 tests/language/test.python.js create mode 100644 tests/language/test.r.js create mode 100644 tests/language/test.ruby.js create mode 100644 tests/language/test.smalltalk.js create mode 100644 tests/libs/chai-1.6.0.js create mode 100644 tests/libs/mocha-1.9.0.css create mode 100644 tests/libs/mocha-1.9.0.js create mode 100644 tests/libs/sinon-1.7.1.js create mode 100644 tests/libs/sinon-chai-2.4.0.js create mode 100644 tests/libs/sinon-ie-1.7.1.js create mode 100644 tests/rainbow.html create mode 100644 themes/all-hallows-eve.css create mode 100644 themes/blackboard.css create mode 100644 themes/dreamweaver.css create mode 100644 themes/espresso-libre.css create mode 100644 themes/github.css create mode 100644 themes/kimbie-dark.css create mode 100644 themes/kimbie-light.css create mode 100644 themes/monokai.css create mode 100644 themes/obsidian.css create mode 100644 themes/paraiso-dark.css create mode 100644 themes/paraiso-light.css create mode 100644 themes/pastie.css create mode 100644 themes/solarized-dark.css create mode 100644 themes/solarized-light.css create mode 100644 themes/sunburst.css create mode 100644 themes/tomorrow-night.css create mode 100644 themes/tricolore.css create mode 100644 themes/twilight.css create mode 100644 themes/zenburnesque.css create mode 100644 util/builder.py create mode 100755 util/compile.py diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..a19a792e --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,40 @@ +/*jshint node:true */ +module.exports = function(grunt) { + 'use strict'; + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + mocha: { + options: { + reporter: 'Nyan', + run: true + }, + mousetrap: { + src: ['tests/rainbow.html'] + } + }, + + complexity: { + options: { + errorsOnly: false, + cyclomatic: 10, + halstead: 30, + maintainability: 90 + }, + generic: { + src: [ + 'js/rainbow.js' + ] + } + } + }); + + grunt.loadNpmTasks('grunt-complexity'); + grunt.loadNpmTasks('grunt-mocha'); + + grunt.registerTask('default', [ + 'complexity', + 'mocha' + ]); +}; diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..430d42bb --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 00000000..a9a1a232 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +# Rainbow + +Rainbow is a code syntax highlighting library written in Javascript. + +It was designed to be lightweight (1.4kb), easy to use, and extendable. + +It is completely themable via CSS. + +If you would like to donate to help support Rainbow development use [Gittip](https://www.gittip.com/ccampbell). + +## Quick Start + +1. Include some markup for code you want to be highlighted: + + ```html +
def openFile(path):
+        file = open(path, "r")
+        content = file.read()
+        file.close()
+        return content
+ ``` + +2. Include a CSS theme file in the ````: + + ```html + + ``` + +3. Include rainbow.js and whatever languages you want before the closing ````: + + ```html + + + + ``` + +## Extending Rainbow +If you have a language specific pattern that you want highlighted, but it does not exist in the language syntax rules you can add a rule on your page. + +Let's say for example you want to reference PHP's apc functions. +You can include the php language then in the markup on your page add: + +```html + +``` + +## Supported Languages + +Currently supported languages are: +- C +- C# +- Coffeescript +- CSS +- D +- Go +- Haskell +- HTML +- Java +- Javascript +- Lua +- PHP +- Python +- R +- Ruby +- Scheme +- Shell +- Smalltalk + +## Building + +Rainbow gets minified with the closure compiler. You can install it on OS X via Homebrew: + + brew install closure-compiler + +To build a minified version of your changes, you can run the compile script: + + ./util/compile.py --core + +In case the compiler cannot be found (which is the case if you installed via Homebrew), +you will have to specify the path to the compiler.jar (see `brew info closure-compiler`) - +here's an example: + + CLOSURE_COMPILER=/usr/local/Cellar/closure-compiler/20120710/libexec/build/compiler.jar util/compile.py --core + +If you want to build a custom version, list the languages you would like to include as +command line arguments: + + util/compile.py ruby javascript + +## Tests + +Unit tests run via mocha. + +### Running in browser + +Download the repo and go to `tests/rainbow.html` in your browser. + +### Running with Grunt and PhantomJS + +1. Install grunt-cli + + ```bash + npm install -g grunt-cli + ``` + +2. Install npm packages + + ```bash + cd /path/to/repo + npm install + ``` + +3. Run tests + + ```bash + grunt mocha + ``` + +## More Info + +If you are looking for line number support you can try one of the following: +- https://github.com/Blender3D/rainbow.linenumbers.js +- https://github.com/Sjeiti/rainbow.linenumbers + +You can check out additional documentation and build custom packages at [rainbowco.de](http://rainbowco.de). diff --git a/demos/c.html b/demos/c.html new file mode 100644 index 00000000..4c6c245c --- /dev/null +++ b/demos/c.html @@ -0,0 +1,211 @@ + + +Syntax Highlighting + + + +
+
+/* variable names that match a keyword or type at the beginning */
+char charcoal;
+float interval;
+char *floating;
+double short_stuff, voider;
+
+void floater(int x, int y)
+{
+    return;
+}
+
+
+ +
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifndef BUF_SIZE
+#define BUF_SIZE 1024
+#endif
+
+static void die (const char * format, ...)
+{
+    va_list vargs;
+    va_start(vargs, format);
+    vfprintf(stderr, format, vargs);
+    fprintf(stderr, ".\n");
+    va_end(vargs);
+    _exit(1);
+}
+
+int main (int argc, char *argv[])
+{
+    static int x;
+    const float y;
+    unsigned int n;
+    unsigned short int g;
+    char* test;
+
+    int outFD, opt, openFlags = O_WRONLY;
+    char buf[BUF_SIZE];
+    ssize_t charCount;
+
+    while ((opt = getopt(argc, argv, ":a")) != -1) {
+	switch (opt) {
+	case 'a': 
+	    openFlags |= O_APPEND;
+	default:
+	    die("Unrecognized option");
+	}
+    }
+
+    outFD = open(argv[1], O_WRONLY);
+    while ((charCount = read(STDIN_FILENO, buf, BUF_SIZE) > 0)) {
+	if (charCount != write(STDOUT_FILENO, buf, BUF_SIZE))
+	    die("Couldn't write same number of bytes to stdout");
+	if (charCount != write(outFD, buf, BUF_SIZE))
+	    die("Couldn't write same number of bytes to output file");
+    }
+    close(outFD);
+
+    return 0;
+}
+
+
+ +
+
+#ifndef type_h
+#define type_h
+
+typedef int type_id;
+
+#define typeid(TYPE) type_find(#TYPE)
+#define type_name(TYPE) #TYPE
+
+type_id type_find(const char* type);
+const char* type_id_name(int id);
+
+#endif
+
+
+
+ +
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "error.h"
+
+#include "type.h"
+
+#define MAX_TYPE_LEN 512
+#define MAX_NUM_TYPES 1024
+
+/* Store Table of type names */
+typedef char type_string[MAX_TYPE_LEN];
+type_string type_table[MAX_NUM_TYPES];
+int type_index = 0;
+
+/* Dynamically enter types into table */
+int type_find(const char* type) {
+  
+  if (strlen(type) >= MAX_TYPE_LEN) {
+    error("Type name %s is too long to index into type table.", type);
+  }
+  if (type_index >= MAX_NUM_TYPES) {
+    error("Too many types in type table already. Cannot add %s.", type);
+  }
+  
+  for (int i = 0; i < type_index; i++) {
+    // Return type index if found
+    if (strcmp(type, type_table[i]) == 0) {
+      return i;
+    }
+  }
+  
+  // If not found add to table and return
+  strcpy(type_table[type_index], type);
+  type_index++;
+  
+  return type_index-1;
+}
+
+const char* type_id_name(int id) {
+  return type_table[id];
+}
+
+
+ +
+
+#ifndef sound_h
+#define sound_h
+
+#include "SDL/SDL.h"
+
+typedef struct {
+  char* data;
+  int length;
+} sound;
+
+sound* wav_load_file(char* filename);
+void sound_delete(sound* s);
+
+#endif
+
+
+ +
+
+#include "error.h"
+
+#include "assets/sound.h"
+
+static void flip_endian(char* data, int length) {
+  for(int i = 0; i < length; i += 2) {
+    int x = data[i];
+    data[i] = data[i + 1];
+    data[i + 1] = x;
+  }
+}
+
+sound* wav_load_file(char* filename) {
+  
+  sound* s = malloc(sizeof(sound));
+  
+  SDL_AudioSpec spec;
+  
+  if( SDL_LoadWAV(filename, &spec, (Uint8**)&s->data, (Uint32*)&s->length) == NULL) {
+    error("Unable to load sound file %s", filename);
+  }
+  
+  if ((spec.format != AUDIO_S16LSB) &&
+      (spec.format != AUDIO_S16MSB)) {
+    error("Unsupported sound format for file %s, id %i.", filename, spec.format);
+  }
+  
+  if (spec.format != AUDIO_S16SYS) {
+    flip_endian(s->data, s->length);
+  }
+  
+  return s;
+}
+
+void sound_delete(sound* s) {
+  SDL_FreeWAV((Uint8*)s->data);
+  free(s);
+}
+
+
+ + + + + diff --git a/demos/coffeescript.html b/demos/coffeescript.html new file mode 100644 index 00000000..f4e72853 --- /dev/null +++ b/demos/coffeescript.html @@ -0,0 +1,75 @@ + + +Syntax Highlighting + + + +
# Assignment:
+number   = 42
+opposite = true
+
+# Conditions:
+number = -42 if opposite
+
+# Functions:
+square = (x) -> x * x
+
+# Arrays:
+list = [1, 2, 3, 4, 5]
+
+# Objects:
+math =
+  root:   Math.sqrt
+  square: square
+  cube:   (x) -> x * square x
+
+# Splats:
+race = (winner, runners...) ->
+  print winner, runners
+
+# Existence:
+alert "I knew it!" if elvis?
+
+# Array comprehensions:
+cubes = (math.cube num for num in list)
+ +
class Animal
+  constructor: (@name) ->
+
+  move: (meters) ->
+    alert @name + " moved #{meters}m."
+
+class Snake extends Animal
+  move: ->
+    alert "Slithering..."
+    super 5
+
+class Horse extends Animal
+  move: ->
+    alert "Galloping..."
+    super 45
+
+sam = new Snake "Sammy the Python"
+tom = new Horse "Tommy the Palomino"
+
+sam.move()
+tom.move()
+
weatherReport = (location) ->
+  # Make an Ajax request to fetch the weather...
+  [location, 72, "Mostly Sunny"]
+
+[city, temp, forecast] = weatherReport "Berkeley, CA"
+ +
fs = require 'fs'
+
+option '-o', '--output [DIR]', 'directory for compiled code'
+
+task 'build:parser', 'rebuild the Jison parser', (options) ->
+  require 'jison'
+  code = require('./lib/grammar').parser.generate()
+  dir  = options.output or 'lib'
+  fs.writeFile "#{dir}/parser.js", code
+ + + + diff --git a/demos/csharp.html b/demos/csharp.html new file mode 100644 index 00000000..b8fd85b9 --- /dev/null +++ b/demos/csharp.html @@ -0,0 +1,613 @@ + + +Syntax Highlighting + + + +
+/*
+ this is some sample C# code
+*/
+
+#pragma warning disable 1591
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:2.0.50727.3521
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace NerdDinner.Models
+{
+	using System.Data.Linq;
+	using System.Data.Linq.Mapping;
+	using System.Data;
+	using System.Collections.Generic;
+	using System.Reflection;
+	using System.Linq;
+	using System.Linq.Expressions;
+	using System.ComponentModel;
+	using System;
+
+	[System.Data.Linq.Mapping.DatabaseAttribute(Name="NerdDinner")]
+	public partial class NerdDinnerDataContext : System.Data.Linq.DataContext
+	{
+
+		private static System.Data.Linq.Mapping.MappingSource mappingSource = new AttributeMappingSource();
+
+		#region Extensibility Method Definitions
+		partial void OnCreated();
+		partial void InsertDinner(Dinner instance);
+		partial void UpdateDinner(Dinner instance);
+		partial void DeleteDinner(Dinner instance);
+		partial void InsertRSVP(RSVP instance);
+		partial void UpdateRSVP(RSVP instance);
+		partial void DeleteRSVP(RSVP instance);
+		#endregion
+
+		public NerdDinnerDataContext() :
+			base(global::System.Configuration.ConfigurationManager.ConnectionStrings["NerdDinnerConnectionString"].ConnectionString, mappingSource)
+		{
+			OnCreated();
+		}
+
+		public NerdDinnerDataContext(string connection) :
+			base(connection, mappingSource)
+		{
+			OnCreated();
+		}
+
+		public NerdDinnerDataContext(System.Data.IDbConnection connection) :
+			base(connection, mappingSource)
+		{
+			OnCreated();
+		}
+
+		public NerdDinnerDataContext(string connection, System.Data.Linq.Mapping.MappingSource mappingSource) :
+			base(connection, mappingSource)
+		{
+			OnCreated();
+		}
+
+		public NerdDinnerDataContext(System.Data.IDbConnection connection, System.Data.Linq.Mapping.MappingSource mappingSource) :
+			base(connection, mappingSource)
+		{
+			OnCreated();
+		}
+
+		public System.Data.Linq.Table<Dinner> Dinners
+		{
+			get
+			{
+				return this.GetTable<Dinner>();
+			}
+		}
+
+		public System.Data.Linq.Table<RSVP> RSVPs
+		{
+			get
+			{
+				return this.GetTable<RSVP>();
+			}
+		}
+
+		[Function(Name="dbo.NearestDinners", IsComposable=true)]
+		public IQueryable<NearestDinnersResult> NearestDinners(
+			[Parameter(DbType="Real")] System.Nullable<float> lat,
+			[Parameter(Name="long", DbType="Real")] System.Nullable<float> @long)
+		{
+			return this.CreateMethodCallQuery<NearestDinnersResult>(
+				this,
+				((MethodInfo)(MethodInfo.GetCurrentMethod())),
+				lat,
+				@long);
+		}
+
+		[Function(Name="dbo.DistanceBetween", IsComposable=true)]
+		public System.Nullable<float> DistanceBetween(
+			[Parameter(Name="Lat1", DbType="Real")] System.Nullable<float> lat1,
+			[Parameter(Name="Long1", DbType="Real")] System.Nullable<float> long1,
+			[Parameter(Name="Lat2", DbType="Real")] System.Nullable<float> lat2,
+			[Parameter(Name="Long2", DbType="Real")] System.Nullable<float> long2)
+		{
+			return ((System.Nullable<float>)(this.ExecuteMethodCall(
+				this,
+				((MethodInfo)(MethodInfo.GetCurrentMethod())),
+				lat1,
+				long1,
+				lat2,
+				long2).ReturnValue));
+		}
+	}
+
+	[Table(Name="dbo.Dinners")]
+	public partial class Dinner : INotifyPropertyChanging, INotifyPropertyChanged
+	{
+
+		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
+
+		private int _DinnerID;
+
+		private string _Title;
+
+		private System.DateTime _EventDate;
+
+		private string _Description;
+
+		private string _HostedBy;
+
+		private string _ContactPhone;
+
+		private string _Address;
+
+		private string _Country;
+
+		private double _Latitude;
+
+		private double _Longitude;
+
+		private EntitySet<RSVP> _RSVPs;
+
+		#region Extensibility Method Definitions
+		partial void OnLoaded();
+		partial void OnValidate(System.Data.Linq.ChangeAction action);
+		partial void OnCreated();
+		partial void OnDinnerIDChanging(int value);
+		partial void OnDinnerIDChanged();
+		partial void OnTitleChanging(string value);
+		partial void OnTitleChanged();
+		partial void OnEventDateChanging(System.DateTime value);
+		partial void OnEventDateChanged();
+		partial void OnDescriptionChanging(string value);
+		partial void OnDescriptionChanged();
+		partial void OnHostedByChanging(string value);
+		partial void OnHostedByChanged();
+		partial void OnContactPhoneChanging(string value);
+		partial void OnContactPhoneChanged();
+		partial void OnAddressChanging(string value);
+		partial void OnAddressChanged();
+		partial void OnCountryChanging(string value);
+		partial void OnCountryChanged();
+		partial void OnLatitudeChanging(double value);
+		partial void OnLatitudeChanged();
+		partial void OnLongitudeChanging(double value);
+		partial void OnLongitudeChanged();
+		#endregion
+
+		public Dinner()
+		{
+			this._RSVPs = new EntitySet<RSVP>(new Action<RSVP>(this.attach_RSVPs), new Action<RSVP>(this.detach_RSVPs));
+			OnCreated();
+		}
+
+		[Column(Storage="_DinnerID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
+		public int DinnerID
+		{
+			get
+			{
+				return this._DinnerID;
+			}
+			set
+			{
+				if ((this._DinnerID != value))
+				{
+					this.OnDinnerIDChanging(value);
+					this.SendPropertyChanging();
+					this._DinnerID = value;
+					this.SendPropertyChanged("DinnerID");
+					this.OnDinnerIDChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_Title", DbType="NVarChar(50) NOT NULL", CanBeNull=false)]
+		public string Title
+		{
+			get
+			{
+				return this._Title;
+			}
+			set
+			{
+				if ((this._Title != value))
+				{
+					this.OnTitleChanging(value);
+					this.SendPropertyChanging();
+					this._Title = value;
+					this.SendPropertyChanged("Title");
+					this.OnTitleChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_EventDate", DbType="DateTime NOT NULL")]
+		public System.DateTime EventDate
+		{
+			get
+			{
+				return this._EventDate;
+			}
+			set
+			{
+				if ((this._EventDate != value))
+				{
+					this.OnEventDateChanging(value);
+					this.SendPropertyChanging();
+					this._EventDate = value;
+					this.SendPropertyChanged("EventDate");
+					this.OnEventDateChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_Description", DbType="NVarChar(256)")]
+		public string Description
+		{
+			get
+			{
+				return this._Description;
+			}
+			set
+			{
+				if ((this._Description != value))
+				{
+					this.OnDescriptionChanging(value);
+					this.SendPropertyChanging();
+					this._Description = value;
+					this.SendPropertyChanged("Description");
+					this.OnDescriptionChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_HostedBy", DbType="NVarChar(50) NOT NULL", CanBeNull=false)]
+		public string HostedBy
+		{
+			get
+			{
+				return this._HostedBy;
+			}
+			set
+			{
+				if ((this._HostedBy != value))
+				{
+					this.OnHostedByChanging(value);
+					this.SendPropertyChanging();
+					this._HostedBy = value;
+					this.SendPropertyChanged("HostedBy");
+					this.OnHostedByChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_ContactPhone", DbType="NVarChar(50) NOT NULL", CanBeNull=false)]
+		public string ContactPhone
+		{
+			get
+			{
+				return this._ContactPhone;
+			}
+			set
+			{
+				if ((this._ContactPhone != value))
+				{
+					this.OnContactPhoneChanging(value);
+					this.SendPropertyChanging();
+					this._ContactPhone = value;
+					this.SendPropertyChanged("ContactPhone");
+					this.OnContactPhoneChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_Address", DbType="NVarChar(50)")]
+		public string Address
+		{
+			get
+			{
+				return this._Address;
+			}
+			set
+			{
+				if ((this._Address != value))
+				{
+					this.OnAddressChanging(value);
+					this.SendPropertyChanging();
+					this._Address = value;
+					this.SendPropertyChanged("Address");
+					this.OnAddressChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_Country", DbType="NVarChar(30)")]
+		public string Country
+		{
+			get
+			{
+				return this._Country;
+			}
+			set
+			{
+				if ((this._Country != value))
+				{
+					this.OnCountryChanging(value);
+					this.SendPropertyChanging();
+					this._Country = value;
+					this.SendPropertyChanged("Country");
+					this.OnCountryChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_Latitude", DbType="Float NOT NULL")]
+		public double Latitude
+		{
+			get
+			{
+				return this._Latitude;
+			}
+			set
+			{
+				if ((this._Latitude != value))
+				{
+					this.OnLatitudeChanging(value);
+					this.SendPropertyChanging();
+					this._Latitude = value;
+					this.SendPropertyChanged("Latitude");
+					this.OnLatitudeChanged();
+			}
+		}
+		}
+
+		[Column(Storage="_Longitude", DbType="Float NOT NULL")]
+		public double Longitude
+		{
+			get
+			{
+				return this._Longitude;
+			}
+			set
+			{
+				if ((this._Longitude != value))
+				{
+					this.OnLongitudeChanging(value);
+					this.SendPropertyChanging();
+					this._Longitude = value;
+					this.SendPropertyChanged("Longitude");
+					this.OnLongitudeChanged();
+				}
+			}
+		}
+
+		[Association(Name="Dinner_RSVP", Storage="_RSVPs", ThisKey="DinnerID", OtherKey="DinnerID")]
+		public EntitySet<RSVP> RSVPs
+		{
+			get
+			{
+				return this._RSVPs;
+			}
+			set
+			{
+				this._RSVPs.Assign(value);
+			}
+		}
+
+		public event PropertyChangingEventHandler PropertyChanging;
+
+		public event PropertyChangedEventHandler PropertyChanged;
+
+		protected virtual void SendPropertyChanging()
+		{
+			if ((this.PropertyChanging != null))
+			{
+				this.PropertyChanging(this, emptyChangingEventArgs);
+			}
+		}
+
+		protected virtual void SendPropertyChanged(String propertyName)
+		{
+			if ((this.PropertyChanged != null))
+			{
+				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+			}
+		}
+
+		private void attach_RSVPs(RSVP entity)
+		{
+			this.SendPropertyChanging();
+			entity.Dinner = this;
+		}
+
+		private void detach_RSVPs(RSVP entity)
+		{
+			this.SendPropertyChanging();
+			entity.Dinner = null;
+		}
+	}
+
+	[Table(Name="dbo.RSVP")]
+	public partial class RSVP : INotifyPropertyChanging, INotifyPropertyChanged
+	{
+
+		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
+
+		private int _RsvpID;
+
+		private int _DinnerID;
+
+		private string _AttendeeName;
+
+		private EntityRef<Dinner> _Dinner;
+
+		#region Extensibility Method Definitions
+		partial void OnLoaded();
+		partial void OnValidate(System.Data.Linq.ChangeAction action);
+		partial void OnCreated();
+		partial void OnRsvpIDChanging(int value);
+		partial void OnRsvpIDChanged();
+		partial void OnDinnerIDChanging(int value);
+		partial void OnDinnerIDChanged();
+		partial void OnAttendeeNameChanging(string value);
+		partial void OnAttendeeNameChanged();
+		#endregion
+
+		public RSVP()
+		{
+			this._Dinner = default(EntityRef<Dinner>);
+			OnCreated();
+		}
+
+		[Column(Storage="_RsvpID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
+		public int RsvpID
+		{
+			get
+			{
+				return this._RsvpID;
+			}
+			set
+			{
+				if ((this._RsvpID != value))
+				{
+					this.OnRsvpIDChanging(value);
+					this.SendPropertyChanging();
+					this._RsvpID = value;
+					this.SendPropertyChanged("RsvpID");
+					this.OnRsvpIDChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_DinnerID", DbType="Int NOT NULL")]
+		public int DinnerID
+		{
+			get
+			{
+				return this._DinnerID;
+			}
+			set
+			{
+				if ((this._DinnerID != value))
+				{
+					if (this._Dinner.HasLoadedOrAssignedValue)
+					{
+						throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
+					}
+					this.OnDinnerIDChanging(value);
+					this.SendPropertyChanging();
+					this._DinnerID = value;
+					this.SendPropertyChanged("DinnerID");
+					this.OnDinnerIDChanged();
+				}
+			}
+		}
+
+		[Column(Storage="_AttendeeName", DbType="NVarChar(50) NOT NULL", CanBeNull=false)]
+		public string AttendeeName
+		{
+			get
+			{
+				return this._AttendeeName;
+			}
+			set
+			{
+				if ((this._AttendeeName != value))
+				{
+					this.OnAttendeeNameChanging(value);
+					this.SendPropertyChanging();
+					this._AttendeeName = value;
+					this.SendPropertyChanged("AttendeeName");
+					this.OnAttendeeNameChanged();
+				}
+			}
+		}
+
+		[Association(Name="Dinner_RSVP", Storage="_Dinner", ThisKey="DinnerID", OtherKey="DinnerID", IsForeignKey=true)]
+		public Dinner Dinner
+		{
+			get
+			{
+				return this._Dinner.Entity;
+			}
+			set
+			{
+				Dinner previousValue = this._Dinner.Entity;
+				if (((previousValue != value)
+				|| (this._Dinner.HasLoadedOrAssignedValue == false)))
+				{
+					this.SendPropertyChanging();
+					if ((previousValue != null))
+					{
+						this._Dinner.Entity = null;
+						previousValue.RSVPs.Remove(this);
+					}
+					this._Dinner.Entity = value;
+					if ((value != null))
+					{
+						value.RSVPs.Add(this);
+						this._DinnerID = value.DinnerID;
+					}
+					else
+					{
+						this._DinnerID = default(int);
+					}
+					this.SendPropertyChanged("Dinner");
+				}
+			}
+		}
+
+		public event PropertyChangingEventHandler PropertyChanging;
+
+		public event PropertyChangedEventHandler PropertyChanged;
+
+		protected virtual void SendPropertyChanging()
+		{
+			if ((this.PropertyChanging != null))
+			{
+				this.PropertyChanging(this, emptyChangingEventArgs);
+			}
+		}
+
+		protected virtual void SendPropertyChanged(String propertyName)
+		{
+			if ((this.PropertyChanged != null))
+			{
+				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+			}
+		}
+	}
+
+	public partial class NearestDinnersResult
+	{
+
+		private int _DinnerID;
+
+		public NearestDinnersResult()
+		{
+		}
+
+		[Column(Storage="_DinnerID", DbType="Int NOT NULL")]
+		public int DinnerID
+		{
+			get
+			{
+				return this._DinnerID;
+			}
+			set
+			{
+				if ((this._DinnerID != value))
+				{
+					this._DinnerID = value;
+				}
+			}
+		}
+	}
+}
+#pragma warning restore 1591
+
+ + + diff --git a/demos/css.html b/demos/css.html new file mode 100644 index 00000000..0b19bd43 --- /dev/null +++ b/demos/css.html @@ -0,0 +1,65 @@ + + +Syntax Highlighting + + + +
+/**
+ * styles for blackboard theme
+ */
+pre {
+    background: #0B1022;
+    white-space: pre-wrap;
+    white-space: -moz-pre-wrap;
+    white-space: -pre-wrap;
+    white-space: -o-pre-wrap;
+    word-wrap: break-word;
+    margin: 0px;
+    padding: 0px;
+    padding: 10px;
+    color: #fff;
+    font-size: 14px;
+    margin-bottom: 20px;
+}
+
+pre, code {
+    font-family: 'Monaco', courier, monospace;
+}
+
+pre .comment {
+    color: #727272;
+}
+
+pre .constant {
+    color: #D8FA3C;
+}
+
+pre .storage {
+    color: #FBDE2D;
+}
+
+pre .string {
+    color: #61CE3C;
+}
+
+pre .keyword, pre .selector {
+    color: #FBDE2D;
+}
+
+pre .parent {
+    font-style: italic;
+}
+
+pre .entity {
+    color: #FF6400;
+}
+
+pre .support {
+    color: #8DA6CE;
+}
+
+ + + + diff --git a/demos/d.html b/demos/d.html new file mode 100644 index 00000000..21b68c9e --- /dev/null +++ b/demos/d.html @@ -0,0 +1,129 @@ + + +Syntax Highlighting + + + +
+
+// Single line comment
+
+/*
+ Multi line comment
+*/
+
+/+
+ Multi line doc comment
++/
+
+import std.stdio;
+
+// Integers
+ushort a = 0;
+short b = 1;
+int c = 2;
+uint d = 3;
+long e = 4;
+ulong f = 5;
+size_t g = 90;
+
+// Boolean
+bool h = true;
+bool m = false;
+
+// Floating point
+float j = 7.9;
+real k = 0;
+double l = 0.8;
+
+// Strings, chars, bytes
+string s = "derp";
+string t = null;
+char u = 'a';
+wchar v = 'b';
+byte w = 0;
+ubyte x = 0;
+
+// Struct
+struct Thing {
+    int count;
+}
+
+auto thing = Thing();
+thing.count = 35;
+writefln("thing.count: %d", thing.count);
+
+// Class
+class Animal {
+    string _name;
+
+    string name() { return _name; }
+
+    this(string name) {
+        _name = name;
+    }
+}
+
+auto ani = new Animal("dog");
+writefln("ani.name: %s", ani.name);
+
+// Class Inheritance
+class Dog : Animal {
+    this(string name) {
+        super(name);
+    }
+
+    void bark() {
+        writeln("Woof!");
+    }
+}
+
+auto dog = new Dog("Rover");
+writefln("dog.name: %s", dog.name);
+dog.bark();
+
+// For loop
+for(size_t i=0; i<5; ++i) {
+    writefln("i: %d", i);
+}
+
+// For each loop
+foreach(i; [0, 1, 2, 3, 4]) {
+    writefln("i: %d", i);
+}
+
+// While loop
+int counter = 0;
+while(counter < 5) {
+    writefln("counter: %d", counter);
+    counter++;
+}
+
+// Do while loop
+counter = 0;
+do {
+    writefln("counter: %d", counter);
+    counter++;
+} while(counter < 5);
+
+
+// If condition
+bool flag_a = true;
+bool flag_b = false;
+if(flag_a && flag_b) {
+
+}
+
+// Switch condition
+string name = "Derper";
+switch(name) {
+    case "Bobrick": write("Stupid name"); break;
+    case "Rickyrick": write("Awesome name"); break;
+    default: write("Unexpected name"); break;
+}
+
+
+ + + + diff --git a/demos/go.html b/demos/go.html new file mode 100644 index 00000000..c6b75945 --- /dev/null +++ b/demos/go.html @@ -0,0 +1,83 @@ + + + +Syntax Highlighting + + +

GO language

+

First example:

+
// You can edit this code!
+// Click here and start typing.
+package main
+
+import "fmt"
+
+func main() {
+    fmt.Println("Hello, world!")
+}
+ +
package main
+
+// fib returns a function that returns
+// successive Fibonacci numbers.
+func fib() func() int {
+    a, b := 0, 1
+    return func() int {
+        a, b = b, a+b
+        c = 256
+        return a
+    }
+}
+
+func main() {
+    f := fib()
+    // Function calls are evaluated left-to-right.
+    println(f(), f(), f(), f(), f())
+}
+ +

Another Example:

+ +
/*
+    This is a long comment
+    Second line.
+*/
+package main
+
+import (
+    "fmt"
+    "github.com/hoisie/web.go"
+)
+
+type mytype struct {
+    A   string
+    B   string
+    C   int
+    D   int64
+    E   uint8
+    F   complex128
+    G   float32
+}
+
+var page = `
+    This is a long string
+    This is another string
+`
+
+func index() string { return page }
+
+func process(ctx *web.Context) string {
+    var data mytype
+    ctx.UnmarshalParams(&data)
+    return fmt.Sprintf("%v\n", data)
+}
+
+func main() {
+    web.Get("/", index)
+    web.Post("/process", process)
+    web.Run("0.0.0.0:9999")
+}
+ + + diff --git a/demos/haskell.html b/demos/haskell.html new file mode 100644 index 00000000..7ba7ad86 --- /dev/null +++ b/demos/haskell.html @@ -0,0 +1,109 @@ + + +Syntax Highlighting + + + +
+
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE CPP #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  System.Exit
+-- Copyright   :  (c) The University of Glasgow 2001
+-- License     :  BSD-style (see the file libraries/base/LICENSE)
+-- 
+-- Maintainer  :  libraries@haskell.org
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-- Exiting the program.
+--
+-----------------------------------------------------------------------------
+
+module System.Exit
+    (
+      ExitCode(ExitSuccess,ExitFailure)
+    , exitWith      -- :: ExitCode -> IO a
+    , exitFailure   -- :: IO a
+    , exitSuccess   -- :: IO a
+  ) where
+
+import Prelude
+
+a = [1..10]
+
+#ifdef __GLASGOW_HASKELL__
+import GHC.IO
+import GHC.IO.Exception
+#endif
+
+#ifdef __HUGS__
+import Hugs.Prelude (ExitCode(..))
+import Control.Exception.Base
+#endif
+
+#ifdef __NHC__
+import System
+  ( ExitCode(..)
+  , exitWith
+  )
+#endif
+
+-- ---------------------------------------------------------------------------
+-- exitWith
+
+-- | Computation 'exitWith' @code@ throws 'ExitCode' @code@.
+-- Normally this terminates the program, returning @code@ to the
+-- program's caller.
+--
+-- On program termination, the standard 'Handle's 'stdout' and
+-- 'stderr' are flushed automatically; any other buffered 'Handle's
+-- need to be flushed manually, otherwise the buffered data will be
+-- discarded.
+--
+-- A program that fails in any other way is treated as if it had
+-- called 'exitFailure'.
+-- A program that terminates successfully without calling 'exitWith'
+-- explicitly is treated as it it had called 'exitWith' 'ExitSuccess'.
+--
+-- As an 'ExitCode' is not an 'IOError', 'exitWith' bypasses
+-- the error handling in the 'IO' monad and cannot be intercepted by
+-- 'catch' from the "Prelude".  However it is a 'SomeException', and can
+-- be caught using the functions of "Control.Exception".  This means
+-- that cleanup computations added with 'Control.Exception.bracket'
+-- (from "Control.Exception") are also executed properly on 'exitWith'.
+--
+-- Note: in GHC, 'exitWith' should be called from the main program
+-- thread in order to exit the process.  When called from another
+-- thread, 'exitWith' will throw an 'ExitException' as normal, but the
+-- exception will not cause the process itself to exit.
+--
+#ifndef __NHC__
+exitWith :: ExitCode -> IO a
+exitWith ExitSuccess = throwIO ExitSuccess
+exitWith code@(ExitFailure n)
+  | n /= 0 = throwIO code
+#ifdef __GLASGOW_HASKELL__
+  | otherwise = ioError (IOError Nothing InvalidArgument "exitWith" "ExitFailure 0" Nothing Nothing)
+#endif
+#endif  /* ! __NHC__ */
+
+-- | The computation 'exitFailure' is equivalent to
+-- 'exitWith' @(@'ExitFailure' /exitfail/@)@,
+-- where /exitfail/ is implementation-dependent.
+exitFailure :: IO a
+exitFailure = exitWith (ExitFailure 1)
+
+-- | The computation 'exitSuccess' is equivalent to
+-- 'exitWith' 'ExitSuccess', It terminates the program
+-- successfully.
+exitSuccess :: IO a
+exitSuccess = exitWith ExitSuccess
+
+ + + + \ No newline at end of file diff --git a/demos/html.html b/demos/html.html new file mode 100644 index 00000000..96ac4643 --- /dev/null +++ b/demos/html.html @@ -0,0 +1,50 @@ + + +Syntax Highlighting + + + +
+<!-- inline styles! -->
+<style type="text/css">
+    body span.blah {
+        background: #000;
+        color: #fff;
+    }
+</style>
+
+<body>
+    <span class="blah" width="200" height="200">test code goes here</span>
+</body>
+
+<!-- inline php! -->
+<?php
+    $test = true;
+
+    /**
+     * test
+     */
+    function what($test) {
+        return $test;
+    }
+?>
+
+<!-- inline javascript! -->
+<script type="text/javascript">
+    function prettyCool() {
+        doSomeJQueryOrWhatever();
+    }
+</script>
+
+<article title="<?= $user->name ?>">test</article>
+
+ + + + + + + + + diff --git a/demos/index.html b/demos/index.html new file mode 100644 index 00000000..62bb7aa7 --- /dev/null +++ b/demos/index.html @@ -0,0 +1,22 @@ + + +Syntax Highlighting + + + diff --git a/demos/js.html b/demos/js.html new file mode 100644 index 00000000..01a61c5d --- /dev/null +++ b/demos/js.html @@ -0,0 +1,88 @@ + + +Syntax Highlighting + + +
+/**
+ * test function
+ *
+ * @param string
+ * @return string
+ */
+function blah(foo, blah) {
+    var test = 25;
+
+    console.log(test.length);
+
+    // if foo is true then return this string
+    if (foo === true) {
+        return 'foo is true';
+    }
+    return 'foo is false';
+}
+
+$(document).ready(function() {
+    $("table").on("click", "td", function() {
+        console.log('td click');
+    });
+});
+
+
+ +
+window.Rainbow = {
+    whatever: function(param) {
+
+    },
+
+    another: function(param) {
+
+    }
+};
+
+ +
+window.Rainbow = window.Rainbow || {};
+
+Rainbow.extend('javascript', [
+    {
+        'name': 'selector',
+        'pattern': /\$(?=\.|\()/g
+    }
+]);
+
+ +
+/**
+ * cross browser get attribute for an element
+ *
+ * @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method
+ *
+ * @param {Element} el
+ * @param {string} attr     attribute you are trying to get
+ * @returns {string}
+ */
+function _attr(el, attr) {
+    var result = (el.getAttribute && el.getAttribute(attr)) || null;
+
+    if (!result) {
+        var attrs = el.attributes,
+            length = attrs.length,
+            i;
+
+        for (i = 0; i < length; ++i) {
+            if (attr[i].nodeName === attr) {
+                result = attr[i].nodeValue;
+            }
+        }
+    }
+
+    return result;
+}
+
+ + + + + diff --git a/demos/lua.html b/demos/lua.html new file mode 100644 index 00000000..a238cb4a --- /dev/null +++ b/demos/lua.html @@ -0,0 +1,115 @@ + + + +Syntax Highlighting + + + +
+
+function get_all_factors(number)
+    --[[--
+    Gets all of the factors of a given number
+
+    @Parameter: number
+        The number to find the factors of
+
+    @Returns: A table of factors of the number
+    --]]--
+    local factors = {}
+    for possible_factor=1, math.sqrt(number), 1 do
+        local remainder = number%possible_factor
+
+        if remainder == 0 then
+            local factor, factor_pair = possible_factor, number/possible_factor
+            table.insert(factors, factor)
+
+            if factor ~= factor_pair then
+                table.insert(factors, factor_pair)
+            end
+        end
+    end
+
+    hello = nil  -- This is it!
+    hello = 3%2
+    print("I haz "..#bag_of_stuff.." things")
+    table.sort(factors)
+    return factors
+end
+
+--The Meaning of the Universe is 42. Let's find all of the factors driving the Universe.
+
+the_universe = 42
+factors_of_the_universe = get_all_factors(the_universe)
+
+--Print out each factor
+
+print("Count",  "The Factors of Life, the Universe, and Everything")
+table.foreach(factors_of_the_universe, print)
+
+
+-- Other example
+-------------------------------------------------
+-- PUBLIC FUNCTIONS
+-------------------------------------------------
+
+function gameminion.init(accessKey, secretKey)  -- constructor
+    -- initialize GM connection
+
+    GM_ACCESS_KEY = accessKey
+    GM_SECRET_KEY = secretKey
+
+    local newGameminion = {
+        authToken = authToken,
+        accessKey = GM_ACCESS_KEY,
+        secretKey = GM_SECRET_KEY,
+        gameID = "4f6f1e456b789d0001000002",
+        cloudStorageBox = cloudStorageBox,
+        gameminion = gameminion
+    }
+
+    return setmetatable( newGameminion, gameminion_mt )
+end
+
+-------------------------------------------------
+-- User
+-------------------------------------------------
+
+function gameminion:loginWeb()
+    local authToken
+
+    return authToken
+end
+
+-------------------------------------------------
+
+function gameminion:loginAPI(username, password)
+    local params = "login="..username.."&password="..password"
+
+    local path = "user_sessions/user_login.json"
+
+    -- set AuthToken when it gets it
+    local function networkListener(event)
+        if (event.isError) then
+            print("Network Error")
+            print("Error: "..event.response)
+            return false
+        else
+            self.authToken = json.decode(event.response).auth_token
+            print("User Logged In!")
+            print("Auth Token: "..self.authToken)
+            return true
+        end
+    end
+
+    postGM(path, params, networkListener)
+
+    return true
+end
+
+
+ + + diff --git a/demos/php-long.html b/demos/php-long.html new file mode 100644 index 00000000..fb853c1a --- /dev/null +++ b/demos/php-long.html @@ -0,0 +1,932 @@ + + +Syntax Highlighting + + + + + +
+<?php
+namespace Sonic;
+
+/**
+ * App singleton
+ *
+ * @category Sonic
+ * @package App
+ * @author Craig Campbell
+ */
+final class App
+{
+    /**
+     * @var string
+     */
+    const WEB = 'www';
+
+    /**
+     * @var string
+     */
+    const COMMAND_LINE = 'cli';
+
+    /**
+     * @var float
+     */
+    const VERSION = '1.1.2';
+
+    /**
+     * @var App
+     */
+    protected static $_instance;
+
+    /**
+     * @var Request
+     */
+    protected $_request;
+
+    /**
+     * @var Delegate
+     */
+    protected $_delegate;
+
+    /**
+     * @var array
+     */
+    protected $_paths = array();
+
+    /**
+     * @var array
+     */
+    protected $_controllers = array();
+
+    /**
+     * @var array
+     */
+    protected $_queued = array();
+
+    /**
+     * @var bool
+     */
+    protected $_layout_processed = false;
+
+    /**
+     * @var array
+     */
+    protected $_configs = array();
+
+    /**
+     * @var array
+     */
+    protected $_included = array();
+
+    /**
+     * @var string
+     */
+    protected $_base_path;
+
+    /**
+     * constants for settings
+     */
+    const MODE = 0;
+    const ENVIRONMENT = 1;
+    const AUTOLOAD = 2;
+    const CONFIG_FILE = 3;
+    const DEVS = 4;
+    const DISABLE_APC = 5;
+    const TURBO = 6;
+    const TURBO_PLACEHOLDER = 7;
+    const DEFAULT_SCHEMA = 8;
+    const EXTENSION_DATA = 9;
+    const EXTENSIONS_LOADED = 10;
+    const URI_PREFIX = 11;
+
+    /**
+     * @var array
+     */
+    protected $_settings = array(
+        self::MODE => self::WEB,
+        self::AUTOLOAD => false,
+        self::CONFIG_FILE => 'ini',
+        self::DEVS => array('dev', 'development'),
+        self::DISABLE_APC => false,
+        self::TURBO => false,
+        self::EXTENSIONS_LOADED => array()
+    );
+
+    /**
+     * constructor
+     *
+     * @return void
+     */
+    private function __construct() {}
+
+    /**
+     * magic call for methods added at runtime
+     *
+     * @param string $name
+     * @param array $args
+     */
+    public function __call($name, $args)
+    {
+        return $this->callIfExists($name, $args, __CLASS__, get_class($this));
+    }
+
+    /**
+     * magic static call for methods added at run time
+     *
+     * @param string $name
+     * @param array $args
+     */
+    public static function __callStatic($name, $args)
+    {
+        return self::getInstance()->callIfExists($name, $args, __CLASS__, get_called_class(), true);
+    }
+
+    /**
+     * calls method if it exists
+     *
+     * @param string $name
+     * @param array $args
+     * @param string $class
+     * @param instance $class_name
+     */
+    public function callIfExists($name, $args, $class, $class_name, $static = false)
+    {
+        if (count($this->getSetting(self::EXTENSIONS_LOADED)) == 0) {
+            return trigger_error('Call to undefined method ' . $class_name . '::' . $name . '()', E_USER_ERROR);
+        }
+        $this->includeFile('Sonic/Extension/Transformation.php');
+        $method = $static ? 'callStatic' : 'call';
+        return Extension\Transformation::$method($name, $args, $class, $class_name);
+    }
+
+    /**
+     * gets instance of App class
+     *
+     * @return App
+     */
+    public static function getInstance()
+    {
+        if (self::$_instance === null) {
+            self::$_instance = new App();
+        }
+        return self::$_instance;
+    }
+
+    /**
+     * handles autoloading
+     *
+     * @param string $class_name
+     * @return void
+     */
+    public function autoloader($class_name)
+    {
+        $path = str_replace('\\', '/', $class_name) . '.php';
+        return $this->includeFile($path);
+    }
+
+    /**
+     * includes a file at the given path
+     *
+     * @param string
+     * @return bool
+     */
+    public function includeFile($path)
+    {
+        // replace / with directory separator for windows
+        $path = str_replace('/', DIRECTORY_SEPARATOR, $path);
+
+        if (isset($this->_included[$path])) {
+            return false;
+        }
+
+        // if the path starts with / or C: then it is an absolute path
+        // otherwise pull it from the libs directory
+        include $path[0] == '/' || $path[1] == ':' ? $path : $this->getPath('libs') . DIRECTORY_SEPARATOR . $path;
+        $this->_included[$path] = true;
+
+        return true;
+    }
+
+    /**
+     * initializes autoloader
+     *
+     * @return void
+     */
+    public function autoload()
+    {
+        spl_autoload_register(array($this, 'autoloader'));
+    }
+
+    /**
+     * sets a setting
+     *
+     * @param string $key
+     * @param mixed $value
+     */
+    public function addSetting($key, $value)
+    {
+        $this->_settings[$key] = $value;
+    }
+
+    /**
+     * gets a setting
+     *
+     * @param string $name
+     * @return mixed
+     */
+    public function getSetting($name)
+    {
+        if (!isset($this->_settings[$name])) {
+            return null;
+        }
+
+        return $this->_settings[$name];
+    }
+
+    /**
+     * returns the config
+     *
+     * first tries to grab it from APC then tries to grab it from instance cache
+     * if neither of those succeed then it will instantiate the config object
+     * and add it to instance cache and/or APC
+     *
+     * @param string $path path to config path
+     * @param string $type (php || ini)
+     * @return Config
+     */
+    public static function getConfig($path = null)
+    {
+        $app = self::getInstance();
+        $environment = $app->getEnvironment();
+
+        $type = $app->getSetting(self::CONFIG_FILE);
+
+        // get the config path
+        if ($path === null) {
+            $path = $app->getPath('configs') . '/app.' . $type;
+        }
+
+        // cache key
+        $cache_key =  'config_' . $path . '_' . $environment;
+
+        // if the config is in instance cache return it
+        if (isset($app->_configs[$cache_key])) {
+            return $app->_configs[$cache_key];
+        }
+
+        // we need to load the util and config object before it fetches it from APC
+        $app->includeFile('Sonic/Util.php');
+        $app->includeFile('Sonic/Config.php');
+
+        // if we are not dev let's try to grab it from APC
+        if (!self::isDev() && !$app->getSetting(self::DISABLE_APC) && ($config = apc_fetch($cache_key))) {
+            $app->_configs[$cache_key] = $config;
+            return $config;
+        }
+
+        // if we have gotten here then that means the config exists so we
+        // now need to get the environment name and load the config
+        $config = new Config($path, $environment, $type);
+        $app->_configs[$cache_key] = $config;
+
+        if (!self::isDev() && !$app->getSetting(self::DISABLE_APC)) {
+            apc_store($cache_key, $config, Util::toSeconds('24 hours'));
+        }
+
+        return $config;
+    }
+
+    /**
+     * is this dev mode?
+     *
+     * @return bool
+     */
+    public static function isDev()
+    {
+        $app = self::getInstance();
+        return in_array($app->getEnvironment(), $app->getSetting(self::DEVS));
+    }
+
+    /**
+     * gets apache/unix environment name
+     *
+     * @return string
+     */
+    public function getEnvironment()
+    {
+        if ($env = $this->getSetting(self::ENVIRONMENT)) {
+            return $env;
+        }
+
+        if ($env = getenv('ENVIRONMENT')) {
+            $this->addSetting(self::ENVIRONMENT, $env);
+            return $env;
+        }
+
+        throw new Exception('ENVIRONMENT variable is not set! check your apache config');
+    }
+
+    /**
+     * gets the request object
+     *
+     * @return Request
+     */
+    public function getRequest()
+    {
+        if (!$this->_request) {
+            $this->_request = new Request();
+        }
+        return $this->_request;
+    }
+
+    /**
+     * overrides base path
+     *
+     * @param string $dir
+     * @return void
+     */
+    public function setBasePath($path)
+    {
+        $this->_base_path = $path;
+    }
+
+    /**
+     * gets base path of the app
+     *
+     * @return string
+     */
+    public function getBasePath()
+    {
+        if ($this->_base_path) {
+            return $this->_base_path;
+        }
+
+        throw new \Exception('base path must be set before App::start() is called');
+    }
+
+    /**
+     * overrides a default path
+     *
+     * @param string $dir
+     * @param string $path
+     * @return void
+     */
+    public function setPath($dir, $path)
+    {
+        $this->_paths['path_' . $dir] = $path;
+    }
+
+    /**
+     * gets the absolute path to a directory
+     *
+     * @param string $dir (views || controllers || lib) etc
+     * @return string
+     */
+    public function getPath($dir = null)
+    {
+        $cache_key =  'path_' . $dir;
+
+        if (isset($this->_paths[$cache_key])) {
+            return $this->_paths[$cache_key];
+        }
+
+        $base_path = $this->getBasePath();
+
+        if ($dir !== null) {
+            $base_path .= DIRECTORY_SEPARATOR . $dir;
+        }
+
+        $this->_paths[$cache_key] = $base_path;
+        return $this->_paths[$cache_key];
+    }
+
+    /**
+     * globally disables layout
+     *
+     * @return void
+     */
+    public function disableLayout()
+    {
+        $this->_layout_processed = true;
+    }
+
+    /**
+     * gets a controller by name
+     *
+     * @param string $name
+     * @return Controller
+     */
+    public function getController($name)
+    {
+        $name = strtolower($name);
+
+        // controller has not been instantiated yet
+        if (!isset($this->_controllers[$name])) {
+            $path = $this->getPath('controllers') . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $name) . '.php';
+            $success = include $path;
+            if (!$success) {
+                throw new Exception('controller does not exist at path: ' . $path);
+            }
+            $class_name = '\Controllers\\' . $name;
+            $this->_controllers[$name] = new $class_name;
+            $this->_controllers[$name]->name($name);
+        }
+
+        return $this->_controllers[$name];
+    }
+
+    /**
+     * runs a controller and action combination
+     *
+     * @param string $controller_name controller to use
+     * @param string $action method within controller to execute
+     * @param array $args arguments to be added to the Request object and view
+     * @param bool $json should we render json
+     * @param string $id view id for if we are in turbo mode an exception is thrown
+     * @return void
+     */
+    protected function _runController($controller_name, $action, $args = array(), $json = false, $id = null)
+    {
+        $this->getRequest()->addParams($args);
+
+        $controller = $this->getController($controller_name);
+        $controller->setView($action, false);
+
+        // if we are requesting JSON that means this is being processed from the turbo queue
+        // if we are not in turbo mode then we run the action normally
+        $can_run = $json || !$this->getSetting(self::TURBO);
+
+        if ($this->_delegate) {
+            $this->_delegate->actionWasCalled($controller, $action, $args);
+        }
+
+        if ($can_run) {
+            $this->_runAction($controller, $action, $args);
+        }
+
+        $view = null;
+        if ($controller->hasView()) {
+            $view = $controller->getView();
+            $view->setAction($action);
+            $view->addVars($args);
+        }
+
+        // process the layout if we can
+        // this takes care of handling this view
+        if ($this->_processLayout($controller, $view, $args)) {
+            return;
+        }
+
+        if (!$view) {
+            return;
+        }
+
+        if ($this->_delegate) {
+            $this->_delegate->viewStartedRendering($view, $json);
+        }
+
+        // output the view contents
+        $view->output($json, $id);
+
+        if ($this->_delegate) {
+            $this->_delegate->viewFinishedRendering($view, $json);
+        }
+    }
+
+    /**
+     * processes the layout if it needs to be processed
+     *
+     * @param Controller $controller
+     * @param View $view
+     * @param array $args
+     * @return bool
+     */
+    protected function _processLayout(Controller $controller, View $view = null, $args)
+    {
+        // if the layout was already processed ignore this call
+        if ($this->_layout_processed) {
+            return false;
+        }
+
+        // if the controller doesn't have a layout ignore this call
+        if (!$controller->hasLayout()) {
+            return false;
+        }
+
+        // if this is not the first controller and not an exception, ignore
+        if (count($this->_controllers) != 1 && !isset($args['exception'])) {
+            return false;
+        }
+
+        // process the layout!
+        $this->_layout_processed = true;
+        $layout = $controller->getLayout();
+
+        $layout->topView($view ?: new View);
+
+        if ($this->_delegate) {
+            $this->_delegate->layoutStartedRendering($layout);
+        }
+
+        $layout->output();
+
+        if ($this->_delegate) {
+            $this->_delegate->layoutFinishedRendering($layout);
+        }
+
+        return true;
+    }
+
+    /**
+     * runs a specific action in a controller
+     *
+     * @param Controller $controller
+     * @param string $action
+     * @return void
+     */
+    protected function _runAction(Controller $controller, $action, array $args = array())
+    {
+        if ($this->_delegate) {
+            $this->_delegate->actionStartedRunning($controller, $action, $args);
+        }
+
+        $controller->$action();
+        $controller->actionComplete($action);
+
+        if ($this->_delegate) {
+            $this->_delegate->actionFinishedRunning($controller, $action, $args);
+        }
+    }
+
+    /**
+     * public access to run a controller (handles exceptions)
+     *
+     * @param string $controller_name controller to use
+     * @param string $action method within controller to execute
+     * @param array $args arguments to be added to the Request object and view
+     * @param bool $json should we render json?
+     * @param string $controller_name
+     */
+    public function runController($controller_name, $action, $args = array(), $json = false)
+    {
+        try {
+            $this->_runController($controller_name, $action, $args, $json);
+        } catch (\Exception $e) {
+            $this->handleException($e, $controller_name, $action);
+            return;
+        }
+    }
+
+    /**
+     * queues up a view for later processing
+     *
+     * only happens in turbo mode
+     *
+     * @param string
+     * @param string
+     * @return void
+     */
+    public function queueView($controller, $name)
+    {
+        $this->_queued[] = array($controller, $name);
+    }
+
+    /**
+     * processes queued up views for turbo mode
+     *
+     * @return void
+     */
+    public function processViewQueue()
+    {
+        if (!$this->getSetting(self::TURBO)) {
+            return;
+        }
+
+        while (count($this->_queued)) {
+            foreach ($this->_queued as $key => $queue) {
+                $this->runController($queue[0], $queue[1], array(), true);
+                unset($this->_queued[$key]);
+            }
+        }
+    }
+
+    /**
+     * handles an exception when loading a page
+     *
+     * @param Exception $e
+     * @param string $controller name of controller
+     * @param string $action name of action
+     * @return void
+     */
+    public function handleException(\Exception $e, $controller = null, $action = null)
+    {
+        if ($this->_delegate) {
+            $this->_delegate->appCaughtException($e, $controller, $action);
+        }
+
+        // turn other exceptions into sonic exceptions
+        if (!$e instanceof Exception) {
+            $e = new Exception($e->getMessage(), Exception::INTERNAL_SERVER_ERROR, $e);
+        }
+
+        // only set the http code if output hasn't started
+        if (!headers_sent()) {
+            header($e->getHttpCode());
+        }
+
+        $json = false;
+        $id = null;
+
+        // in turbo mode we have to write the exception markup out to the
+        // same div created before the exception was triggered.  this means
+        // we have to get the id based on the controller and action that the
+        // exception came from
+        if ($this->getSetting(self::TURBO) && $this->_layout_processed) {
+            $json = true;
+            $id = View::generateId($controller, $action);
+        }
+
+        $completed = false;
+
+        // controller and action are only null if this is a page not found
+        // because we were not able to match any routes.  in all other cases
+        // we can get the initial controller and action to determine if it has
+        // completed
+        if ($controller !== null && $action !== null) {
+            $req = $this->getRequest();
+            $first_controller = $req->getControllerName();
+            $first_action = $req->getAction();
+            $completed = $this->getController($first_controller)->hasCompleted($first_action);
+        }
+
+        $args = array(
+            'exception' => $e,
+            'top_level_exception' => !$completed,
+            'from_controller' => $controller,
+            'from_action' => $action
+        );
+
+        return $this->_runController('main', 'error', $args, $json, $id);
+    }
+
+    /**
+     * determines if we should turn off turbo mode
+     *
+     * @return bool
+     */
+    protected function _robotnikWins()
+    {
+        if ($this->getRequest()->isAjax() || isset($_COOKIE['noturbo']) || isset($_COOKIE['bot'])) {
+            return true;
+        }
+
+        if (isset($_GET['noturbo'])) {
+            setcookie('noturbo', true, time() + 86400);
+            return true;
+        }
+
+        if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
+            setcookie('bot', true, time() + 86400);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * sets a delegate class to receive events as the application runs
+     *
+     * @param string $delegate name of delegate class
+     * @return \Sonic\App
+     */
+    public function setDelegate($delegate)
+    {
+        $this->includeFile('Sonic/App/Delegate.php');
+        $this->autoloader($delegate);
+
+        $delegate = new $delegate;
+
+        if (!$delegate instanceof App\Delegate) {
+            throw new \Exception('app delegate of class ' . get_class($delegate) . ' must be instance of \Sonic\App\Delegate');
+        }
+
+        $this->_delegate = $delegate;
+        $this->_delegate->setApp($this);
+        return $this;
+    }
+
+    /**
+     * loads an extension by name
+     *
+     * @param string $name
+     * @return App
+     */
+    public function loadExtension($name)
+    {
+        // if this is already loaded don't do anything
+        if ($this->extensionLoaded($name)) {
+            return $this;
+        }
+
+        $name = strtolower($name);
+
+        // first grab the extension installation data
+        $extensions = $this->getSetting(self::EXTENSION_DATA);
+        if (!$extensions) {
+            $path = $this->getPath('extensions/installed.json');
+
+            if (file_exists($path)) {
+                $extensions = json_decode(file_get_contents($path), true);
+                $this->addSetting(self::EXTENSION_DATA, $extensions);
+            }
+        }
+
+        if (!isset($extensions[$name])) {
+            throw new Exception('trying to load extension "' . $name . '" which is not installed!');
+        }
+
+        // get the data related to this extension
+        $data = $extensions[$name];
+
+        // create a delegate object if this extension has one
+        $delegate = null;
+        if (isset($data['delegate_path']) && isset($data['delegate'])) {
+            $this->includeFile('Sonic/Extension/Delegate.php');
+            $this->includeFile($this->getPath($data['delegate_path']));
+            $delegate = new $data['delegate'];
+        }
+
+        if ($delegate) {
+            $delegate->extensionStartedLoading();
+        }
+
+        $base_path = $this->getPath();
+
+        $core = 'extensions/' . $name . '/Core.php';
+        $has_core = isset($data['has_core']) && $data['has_core'];
+        $dev = isset($data['dev']) && $data['dev'];
+
+        foreach ($data['files'] as $file) {
+
+            // if the file is not in the extensions or libs directory then skip it
+            // we don't want to load controllers/views/etc. here
+            $lib_file = strpos($file, 'libs') === 0;
+
+            // don't load libs files unless the extension says to explicitly
+            if ($lib_file && !$data['load_libs']) {
+                continue;
+            }
+
+            if (strpos($file, 'extensions') !== 0 && !$lib_file) {
+                continue;
+            }
+
+            // if this is not a PHP file then skip it
+            if (substr($file, -4) != '.php') {
+                continue;
+            }
+
+            // skip core in dev mode
+            if ($dev && $file == $core) {
+                continue;
+            }
+
+            // if this is a file that is not in libs and not core then skip it
+            if (!$dev && !$lib_file && $has_core && $file != $core) {
+                continue;
+            }
+
+            $this->includeFile($base_path . '/' . $file);
+
+            if ($delegate) {
+                $delegate->extensionLoadedFile($file);
+            }
+        }
+
+        $loaded = $this->getSetting(self::EXTENSIONS_LOADED);
+        $loaded[] = $name;
+        $this->addSetting(self::EXTENSIONS_LOADED, $loaded);
+
+        if ($delegate) {
+            $delegate->extensionFinishedLoading();
+        }
+
+        return $this;
+    }
+
+    /**
+     * determines if an extension is loaded
+     *
+     * @param string $name
+     * @return bool
+     */
+    public function extensionLoaded($name)
+    {
+        $loaded = $this->getSetting(self::EXTENSIONS_LOADED);
+        return in_array(strtolower($name), $loaded);
+    }
+
+    /**
+     * gets an extension helper for this extension
+     *
+     * @param string $name
+     * @return \Sonic\Extension\Helper
+     */
+    public function extension($name)
+    {
+        $this->includeFile('Sonic/Extension/Helper.php');
+        return Extension\Helper::forExtension($name);
+    }
+
+    /**
+     * pushes over the first domino
+     *
+     * @param string $mode
+     * @return void
+     */
+    public function start($mode = self::WEB)
+    {
+        $lib = $this->getPath('libs') . DIRECTORY_SEPARATOR . 'Sonic' . DIRECTORY_SEPARATOR;
+        try {
+            if ($this->_delegate) {
+                $this->_delegate->appStartedLoading($mode);
+            }
+
+            $this->addSetting(self::MODE, $mode);
+
+            require_once $lib . 'Exception.php';
+            require_once $lib . 'Request.php';
+            require_once $lib . 'Router.php';
+            require_once $lib . 'Controller.php';
+            require_once $lib . 'View.php';
+            require_once $lib . 'Layout.php';
+
+            if ($this->getSetting(self::AUTOLOAD)) {
+                $this->autoload();
+            }
+
+            if ($this->_delegate) {
+                $this->_delegate->appFinishedLoading();
+            }
+
+            // if we are calling this app from command line then all we want to do
+            // is load the core application files
+            if ($mode != self::WEB) {
+                return;
+            }
+
+            if ($this->getSetting(self::TURBO) && $this->_robotnikWins()) {
+                $this->addSetting(self::TURBO, false);
+            }
+
+            // try to get the controller and action
+            // if an exception is thrown that means the page requested does not exist
+            $controller = $this->getRequest()->getControllerName();
+            $action = $this->getRequest()->getAction();
+
+            if ($this->_delegate) {
+                $this->_delegate->appStartedRunning();
+            }
+
+            $this->runController($controller, $action);
+
+            if ($this->_delegate) {
+                $this->_delegate->appFinishedRunning();
+            }
+        } catch (\Exception $e) {
+            $this->handleException($e);
+        }
+    }
+}
+
+
+ + + + + diff --git a/demos/php.html b/demos/php.html new file mode 100644 index 00000000..57e54e0e --- /dev/null +++ b/demos/php.html @@ -0,0 +1,89 @@ + + +Syntax Highlighting + + + +
+// this is some sample php code
+$i = 0;
+for ($i = 0; $i < 25; ++$i) {
+    echo $i;
+}
+
+# comment like this
+function customFunction()
+{
+    return mt_rand(1, 100);
+}
+
+while ($test) {
+    echo 'blah' . "\n";
+};
+
+$fruits = array('banana', 'strawberry', 'blueberry', 'apple', 'blackberry');
+
+asort($fruits);
+
+foreach ($fruits as $key => $value) {
+    echo $value;
+}
+
+ +
+<?php
+namespace Sonic;
+
+/**
+ * Util
+ *
+ * @category Sonic
+ * @package Util
+ * @author Craig Campbell
+ */
+class Util
+{
+    /**
+     * deletes a directory recursively
+     *
+     * php's native rmdir() function only removes a directory if there is nothing in it
+     *
+     * @param string $path
+     * @return void
+     */
+    public static function removeDir($path)
+    {
+        if (is_link($path)) {
+            return unlink($path);
+        }
+
+        $files = new \RecursiveDirectoryIterator($path);
+        foreach ($files as $file) {
+            if (in_array($file->getFilename(), array('.', '..'))) {
+                continue;
+            }
+
+            if ($file->isLink()) {
+                unlink($file->getPathName());
+                continue;
+            }
+
+            if ($file->isFile()) {
+                unlink($file->getRealPath());
+                continue;
+            }
+
+            if ($file->isDir()) {
+                self::removeDir($file->getRealPath());
+            }
+        }
+        return rmdir($path);
+    }
+}
+
+
+ + + + + diff --git a/demos/python.html b/demos/python.html new file mode 100644 index 00000000..c6c60723 --- /dev/null +++ b/demos/python.html @@ -0,0 +1,92 @@ + + +Syntax Highlighting + + + +
+#!/usr/bin/env python
+# Copyright 2012 Craig Campbell
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import re, os, math
+from util import Util
+from instrument import Instrument
+
+class WarpWhistle(object):
+    TEMPO = 'tempo'
+    VOLUME = 'volume'
+    TIMBRE = 'timbre'
+    ARPEGGIO = 'arpeggio'
+    INSTRUMENT = 'instrument'
+    PITCH = 'pitch'
+    OCTAVE = 'octave'
+    SLIDE = 'slide'
+    Q = 'q'
+
+    ABSOLUTE_NOTES = 'X-ABSOLUTE-NOTES'
+    TRANSPOSE = 'X-TRANSPOSE'
+    COUNTER = 'X-COUNTER'
+    X_TEMPO = 'X-TEMPO'
+    SMOOTH = 'X-SMOOTH'
+    N106 = 'EX-NAMCO106'
+    FDS = 'EX-DISKFM'
+    VRC6 = 'EX-VRC6'
+    PITCH_CORRECTION = 'PITCH-CORRECTION'
+
+    CHIP_N106 = 'N106'
+    CHIP_FDS = 'FDS'
+    CHIP_VRC6 = 'VRC6'
+
+    def __init__(self, content, logger, options):
+        self.first_run = True
+
+        # current voice we are processing if we are processing voices separately
+        self.process_voice = None
+
+        # list of voices to process
+        self.voices_to_process = None
+
+        # list of voices
+        self.voices = None
+
+        self.content = content
+        self.logger = logger
+        self.options = options
+        self.reset()
+
+    def reset(self):
+        """
+        This method resets the properties of the instance.
+        """
+        self.current_voices = []
+        self.global_vars = {}
+        self.vars = {}
+        self.instruments = {}
+        self.data = {}
+        self.global_lines = []
+
+    def getDataForVoice(self, voice, key):
+        if not voice in self.data:
+            return None
+
+        if not key in self.data[voice]:
+            return None
+
+        return self.data[voice][key]
+
+ + + + + diff --git a/demos/r.html b/demos/r.html new file mode 100644 index 00000000..e448efdb --- /dev/null +++ b/demos/r.html @@ -0,0 +1,60 @@ + + + + R Demo + + + +
+## Probability density function for the Generalised Normal Laplace distribution
+dgnl <- function(x, mu = 0, sigma = 1, alpha = 1, beta = 1, rho = 1,
+                 param = c(mu, sigma, alpha, beta, rho)) {
+
+  ## check parameters
+  parResult <- gnlCheckPars(param)
+  case <- parResult$case
+  errMessage <- parResult$errMessage
+
+  if (case == "error")
+    stop(errMessage)
+
+  mu <- param[1]
+  sigma <- param[2]
+  alpha <- param[3]
+  beta <- param[4]
+  rho <- param[5]
+
+  ## Shifting by mu
+  x <- x - mu
+
+  ## Initialising result vector
+  pdfValues <- rep(0, length(x))
+  
+  ## Because 'integrate' doesn't take vectors as input, we need to iterate over
+  ## x to evaluate densities
+  for (i in 1:length(x)) {
+    ## Modified characteristic function. Includes minor calculation regarding
+    ## complex numbers to ensure the function returns a real number
+    chfn <- function(s) {
+      result <- (alpha * beta * exp(-((sigma^2 * s^2) / 2))) /
+                (complex(real = alpha, imaginary = -s) *
+                 complex(real = beta, imaginary = s))
+      result <- result^rho  ## Scaling result by rho
+      r <- Mod(result)
+      theta <- Arg(result)  
+      r * cos(theta - (s * x[i]))
+    }
+    
+    ## Integrating modified characteristic function
+    pdfValues[i] <- (1 / pi) * integrate(chfn, 0, Inf)$value
+  }
+  
+  ## Returning vector of densities
+  pdfValues
+}
+
+ + + + + diff --git a/demos/ruby-test.html b/demos/ruby-test.html new file mode 100644 index 00000000..6776246b --- /dev/null +++ b/demos/ruby-test.html @@ -0,0 +1,153 @@ + + + + Ruby Test + + + +
+# Comments
+not_comment # comment
+
+=begin
+comment
+=end
+
+  =begin
+  not_comment
+  =end
+
+#string interpolation
+12.times do |x|
+  "Chapter #{x+1}:"
+end
+# Strings
+'string'
+"string"
+%q(string)
+%q[string]
+%q{string}
+%q<string>
+%q|string|
+%Q(string)
+%Q[string]
+%Q{string}
+%Q<string>
+%Q|string|
+
+foo('string', 'string')
+
+"unsupported\"string"
+
+# Heredocs
+if true
+  DOC = foo(<<-DOC)
+heredoc
+  xxx
+    xxx
+  DOC
+  # ^heredoc ends here
+DOC
+end
+
+if true
+  DOC = foo(<<DOC)
+heredoc
+  xxx
+    xxx
+  DOC
+DOC
+# ^heredoc ends here
+end
+
+# Symbols
+:symbol
+:'long symbol'
+:"long symbol"
+
+# Regular Expressions
+/regex/xxx
+%r(regex)xxx
+%r[regex]xxx
+%r{regex}xxx
+%r<regex>xxx
+%r|regex|xxx
+
+foo(/regex/xxx, /regex/xxx)
+@path.sub(/^#{@root}/, '')
+
+/unsupported\/regex/
+
+# Classes
+class Test < Object
+  attr_accessor :z
+end
+
+x = Test.method(1, 2)
+x = Test::method(1, 2)
+x = Test::CONSTANT
+
+# Methods
+def method(x, y)
+  z = 3
+end
+
+def self.method(x, y)
+  z = 3
+end
+
+# Sigils
+$stderr.puts 3
+@@foo = 3
+@foo = 3
+
+# Data Structures
+[:value]
+['value']
+{:key=>'value'}
+{:key => 'value'}
+{'key' => 'value'}
+{key: 'value'}
+foo(:key => 'value')
+foo(key: 'value')
+
+# Classes, modules, etc.
+module Foo
+  CONSTANT = 'An \'escaped\' string'
+  class Bar
+    def self.something
+    begin
+      1 + 1
+    rescue StandardError => e
+      puts "Whoa buddy!"
+    end
+
+    class << self
+      def something
+        1 + 1
+      end
+    end
+
+    def something
+    end
+  end
+end
+
+class MyClass < ::Foo::Bar
+end
+
+foo(::Foo::Bar.something)
+
+
+__END__
+if (parsed)
+  "this should not be classified as a string"
+else
+  raise "Epic Fail"
+end
+
+ + + + + diff --git a/demos/ruby.html b/demos/ruby.html new file mode 100644 index 00000000..b63c5745 --- /dev/null +++ b/demos/ruby.html @@ -0,0 +1,194 @@ + + + + rack/directory.rb + + + +
+# Copyright (c) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
+# 
+# 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 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.
+
+require 'time'
+require 'rack/utils'
+require 'rack/mime'
+
+module Rack
+  # Rack::Directory serves entries below the +root+ given, according to the
+  # path info of the Rack request. If a directory is found, the file's contents
+  # will be presented in an html based index. If a file is found, the env will
+  # be passed to the specified +app+.
+  #
+  # If +app+ is not specified, a Rack::File of the same +root+ will be used.
+
+  class Directory
+    DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
+    DIR_PAGE = <<-PAGE
+<html><head>
+  <title>%s</title>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+  <style type='text/css'>
+table { width:100%%; }
+.name { text-align:left; }
+.size, .mtime { text-align:right; }
+.type { width:11em; }
+.mtime { width:15em; }
+  </style>
+</head><body>
+<h1>%s</h1>
+<hr />
+<table>
+  <tr>
+    <th class='name'>Name</th>
+    <th class='size'>Size</th>
+    <th class='type'>Type</th>
+    <th class='mtime'>Last Modified</th>
+  </tr>
+%s
+</table>
+<hr />
+</body></html>
+    PAGE
+
+    attr_reader :files
+    attr_accessor :root, :path
+
+    def initialize(root, app=nil)
+      @root = F.expand_path(root)
+      @app = app || Rack::File.new(@root)
+    end
+
+    def call(env)
+      dup._call(env)
+    end
+
+    F = ::File
+
+    def _call(env)
+      @env = env
+      @script_name = env['SCRIPT_NAME']
+      @path_info = Utils.unescape(env['PATH_INFO'])
+
+      if forbidden = check_forbidden
+        forbidden
+      else
+        @path = F.join(@root, @path_info)
+        list_path
+      end
+    end
+
+    def check_forbidden
+      return unless @path_info.include? ".."
+
+      body = "Forbidden\n"
+      size = Rack::Utils.bytesize(body)
+      return [403, {"Content-Type" => "text/plain",
+        "Content-Length" => size.to_s,
+        "X-Cascade" => "pass"}, [body]]
+    end
+
+    def list_directory
+      @files = [['../','Parent Directory','','','']]
+      glob = F.join(@path, '*')
+
+      url_head = ([@script_name] + @path_info.split('/')).map do |part|
+        Rack::Utils.escape part
+      end
+
+      Dir[glob].sort.each do |node|
+        stat = stat(node)
+        next  unless stat
+        basename = F.basename(node)
+        ext = F.extname(node)
+
+        url = F.join(*url_head + [Rack::Utils.escape(basename)])
+        size = stat.size
+        type = stat.directory? ? 'directory' : Mime.mime_type(ext)
+        size = stat.directory? ? '-' : filesize_format(size)
+        mtime = stat.mtime.httpdate
+        url << '/'  if stat.directory?
+        basename << '/'  if stat.directory?
+
+        @files << [ url, basename, size, type, mtime ]
+      end
+
+      return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
+    end
+
+    def stat(node, max = 10)
+      F.stat(node)
+    rescue Errno::ENOENT, Errno::ELOOP
+      return nil
+    end
+
+    # TODO: add correct response if not readable, not sure if 404 is the best
+    #       option
+    def list_path
+      @stat = F.stat(@path)
+
+      if @stat.readable?
+        return @app.call(@env) if @stat.file?
+        return list_directory if @stat.directory?
+      else
+        raise Errno::ENOENT, 'No such file or directory'
+      end
+
+    rescue Errno::ENOENT, Errno::ELOOP
+      return entity_not_found
+    end
+
+    def entity_not_found
+      body = "Entity not found: #{@path_info}\n"
+      size = Rack::Utils.bytesize(body)
+      return [404, {"Content-Type" => "text/plain",
+        "Content-Length" => size.to_s,
+        "X-Cascade" => "pass"}, [body]]
+    end
+
+    def each
+      show_path = @path.sub(/^#{@root}/,'')
+      files = @files.map{|f| DIR_FILE % f }*"\n"
+      page  = DIR_PAGE % [ show_path, show_path , files ]
+      page.each_line{|l| yield l }
+    end
+
+    # Stolen from Ramaze
+
+    FILESIZE_FORMAT = [
+      ['%.1fT', 1 << 40],
+      ['%.1fG', 1 << 30],
+      ['%.1fM', 1 << 20],
+      ['%.1fK', 1 << 10],
+    ]
+
+    def filesize_format(int)
+      FILESIZE_FORMAT.each do |format, size|
+        return format % (int.to_f / size) if int >= size
+      end
+
+      int.to_s + 'B'
+    end
+  end
+end
+
+ + + + + diff --git a/demos/scheme.html b/demos/scheme.html new file mode 100644 index 00000000..4c69e6f7 --- /dev/null +++ b/demos/scheme.html @@ -0,0 +1,48 @@ + + +Syntax Highlighting + + + + +

+ +
+
+;; Produces the union of two sets
+(define (set-union set1 set2)
+  (let loop ((set1 set1)
+	     (set2 set2))
+    (if (null? set2)
+	set1
+	(let ((item (car set2)))
+	  (if (member item set1)
+	      (loop set1 (cdr set2))
+	      (loop (cons item set1) (cdr set2)))))))
+
+(define (all? pred coll)
+  (if (null? coll)
+      #t
+      (and (pred (car coll))
+           (all? pred (cdr coll)))))
+
+(define (filter pred coll)
+  (let loop ((ret '())
+             (coll coll))
+    (if (null? coll)
+        (reverse ret)
+        (let ((a (car coll)))
+          (if (pred a)
+              (loop (cons a ret) (cdr coll))
+              (loop ret (cdr coll)))))))
+
+
+ + +
+
+Last modified: Wed Jan 25 22:04:37 CET 2012 + + + + diff --git a/demos/shell.html b/demos/shell.html new file mode 100644 index 00000000..af4adcfc --- /dev/null +++ b/demos/shell.html @@ -0,0 +1,433 @@ + + + + ruby-build + + + +
+#!/usr/bin/env bash
+# Copyright (c) 2011 Sam Stephenson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+RUBY_BUILD_VERSION="20120216"
+
+set -E
+exec 3<&2 # preserve original stderr at fd 3
+
+resolve_link() {
+  $(type -p greadlink readlink | head -1) "$1"
+}
+
+abs_dirname() {
+  local cwd="$(pwd)"
+  local path="$1"
+
+  while [ -n "$path" ]; do
+    cd "${path%/*}"
+    local name="${path##*/}"
+    path="$(resolve_link "$name" || true)"
+  done
+
+  pwd
+  cd "$cwd"
+}
+
+build_failed() {
+  { echo
+    echo "BUILD FAILED"
+    echo
+
+    if ! rmdir "${TEMP_PATH}" 2>/dev/null; then
+      echo "Inspect or clean up the working tree at ${TEMP_PATH}"
+
+      if file_is_not_empty "$LOG_PATH"; then
+        echo "Results logged to ${LOG_PATH}"
+        echo
+        echo "Last 10 log lines:"
+        tail -n 10 "$LOG_PATH"
+      fi
+    fi
+  } >&3
+  exit 1
+}
+
+file_is_not_empty() {
+  local filename="$1"
+  local line_count="$(wc -l "$filename" 2>/dev/null || true)"
+
+  if [ -n "$line_count" ]; then
+    words=( $line_count )
+    [ "${words[0]}" -gt 0 ]
+  else
+    return 1
+  fi
+}
+
+install_package() {
+  install_package_using "tarball" 1 $*
+}
+
+install_git() {
+  install_package_using "git" 2 $*
+}
+
+install_package_using() {
+  local package_type="$1"
+  local package_type_nargs="$2"
+  local package_name="$3"
+  shift 3
+
+  pushd "$TEMP_PATH" >&4
+  "fetch_${package_type}" "$package_name" $*
+  shift $(($package_type_nargs))
+  make_package "$package_name" $*
+  popd >&4
+
+  echo "Installed ${package_name} to ${PREFIX_PATH}" >&2
+}
+
+make_package() {
+  local package_name="$1"
+  shift
+
+  pushd "$package_name" >&4
+  before_install_package "$package_name"
+  build_package "$package_name" $*
+  after_install_package "$package_name"
+  fix_directory_permissions
+  popd >&4
+}
+
+fetch_tarball() {
+  local package_name="$1"
+  local package_url="$2"
+
+  echo "Downloading ${package_url}..." >&2
+  { curl "$package_url" > "${package_name}.tar.gz"
+    tar xzvf "${package_name}.tar.gz"
+  } >&4 2>&1
+}
+
+fetch_git() {
+  local package_name="$1"
+  local git_url="$2"
+  local git_ref="$3"
+
+  echo "Cloning ${git_url}..." >&2
+  { git clone --depth 1 --branch "$git_ref" "$git_url" "${package_name}"
+  } >&4 2>&1
+}
+
+build_package() {
+  local package_name="$1"
+  shift
+
+  if [ "$#" -eq 0 ]; then
+    local commands="standard"
+  else
+    local commands="$*"
+  fi
+
+  echo "Installing ${package_name}..." >&2
+
+  for command in $commands; do
+    "build_package_${command}"
+  done
+}
+
+build_package_standard() {
+  local package_name="$1"
+
+  if [ -z "${MAKEOPTS+defined}" ]; then
+    MAKE_OPTS="$MAKEOPTS"
+  elif [ -z "${MAKE_OPTS+defined}" ]; then
+    MAKE_OPTS="-j 2"
+  fi
+
+  { ./configure --prefix="$PREFIX_PATH" $CONFIGURE_OPTS
+    make $MAKE_OPTS
+    make install
+  } >&4 2>&1
+}
+
+build_package_autoconf() {
+  { autoconf
+  } >&4 2>&1
+}
+
+build_package_ruby() {
+  local package_name="$1"
+
+  { "$RUBY_BIN" setup.rb
+  } >&4 2>&1
+}
+
+build_package_ree_installer() {
+  local options=""
+  if [[ "Darwin" = "$(uname)" ]]; then
+    options="--no-tcmalloc"
+  fi
+
+  # Work around install_useful_libraries crash with --dont-install-useful-gems
+  mkdir -p "$PREFIX_PATH/lib/ruby/gems/1.8/gems"
+
+  { ./installer --auto "$PREFIX_PATH" --dont-install-useful-gems $options $CONFIGURE_OPTS
+  } >&4 2>&1
+}
+
+build_package_rbx() {
+  local package_name="$1"
+
+  { ./configure --prefix="$PREFIX_PATH" --gemsdir="$PREFIX_PATH"
+    rake install
+  } >&4 2>&1
+}
+
+build_package_maglev() {
+  build_package_copy
+
+  { cd "${PREFIX_PATH}"
+    ./install.sh
+    cd "${PREFIX_PATH}/bin"
+    echo "Creating symlink for ruby*"
+    ln -fs maglev-ruby ruby
+    echo "Creating symlink for irb*"
+    ln -fs maglev-irb irb
+  } >&4 2>&1
+  echo
+  echo "Run 'maglev start' to start up the stone before using 'ruby' or 'irb'"
+}
+
+build_package_jruby() {
+  build_package_copy
+  cd "${PREFIX_PATH}/bin"
+  ln -fs jruby ruby
+  install_jruby_launcher
+  remove_windows_files
+}
+
+install_jruby_launcher() {
+  cd "${PREFIX_PATH}/bin"
+  { ./ruby gem install jruby-launcher
+  } >&4 2>&1
+}
+
+remove_windows_files() {
+  cd "$PREFIX_PATH"
+  rm -f bin/*.exe bin/*.dll bin/*.bat bin/jruby.sh
+}
+
+build_package_copy() {
+  mkdir -p "$PREFIX_PATH"
+  cp -R . "$PREFIX_PATH"
+}
+
+before_install_package() {
+  local stub=1
+}
+
+after_install_package() {
+  local stub=1
+}
+
+fix_directory_permissions() {
+  # Ensure installed directories are not world-writable to avoid Bundler warnings
+  find "$PREFIX_PATH" -type d -exec chmod go-w {} \;
+}
+
+require_gcc() {
+  local gcc="$(locate_gcc || true)"
+  if [ -z "$gcc" ]; then
+    { echo
+      echo "ERROR: This package must be compiled with GCC, and we"
+      echo "couldn't find a suitable \`gcc' binary on your system."
+      echo "Please install GCC and try again."
+      echo
+
+      if [ "$(uname -s)" = "Darwin" ]; then
+        echo "As of version 4.2, Xcode is LLVM-only and no longer"
+        echo "includes GCC. You can install GCC with these binary"
+        echo "packages on Mac OS X:"
+        echo
+        echo "https://github.com/kennethreitz/osx-gcc-installer/downloads"
+        echo
+      fi
+    } >&3
+    return 1
+  fi
+
+  export CC="$gcc"
+}
+
+locate_gcc() {
+  local gcc gccs
+  IFS=: gccs=($(gccs_in_path))
+
+  verify_gcc "$CC" ||
+  verify_gcc "$(command -v gcc || true)" || {
+    for gcc in "${gccs[@]}"; do
+      verify_gcc "$gcc" && break || true
+    done
+  }
+
+  return 1
+}
+
+gccs_in_path() {
+  local gcc path paths
+  local gccs=()
+  IFS=: paths=($PATH)
+
+  shopt -s nullglob
+  for path in "${paths[@]}"; do
+    for gcc in "$path"/gcc-*; do
+      gccs["${#gccs[@]}"]="$gcc"
+    done
+  done
+  shopt -u nullglob
+
+  printf :%s "${gccs[@]}"
+}
+
+verify_gcc() {
+  local gcc="$1"
+  if [ -z "$gcc" ]; then
+    return 1
+  fi
+
+  local version="$("$gcc" --version || true)"
+  if [ -z "$version" ]; then
+    return 1
+  fi
+
+  if echo "$version" | grep LLVM >/dev/null; then
+    return 1
+  fi
+
+  echo "$gcc"
+}
+
+version() {
+  echo "ruby-build ${RUBY_BUILD_VERSION}"
+}
+
+usage() {
+  { version
+    echo "usage: ruby-build [-v|--verbose] definition prefix"
+    echo "       ruby-build --definitions"
+  } >&2
+
+  if [ -z "$1" ]; then
+    exit 1
+  fi
+}
+
+list_definitions() {
+  { for definition in "${RUBY_BUILD_ROOT}/share/ruby-build/"*; do
+      echo "${definition##*/}"
+    done
+  } | sort
+}
+
+
+
+unset VERBOSE
+RUBY_BUILD_ROOT="$(abs_dirname "$0")/.."
+
+case "$1" in
+"-h" | "--help" )
+  usage without_exiting
+  { echo
+    echo "  -v/--verbose     Verbose mode: print compilation status to stdout"
+    echo "  --definitions    List all built-in definitions"
+    echo
+  } >&2
+  exit 0
+  ;;
+"--definitions" )
+  list_definitions
+  exit 0
+  ;;
+"--version" )
+  version
+  exit 0
+  ;;
+"-v" | "--verbose" )
+  VERBOSE=true
+  shift
+  ;;
+esac
+
+
+DEFINITION_PATH="$1"
+if [ -z "$DEFINITION_PATH" ]; then
+  usage
+elif [ ! -e "$DEFINITION_PATH" ]; then
+  BUILTIN_DEFINITION_PATH="${RUBY_BUILD_ROOT}/share/ruby-build/${DEFINITION_PATH}"
+  if [ -e "$BUILTIN_DEFINITION_PATH" ]; then
+    DEFINITION_PATH="$BUILTIN_DEFINITION_PATH"
+  else
+    echo "ruby-build: definition not found: ${DEFINITION_PATH}" >&2
+    exit 1
+  fi
+fi
+
+PREFIX_PATH="$2"
+if [ -z "$PREFIX_PATH" ]; then
+  usage
+fi
+
+if [ -z "$TMPDIR" ]; then
+  TMP="/tmp"
+else
+  TMP="${TMPDIR%/}"
+fi
+
+SEED="$(date "+%Y%m%d%H%M%S").$$"
+LOG_PATH="${TMP}/ruby-build.${SEED}.log"
+TEMP_PATH="${TMP}/ruby-build.${SEED}"
+RUBY_BIN="${PREFIX_PATH}/bin/ruby"
+CWD="$(pwd)"
+
+exec 4<> "$LOG_PATH" # open the log file at fd 4
+if [ -n "$VERBOSE" ]; then
+  tail -f "$LOG_PATH" &
+  trap "kill 0" SIGINT SIGTERM EXIT
+fi
+
+export LDFLAGS="-L'${PREFIX_PATH}/lib' ${LDFLAGS}"
+export CPPFLAGS="-I'${PREFIX_PATH}/include' ${CPPFLAGS}"
+
+unset RUBYOPT
+unset RUBYLIB
+
+trap build_failed ERR
+mkdir -p "$TEMP_PATH"
+source "$DEFINITION_PATH"
+rm -fr "$TEMP_PATH"
+trap - ERR
+
+ + + + + diff --git a/js/language/c.js b/js/language/c.js new file mode 100644 index 00000000..79b02502 --- /dev/null +++ b/js/language/c.js @@ -0,0 +1,69 @@ +/** + * C patterns + * + * @author Daniel Holden + * @author Craig Campbell + * @version 1.0.7 + */ +Rainbow.extend('c', [ + { + 'name': 'meta.preprocessor', + 'matches': { + 1: [ + { + 'matches': { + 1: 'keyword.define', + 2: 'entity.name' + }, + 'pattern': /(\w+)\s(\w+)\b/g + }, + { + 'name': 'keyword.define', + 'pattern': /endif/g + }, + { + 'name': 'constant.numeric', + 'pattern': /\d+/g + }, + { + 'matches': { + 1: 'keyword.include', + 2: 'string' + }, + 'pattern': /(include)\s(.*?)$/g + } + ] + }, + 'pattern': /\#([\S\s]*?)$/gm + }, + { + 'name': 'keyword', + 'pattern': /\b(do|goto|typedef)\b/g + }, + { + 'name': 'entity.label', + 'pattern': /\w+:/g + }, + { + 'matches': { + 1: 'storage.type', + 3: 'storage.type', + 4: 'entity.name.function' + }, + 'pattern': /\b((un)?signed|const)? ?(void|char|short|int|long|float|double)\*? +((\w+)(?= ?\())?/g + }, + { + 'matches': { + 2: 'entity.name.function' + }, + 'pattern': /(\w|\*) +((\w+)(?= ?\())/g + }, + { + 'name': 'storage.modifier', + 'pattern': /\b(static|extern|auto|register|volatile|inline)\b/g + }, + { + 'name': 'support.type', + 'pattern': /\b(struct|union|enum)\b/g + } +]); diff --git a/js/language/coffeescript.js b/js/language/coffeescript.js new file mode 100644 index 00000000..ecf082e0 --- /dev/null +++ b/js/language/coffeescript.js @@ -0,0 +1,125 @@ +/** + * Coffeescript patterns + * + * @author Craig Campbell + * @version 1.0 + */ +Rainbow.extend('coffeescript', [ + { + 'name': 'comment.block', + 'pattern': /(\#{3})[\s\S]*\1/gm + }, + { + 'name': 'string.block', + 'pattern': /('{3}|"{3})[\s\S]*\1/gm + }, + + /** + * multiline regex with comments + */ + { + 'name': 'string.regex', + 'matches': { + 2: { + 'name': 'comment', + 'pattern': /\#(.*?)\n/g + } + }, + 'pattern': /(\/{3})([\s\S]*)\1/gm + }, + { + 'matches': { + 1: 'keyword' + }, + 'pattern': /\b(in|when|is|isnt|of|not|unless|until|super)(?=\b)/gi + }, + { + 'name': 'keyword.operator', + 'pattern': /\?/g + }, + { + 'name': 'constant.language', + 'pattern': /\b(undefined|yes|on|no|off)\b/g + }, + { + 'name': 'keyword.variable.coffee', + 'pattern': /@(\w+)/gi + }, + + /** + * reset global keywards from generic + */ + { + 'name': 'reset', + 'pattern': /object|class|print/gi + }, + + /** + * named function + */ + { + 'matches' : { + 1: 'entity.name.function', + 2: 'keyword.operator', + 3: { + 'name': 'function.argument.coffee', + 'pattern': /([\@\w]+)/g + }, + 4: 'keyword.function' + }, + 'pattern': /(\w+)\s{0,}(=|:)\s{0,}\((.*?)((-|=)>)/gi + }, + + /** + * anonymous function + */ + { + 'matches': { + 1: { + 'name': 'function.argument.coffee', + 'pattern': /([\@\w]+)/g + }, + 2: 'keyword.function' + }, + 'pattern': /\s\((.*?)\)\s{0,}((-|=)>)/gi + }, + + /** + * direct function no arguments + */ + { + 'matches' : { + 1: 'entity.name.function', + 2: 'keyword.operator', + 3: 'keyword.function' + }, + 'pattern': /(\w+)\s{0,}(=|:)\s{0,}((-|=)>)/gi + }, + + /** + * class definitions + */ + { + 'matches': { + 1: 'storage.class', + 2: 'entity.name.class', + 3: 'storage.modifier.extends', + 4: 'entity.other.inherited-class' + }, + 'pattern': /\b(class)\s(\w+)(\sextends\s)?([\w\\]*)?\b/g + }, + + /** + * object instantiation + */ + { + 'matches': { + 1: 'keyword.new', + 2: { + 'name': 'support.class', + 'pattern': /\w+/g + } + }, + 'pattern': /\b(new)\s(.*?)(?=\s)/g + } +]); diff --git a/js/language/csharp.js b/js/language/csharp.js new file mode 100644 index 00000000..14682da1 --- /dev/null +++ b/js/language/csharp.js @@ -0,0 +1,87 @@ +/** +* C# patterns +* +* @author Dan Stewart +* @version 1.0.1 +*/ +Rainbow.extend('csharp', [ + { + // @see http://msdn.microsoft.com/en-us/library/23954zh5.aspx + 'name': 'constant', + 'pattern': /\b(false|null|true)\b/g + }, + { + // @see http://msdn.microsoft.com/en-us/library/x53a06bb%28v=vs.100%29.aspx + // Does not support putting an @ in front of a keyword which makes it not a keyword anymore. + 'name': 'keyword', + 'pattern': /\b(abstract|add|alias|ascending|as|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|descending|double|do|dynamic|else|enum|event|explicit|extern|false|finally|fixed|float|foreach|for|from|get|global|goto|group|if|implicit|int|interface|internal|into|in|is|join|let|lock|long|namespace|new|object|operator|orderby|out|override|params|partial|private|protected|public|readonly|ref|remove|return|sbyte|sealed|select|set|short|sizeof|stackalloc|static|string|struct|switch|this|throw|try|typeof|uint|unchecked|ulong|unsafe|ushort|using|value|var|virtual|void|volatile|where|while|yield)\b/g + }, + { + 'matches': { + 1: 'keyword', + 2: { + 'name': 'support.class', + 'pattern': /\w+/g + } + }, + 'pattern': /(typeof)\s([^\$].*?)(\)|;)/g + }, + { + 'matches': { + 1: 'keyword.namespace', + 2: { + 'name': 'support.namespace', + 'pattern': /\w+/g + } + }, + 'pattern': /\b(namespace)\s(.*?);/g + }, + { + 'matches': { + 1: 'storage.modifier', + 2: 'storage.class', + 3: 'entity.name.class', + 4: 'storage.modifier.extends', + 5: 'entity.other.inherited-class' + }, + 'pattern': /\b(abstract|sealed)?\s?(class)\s(\w+)(\sextends\s)?([\w\\]*)?\s?\{?(\n|\})/g + }, + { + 'name': 'keyword.static', + 'pattern': /\b(static)\b/g + }, + { + 'matches': { + 1: 'keyword.new', + 2: { + 'name': 'support.class', + 'pattern': /\w+/g + } + + }, + 'pattern': /\b(new)\s([^\$].*?)(?=\)|\(|;|&)/g + }, + { + 'name': 'string', + 'pattern': /(")(.*?)\1/g + }, + { + 'name': 'integer', + 'pattern': /\b(0x[\da-f]+|\d+)\b/g + }, + { + 'name': 'comment', + 'pattern': /\/\*[\s\S]*?\*\/|(\/\/)[\s\S]*?$/gm + }, + { + 'name': 'operator', + // @see http://msdn.microsoft.com/en-us/library/6a71f45d%28v=vs.100%29.aspx + // ++ += + -- -= - <<= << <= => >>= >> >= != ! ~ ^ || && &= & ?? :: : *= * |= %= |= == = + 'pattern': /(\+\+|\+=|\+|--|-=|-|<<=|<<|<=|=>|>>=|>>|>=|!=|!|~|\^|\|\||&&|&=|&|\?\?|::|:|\*=|\*|\/=|%=|\|=|==|=)/g + }, + { + // @see http://msdn.microsoft.com/en-us/library/ed8yd1ha%28v=vs.100%29.aspx + 'name': 'preprocessor', + 'pattern': /(\#if|\#else|\#elif|\#endif|\#define|\#undef|\#warning|\#error|\#line|\#region|\#endregion|\#pragma)[\s\S]*?$/gm + } +], true); diff --git a/js/language/css.js b/js/language/css.js new file mode 100644 index 00000000..e3bc6f53 --- /dev/null +++ b/js/language/css.js @@ -0,0 +1,72 @@ +/** + * CSS patterns + * + * @author Craig Campbell + * @version 1.0.9 + */ +Rainbow.extend('css', [ + { + 'name': 'comment', + 'pattern': /\/\*[\s\S]*?\*\//gm + }, + { + 'name': 'constant.hex-color', + 'pattern': /#([a-f0-9]{3}|[a-f0-9]{6})(?=;|\s|,|\))/gi + }, + { + 'matches': { + 1: 'constant.numeric', + 2: 'keyword.unit' + }, + 'pattern': /(\d+)(px|em|cm|s|%)?/g + }, + { + 'name': 'string', + 'pattern': /('|")(.*?)\1/g + }, + { + 'name': 'support.css-property', + 'matches': { + 1: 'support.vendor-prefix' + }, + 'pattern': /(-o-|-moz-|-webkit-|-ms-)?[\w-]+(?=\s?:)(?!.*\{)/g + }, + { + 'matches': { + 1: [ + { + 'name': 'entity.name.sass', + 'pattern': /&/g + }, + { + 'name': 'direct-descendant', + 'pattern': />/g + }, + { + 'name': 'entity.name.class', + 'pattern': /\.[\w\-_]+/g + }, + { + 'name': 'entity.name.id', + 'pattern': /\#[\w\-_]+/g + }, + { + 'name': 'entity.name.pseudo', + 'pattern': /:[\w\-_]+/g + }, + { + 'name': 'entity.name.tag', + 'pattern': /\w+/g + } + ] + }, + 'pattern': /([\w\ ,\n:\.\#\&\;\-_]+)(?=.*\{)/g + }, + { + 'matches': { + 2: 'support.vendor-prefix', + 3: 'support.css-value' + }, + 'pattern': /(:|,)\s*(-o-|-moz-|-webkit-|-ms-)?([a-zA-Z-]*)(?=\b)(?!.*\{)/g + } +], true); diff --git a/js/language/d.js b/js/language/d.js new file mode 100644 index 00000000..f4c19f24 --- /dev/null +++ b/js/language/d.js @@ -0,0 +1,81 @@ +/** +* D patterns +* +* @author Matthew Brennan Jones +* @version 1.0.1 +*/ +Rainbow.extend('d', [ + { + 'name': 'constant', + 'pattern': /\b(false|null|true)\b/gm + }, + { + // http://dlang.org/lex.html + 'name': 'keyword', + 'pattern': /\b(abstract|alias|align|asm|assert|auto|body|bool|break|byte|case|cast|catch|cdouble|cent|cfloat|char|class|const|continue|creal|dchar|debug|default|delegate|delete|deprecated|do|double|else|enum|export|extern|final|finally|float|for|foreach|foreach_reverse|function|goto|idouble|if|ifloat|immutable|import|in|inout|int|interface|invariant|ireal|is|lazy|long|macro|mixin|module|new|nothrow|null|out|override|package|pragma|private|protected|public|pure|real|ref|return|scope|shared|short|size_t|static|string|struct|super|switch|synchronized|template|this|throw|try|typedef|typeid|typeof|ubyte|ucent|uint|ulong|union|unittest|ushort|version|void|volatile|wchar|while|with|__FILE__|__LINE__|__gshared|__traits|__vector|__parameters)\b/gm + }, + { + 'matches': { + 1: 'keyword', + 2: { + 'name': 'support.class', + 'pattern': /\w+/gm + } + }, + 'pattern': /(typeof)\s([^\$].*?)(\)|;)/gm + }, + { + 'matches': { + 1: 'keyword.namespace', + 2: { + 'name': 'support.namespace', + 'pattern': /\w+/gm + } + }, + 'pattern': /\b(namespace)\s(.*?);/gm + }, + { + 'matches': { + 1: 'storage.modifier', + 2: 'storage.class', + 3: 'entity.name.class', + 4: 'storage.modifier.extends', + 5: 'entity.other.inherited-class' + }, + 'pattern': /\b(abstract|sealed)?\s?(class)\s(\w+)(\sextends\s)?([\w\\]*)?\s?\{?(\n|\})/gm + }, + { + 'name': 'keyword.static', + 'pattern': /\b(static)\b/gm + }, + { + 'matches': { + 1: 'keyword.new', + 2: { + 'name': 'support.class', + 'pattern': /\w+/gm + } + + }, + 'pattern': /\b(new)\s([^\$].*?)(?=\)|\(|;|&)/gm + }, + { + 'name': 'string', + 'pattern': /("|')(.*?)\1/gm + }, + { + 'name': 'integer', + 'pattern': /\b(0x[\da-f]+|\d+)\b/gm + }, + { + 'name': 'comment', + 'pattern': /\/\*[\s\S]*?\*\/|\/\+[\s\S]*?\+\/|(\/\/)[\s\S]*?$/gm + }, + { + // http://dlang.org/operatoroverloading.html + 'name': 'operator', + // / /= &= && & |= || | -= -- - += ++ + <= << < <<= <>= <> > >>>= >>= >= >> >>> != !<>= !<> !<= !< !>= !> ! [ ] $ == = *= * %= % ^^= ^= ^^ ^ ~= ~ @ => : + 'pattern': /(\/|\/=|&=|&&|&|\|=|\|\|\||\-=|\-\-|\-|\+=|\+\+|\+|<=|<<|<|<<=|<>=|<>|>|>>>=|>>=|>=|>>|>>>|!=|!<>=|!<>|!<=|!<|!>=|!>|!|[|]|\$|==|=|\*=|\*|%=|%|\^\^=|\^=|\^\^|\^|~=|~|@|=>|\:)/gm + } +], true); + diff --git a/js/language/generic.js b/js/language/generic.js new file mode 100644 index 00000000..6c1b010d --- /dev/null +++ b/js/language/generic.js @@ -0,0 +1,65 @@ +/** + * Generic language patterns + * + * @author Craig Campbell + * @version 1.0.13 + */ +Rainbow.extend([ + { + 'matches': { + 1: [ + { + 'name': 'keyword.operator', + 'pattern': /\=|\+/g + }, + { + 'name': 'keyword.dot', + 'pattern': /\./g + } + ], + 2: { + 'name': 'string', + 'matches': { + 'name': 'constant.character.escape', + 'pattern': /\\('|"){1}/g + } + } + }, + 'pattern': /(\(|\s|\[|\=|:|\+|\.|\{)(('|")([^\\\1]|\\.)*?(\3))/gm + }, + { + 'name': 'comment', + 'pattern': /\/\*[\s\S]*?\*\/|(\/\/|\#)[\s\S]*?$/gm + }, + { + 'name': 'constant.numeric', + 'pattern': /\b(\d+(\.\d+)?(e(\+|\-)?\d+)?(f|d)?|0x[\da-f]+)\b/gi + }, + { + 'matches': { + 1: 'keyword' + }, + 'pattern': /\b(and|array|as|b(ool(ean)?|reak)|c(ase|atch|har|lass|on(st|tinue))|d(ef|elete|o(uble)?)|e(cho|lse(if)?|xit|xtends|xcept)|f(inally|loat|or(each)?|unction)|global|if|import|int(eger)?|long|new|object|or|pr(int|ivate|otected)|public|return|self|st(ring|ruct|atic)|switch|th(en|is|row)|try|(un)?signed|var|void|while)(?=\(|\b)/gi + }, + { + 'name': 'constant.language', + 'pattern': /true|false|null/g + }, + { + 'name': 'keyword.operator', + 'pattern': /\+|\!|\-|&(gt|lt|amp);|\||\*|\=/g + }, + { + 'matches': { + 1: 'function.call' + }, + 'pattern': /(\w+?)(?=\()/g + }, + { + 'matches': { + 1: 'storage.function', + 2: 'entity.name.function' + }, + 'pattern': /(function)\s(.*?)(?=\()/g + } +]); diff --git a/js/language/go.js b/js/language/go.js new file mode 100644 index 00000000..400c2e97 --- /dev/null +++ b/js/language/go.js @@ -0,0 +1,59 @@ +/** + * GO Language + * + * @author Javier Aguirre + * @version 1.0 + */ +Rainbow.extend('go', [ + { + 'matches': { + 1: { + 'name': 'keyword.operator', + 'pattern': /\=/g + }, + 2: { + 'name': 'string', + 'matches': { + 'name': 'constant.character.escape', + 'pattern': /\\(`|"){1}/g + } + } + }, + 'pattern': /(\(|\s|\[|\=|:)((`|")([^\\\1]|\\.)*?(\3))/gm + }, + { + 'name': 'comment', + 'pattern': /\/\*[\s\S]*?\*\/|(\/\/)[\s\S]*?$/gm + }, + { + 'name': 'constant.numeric', + 'pattern': /\b(\d+(\.\d+)?(e(\+|\-)?\d+)?(f|d)?|0x[\da-f]+)\b/gi + }, + { + 'matches': { + 1: 'keyword' + }, + 'pattern': /\b(break|c(ase|onst|ontinue)|d(efault|efer)|else|fallthrough|for|go(to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)(?=\(|\b)/gi + }, + { + 'name': 'constant.language', + 'pattern': /true|false|null|string|byte|rune|u?int(8|16|32|64)?|float(32|64)|complex(64|128)/g + }, + { + 'name': 'keyword.operator', + 'pattern': /\+|\!|\-|&(gt|lt|amp);|\||\*|\:?=/g + }, + { + 'matches': { + 1: 'function.call' + }, + 'pattern': /(\w+?)(?=\()/g + }, + { + 'matches': { + 1: 'storage.function', + 2: 'entity.name.function' + }, + 'pattern': /(func)\s(.*?)(?=\()/g + } +]); diff --git a/js/language/haskell.js b/js/language/haskell.js new file mode 100644 index 00000000..614afbaf --- /dev/null +++ b/js/language/haskell.js @@ -0,0 +1,94 @@ +/** + * Haskell patterns + * + * @author Bruno Dias + * @version 1.0.1 + */ +//TODO: {-# ... #-} stuff... +Rainbow.extend('haskell', [ + ///- Comments + { + 'name': 'comment', + 'pattern': /\{\-\-[\s\S(\w+)]+[\-\-][\}$]/gm + // /\{\-{2}[\s\S(.*)]+[\-\-][\}$]/gm [multiple lines] + }, + { + 'name': 'comment', + 'pattern': /\-\-(.*)/g + // /\-\-\s(.+)$/gm [single] + }, + ///- End Comments + + ///- Namespace (module) + { + 'matches': { + 1: 'keyword', + 2: 'support.namespace' + }, + 'pattern': /\b(module)\s(\w+)\s[\(]?(\w+)?[\)?]\swhere/g + }, + ///- End Namespace (module) + + ///- Keywords and Operators + { + 'name': 'keyword.operator', + 'pattern': /\+|\!|\-|&(gt|lt|amp);|\/\=|\||\@|\:|\.|\+{2}|\:|\*|\=|#|\.{2}|(\\)[a-zA-Z_]/g + }, + { + 'name': 'keyword', + 'pattern': /\b(case|class|foreign|hiding|qualified|data|family|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|in|otherwise|module|newtype|of|then|type|where)\b/g + }, + { + 'name': 'keyword', + 'pattern': /[\`][a-zA-Z_']*?[\`]/g + }, + ///- End Keywords and Operators + + + ///- Infix|Infixr|Infixl + { + 'matches': { + 1: 'keyword', + 2: 'keyword.operator' + }, + 'pattern': /\b(infix|infixr|infixl)+\s\d+\s(\w+)*/g + }, + ///- End Infix|Infixr|Infixl + + { + 'name': 'entity.class', + 'pattern': /\b([A-Z][A-Za-z0-9_']*)/g + }, + + // From c.js + { + 'name': 'meta.preprocessor', + 'matches': { + 1: [ + { + 'matches': { + 1: 'keyword.define', + 2: 'entity.name' + }, + 'pattern': /(\w+)\s(\w+)\b/g + }, + { + 'name': 'keyword.define', + 'pattern': /endif/g + }, + { + 'name': 'constant.numeric', + 'pattern': /\d+/g + }, + { + 'matches': { + 1: 'keyword.include', + 2: 'string' + }, + 'pattern': /(include)\s(.*?)$/g + } + ] + }, + 'pattern': /^\#([\S\s]*?)$/gm + } +]); diff --git a/js/language/html.js b/js/language/html.js new file mode 100644 index 00000000..31c80f4b --- /dev/null +++ b/js/language/html.js @@ -0,0 +1,132 @@ +/** + * HTML patterns + * + * @author Craig Campbell + * @version 1.0.9 + */ +Rainbow.extend('html', [ + { + 'name': 'source.php.embedded', + 'matches': { + 2: { + 'language': 'php' + } + }, + 'pattern': /<\?=?(?!xml)(php)?([\s\S]*?)(\?>)/gm + }, + { + 'name': 'source.css.embedded', + 'matches': { + 1: { + 'matches': { + 1: 'support.tag.style', + 2: [ + { + 'name': 'entity.tag.style', + 'pattern': /^style/g + }, + { + 'name': 'string', + 'pattern': /('|")(.*?)(\1)/g + }, + { + 'name': 'entity.tag.style.attribute', + 'pattern': /(\w+)/g + } + ], + 3: 'support.tag.style' + }, + 'pattern': /(<\/?)(style.*?)(>)/g + }, + 2: { + 'language': 'css' + }, + 3: 'support.tag.style', + 4: 'entity.tag.style', + 5: 'support.tag.style' + }, + 'pattern': /(<style.*?>)([\s\S]*?)(<\/)(style)(>)/gm + }, + { + 'name': 'source.js.embedded', + 'matches': { + 1: { + 'matches': { + 1: 'support.tag.script', + 2: [ + { + 'name': 'entity.tag.script', + 'pattern': /^script/g + }, + + { + 'name': 'string', + 'pattern': /('|")(.*?)(\1)/g + }, + { + 'name': 'entity.tag.script.attribute', + 'pattern': /(\w+)/g + } + ], + 3: 'support.tag.script' + }, + 'pattern': /(<\/?)(script.*?)(>)/g + }, + 2: { + 'language': 'javascript' + }, + 3: 'support.tag.script', + 4: 'entity.tag.script', + 5: 'support.tag.script' + }, + 'pattern': /(<script(?! src).*?>)([\s\S]*?)(<\/)(script)(>)/gm + }, + { + 'name': 'comment.html', + 'pattern': /<\!--[\S\s]*?-->/g + }, + { + 'matches': { + 1: 'support.tag.open', + 2: 'support.tag.close' + }, + 'pattern': /(<)|(\/?\??>)/g + }, + { + 'name': 'support.tag', + 'matches': { + 1: 'support.tag', + 2: 'support.tag.special', + 3: 'support.tag-name' + }, + 'pattern': /(<\??)(\/|\!?)(\w+)/g + }, + { + 'matches': { + 1: 'support.attribute' + }, + 'pattern': /([a-z-]+)(?=\=)/gi + }, + { + 'matches': { + 1: 'support.operator', + 2: 'string.quote', + 3: 'string.value', + 4: 'string.quote' + }, + 'pattern': /(=)('|")(.*?)(\2)/g + }, + { + 'matches': { + 1: 'support.operator', + 2: 'support.value' + }, + 'pattern': /(=)([a-zA-Z\-0-9]*)\b/g + }, + { + 'matches': { + 1: 'support.attribute' + }, + 'pattern': /\s(\w+)(?=\s|>)(?![\s\S]*<)/g + } +], true); diff --git a/js/language/java.js b/js/language/java.js new file mode 100644 index 00000000..dd560f8c --- /dev/null +++ b/js/language/java.js @@ -0,0 +1,59 @@ +/** +* Java patterns +* +* @author Leo Accend +* @version 1.0.0 +*/ +Rainbow.extend( "java", [ + { + name: "constant", + pattern: /\b(false|null|true|[A-Z_]+)\b/g + }, + { + matches: { + 1: "keyword", + 2: "support.namespace" + }, + pattern: /(import|package)\s(.+)/g + }, + { + // see http://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html + name: "keyword", + pattern: /\b(abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|native|new|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|transient|try|void|volatile|while)\b/g + }, + { + name: "string", + pattern: /(".*?")/g + }, + { + name: "char", + pattern: /(')(.|\\.|\\u[\dA-Fa-f]{4})\1/g + }, + { + name: "integer", + pattern: /\b(0x[\da-f]+|\d+)L?\b/g + }, + { + name: "comment", + pattern: /\/\*[\s\S]*?\*\/|(\/\/).*?$/gm + }, + { + name: "support.annotation", + pattern: /@\w+/g + }, + { + matches: { + 1: "entity.function" + }, + pattern: /([^@\.\s]+)\(/g + }, + { + name: "entity.class", + pattern: /\b([A-Z]\w*)\b/g + }, + { + // see http://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html + name: "operator", + pattern: /(\+{1,2}|-{1,2}|~|!|\*|\/|%|(?:<){1,2}|(?:>){1,3}|instanceof|(?:&){1,2}|\^|\|{1,2}|\?|:|(?:=|!|\+|-|\*|\/|%|\^|\||(?:<){1,2}|(?:>){1,3})?=)/g + } +], true ); diff --git a/js/language/javascript.js b/js/language/javascript.js new file mode 100644 index 00000000..fc881f82 --- /dev/null +++ b/js/language/javascript.js @@ -0,0 +1,93 @@ +/** + * Javascript patterns + * + * @author Craig Campbell + * @version 1.0.9 + */ +Rainbow.extend('javascript', [ + + /** + * matches $. or $( + */ + { + 'name': 'selector', + 'pattern': /(\s|^)\$(?=\.|\()/g + }, + { + 'name': 'support', + 'pattern': /\b(window|document)\b/g + }, + { + 'matches': { + 1: 'support.property' + }, + 'pattern': /\.(length|node(Name|Value))\b/g + }, + { + 'matches': { + 1: 'support.function' + }, + 'pattern': /(setTimeout|setInterval)(?=\()/g + + }, + { + 'matches': { + 1: 'support.method' + }, + 'pattern': /\.(getAttribute|push|getElementById|getElementsByClassName|log|setTimeout|setInterval)(?=\()/g + }, + + /** + * matches any escaped characters inside of a js regex pattern + * + * @see https://github.com/ccampbell/rainbow/issues/22 + * + * this was causing single line comments to fail so it now makes sure + * the opening / is not directly followed by a * + * + * @todo check that there is valid regex in match group 1 + */ + { + 'name': 'string.regexp', + 'matches': { + 1: 'string.regexp.open', + 2: { + 'name': 'constant.regexp.escape', + 'pattern': /\\(.){1}/g + }, + 3: 'string.regexp.close', + 4: 'string.regexp.modifier' + }, + 'pattern': /(\/)(?!\*)(.+)(\/)([igm]{0,3})/g + }, + + /** + * matches runtime function declarations + */ + { + 'matches': { + 1: 'storage', + 3: 'entity.function' + }, + 'pattern': /(var)?(\s|^)(\S*)(?=\s?=\s?function\()/g + }, + + /** + * matches constructor call + */ + { + 'matches': { + 1: 'keyword', + 2: 'entity.function' + }, + 'pattern': /(new)\s+(.*)(?=\()/g + }, + + /** + * matches any function call in the style functionName: function() + */ + { + 'name': 'entity.function', + 'pattern': /(\w+)(?=:\s{0,}function)/g + } +]); diff --git a/js/language/lua.js b/js/language/lua.js new file mode 100644 index 00000000..41306fe7 --- /dev/null +++ b/js/language/lua.js @@ -0,0 +1,59 @@ +/** + * Lua patterns + * + * @author Javier Aguirre + * @version 1.0.1 + */ +Rainbow.extend('lua', [ + { + 'matches': { + 1: { + 'name': 'keyword.operator', + 'pattern': /\=/g + }, + 2: { + 'name': 'string', + 'matches': { + 'name': 'constant.character.escape', + 'pattern': /\\('|"){1}/g + } + } + }, + 'pattern': /(\(|\s|\[|\=)(('|")([^\\\1]|\\.)*?(\3))/gm + }, + { + 'name': 'comment', + 'pattern': /\-{2}\[{2}\-{2}[\s\S]*?\-{2}\]{2}\-{2}|(\-{2})[\s\S]*?$/gm + }, + { + 'name': 'constant.numeric', + 'pattern': /\b(\d+(\.\d+)?(e(\+|\-)?\d+)?(f|d)?|0x[\da-f]+)\b/gi + }, + { + 'matches': { + 1: 'keyword' + }, + 'pattern': /\b((a|e)nd|in|repeat|break|local|return|do|for|then|else(if)?|function|not|if|or|until|while)(?=\(|\b)/gi + }, + { + 'name': 'constant.language', + 'pattern': /true|false|nil/g + }, + { + 'name': 'keyword.operator', + 'pattern': /\+|\!|\-|&(gt|lt|amp);|\||\*|\=|#|\.{2}/g + }, + { + 'matches': { + 1: 'storage.function', + 2: 'entity.name.function' + }, + 'pattern': /(function)\s+(\w+[\:|\.]?\w+?)(?=\()/g + }, + { + 'matches': { + 1: 'support.function' + }, + 'pattern': /\b(print|require|module|\w+\.\w+)(?=\()/g + } +], true); diff --git a/js/language/php.js b/js/language/php.js new file mode 100644 index 00000000..943f37d3 --- /dev/null +++ b/js/language/php.js @@ -0,0 +1,123 @@ +/** + * PHP patterns + * + * @author Craig Campbell + * @version 1.0.8 + */ +Rainbow.extend('php', [ + { + 'name': 'support', + 'pattern': /\becho\b/g + }, + { + 'matches': { + 1: 'variable.dollar-sign', + 2: 'variable' + }, + 'pattern': /(\$)(\w+)\b/g + }, + { + 'name': 'constant.language', + 'pattern': /true|false|null/ig + }, + { + 'name': 'constant', + 'pattern': /\b[A-Z0-9_]{2,}\b/g + }, + { + 'name': 'keyword.dot', + 'pattern': /\./g + }, + { + 'name': 'keyword', + 'pattern': /\b(die|end(for(each)?|switch|if)|case|require(_once)?|include(_once)?)(?=\(|\b)/g + }, + { + 'matches': { + 1: 'keyword', + 2: { + 'name': 'support.class', + 'pattern': /\w+/g + } + }, + 'pattern': /(instanceof)\s([^\$].*?)(\)|;)/g + }, + + /** + * these are the top 50 most used PHP functions + * found from running a script and checking the frequency of each function + * over a bunch of popular PHP frameworks then combining the results + */ + { + 'matches': { + 1: 'support.function' + }, + 'pattern': /\b(array(_key_exists|_merge|_keys|_shift)?|isset|count|empty|unset|printf|is_(array|string|numeric|object)|sprintf|each|date|time|substr|pos|str(len|pos|tolower|_replace|totime)?|ord|trim|in_array|implode|end|preg_match|explode|fmod|define|link|list|get_class|serialize|file|sort|mail|dir|idate|log|intval|header|chr|function_exists|dirname|preg_replace|file_exists)(?=\()/g + }, + { + 'name': 'variable.language.php-tag', + 'pattern': /(<\?(php)?|\?>)/g + }, + { + 'matches': { + 1: 'keyword.namespace', + 2: { + 'name': 'support.namespace', + 'pattern': /\w+/g + } + }, + 'pattern': /\b(namespace|use)\s(.*?);/g + }, + { + 'matches': { + 1: 'storage.modifier', + 2: 'storage.class', + 3: 'entity.name.class', + 4: 'storage.modifier.extends', + 5: 'entity.other.inherited-class', + 6: 'storage.modifier.extends', + 7: 'entity.other.inherited-class' + }, + 'pattern': /\b(abstract|final)?\s?(class|interface|trait)\s(\w+)(\sextends\s)?([\w\\]*)?(\simplements\s)?([\w\\]*)?\s?\{?(\n|\})/g + }, + { + 'name': 'keyword.static', + 'pattern': /self::|static::/g + }, + { + 'matches': { + 1: 'storage.function', + 2: 'support.magic' + }, + 'pattern': /(function)\s(__.*?)(?=\()/g + }, + { + 'matches': { + 1: 'keyword.new', + 2: { + 'name': 'support.class', + 'pattern': /\w+/g + } + }, + 'pattern': /\b(new)\s([^\$].*?)(?=\)|\(|;)/g + }, + { + 'matches': { + 1: { + 'name': 'support.class', + 'pattern': /\w+/g + }, + 2: 'keyword.static' + }, + 'pattern': /([\w\\]*?)(::)(?=\b|\$)/g + }, + { + 'matches': { + 2: { + 'name': 'support.class', + 'pattern': /\w+/g + } + }, + 'pattern': /(\(|,\s?)([\w\\]*?)(?=\s\$)/g + } +]); diff --git a/js/language/python.js b/js/language/python.js new file mode 100644 index 00000000..261195cf --- /dev/null +++ b/js/language/python.js @@ -0,0 +1,84 @@ +/** + * Python patterns + * + * @author Craig Campbell + * @version 1.0.9 + */ +Rainbow.extend('python', [ + /** + * don't highlight self as a keyword + */ + { + 'name': 'variable.self', + 'pattern': /self/g + }, + { + 'name': 'constant.language', + 'pattern': /None|True|False|NotImplemented|\.\.\./g + }, + { + 'name': 'support.object', + 'pattern': /object/g + }, + + /** + * built in python functions + * + * this entire list is 580 bytes minified / 379 bytes gzipped + * + * @see http://docs.python.org/library/functions.html + * + * @todo strip some out or consolidate the regexes with matching patterns? + */ + { + 'name': 'support.function.python', + 'pattern': /\b(bs|divmod|input|open|staticmethod|all|enumerate|int|ord|str|any|eval|isinstance|pow|sum|basestring|execfile|issubclass|print|super|bin|file|iter|property|tuple|bool|filter|len|range|type|bytearray|float|list|raw_input|unichr|callable|format|locals|reduce|unicode|chr|frozenset|long|reload|vars|classmethod|getattr|map|repr|xrange|cmp|globals|max|reversed|zip|compile|hasattr|memoryview|round|__import__|complex|hash|min|set|apply|delattr|help|next|setattr|buffer|dict|hex|object|slice|coerce|dir|id|oct|sorted|intern)(?=\()/g + }, + { + 'matches': { + 1: 'keyword' + }, + 'pattern': /\b(pass|lambda|with|is|not|in|from|elif|raise|del)(?=\(|\b)/g + }, + { + 'matches': { + 1: 'storage.class', + 2: 'entity.name.class', + 3: 'entity.other.inherited-class' + }, + 'pattern': /(class)\s+(\w+)\((\w+?)\)/g + }, + { + 'matches': { + 1: 'storage.function', + 2: 'support.magic' + }, + 'pattern': /(def)\s+(__\w+)(?=\()/g + }, + { + 'name': 'support.magic', + 'pattern': /__(name)__/g + }, + { + 'matches': { + 1: 'keyword.control', + 2: 'support.exception.type' + }, + 'pattern': /(except) (\w+):/g + }, + { + 'matches': { + 1: 'storage.function', + 2: 'entity.name.function' + }, + 'pattern': /(def)\s+(\w+)(?=\()/g + }, + { + 'name': 'entity.name.function.decorator', + 'pattern': /@([\w\.]+)/g + }, + { + 'name': 'comment.docstring', + 'pattern': /('{3}|"{3})[\s\S]*?\1/gm + } +]); diff --git a/js/language/r.js b/js/language/r.js new file mode 100644 index 00000000..86e11e95 --- /dev/null +++ b/js/language/r.js @@ -0,0 +1,89 @@ +/** + * R language patterns + * + * @author Simon Potter + * @version 1.0 + */ +Rainbow.extend('r', [ + /** + * Note that a valid variable name is of the form: + * [.a-zA-Z][0-9a-zA-Z._]* + */ + { + 'matches': { + 1: { + 'name': 'keyword.operator', + 'pattern': /\=|<\-|<-/g + }, + 2: { + 'name': 'string', + 'matches': { + 'name': 'constant.character.escape', + 'pattern': /\\('|"){1}/g + } + } + }, + 'pattern': /(\(|\s|\[|\=|:)(('|")([^\\\1]|\\.)*?(\3))/gm + }, + + /** + * Most of these are known via the Language Reference. + * The built-in constant symbols are known via ?Constants. + */ + { + 'matches': { + 1: 'constant.language' + }, + 'pattern': /\b(NULL|NA|TRUE|FALSE|T|F|NaN|Inf|NA_integer_|NA_real_|NA_complex_|NA_character_)\b/g + }, + { + 'matches': { + 1: 'constant.symbol' + }, + 'pattern': /[^0-9a-zA-Z\._](LETTERS|letters|month\.(abb|name)|pi)/g + }, + + /** + * @todo: The list subsetting operator isn't quite working properly. + * It includes the previous variable when it should only match [[ + */ + { + 'name': 'keyword.operator', + 'pattern': /<-|<-|-|==|<=|<=|>>|>=|<|>|&&|&&|&|&|!=|\|\|?|\*|\+|\^|\/|%%|%\/%|\=|%in%|%\*%|%o%|%x%|\$|:|~|\[{1,2}|\]{1,2}/g + }, + { + 'matches': { + 1: 'storage', + 3: 'entity.function' + }, + 'pattern': /(\s|^)(.*)(?=\s?=\s?function\s\()/g + }, + { + 'matches': { + 1: 'storage.function' + }, + 'pattern': /[^a-zA-Z0-9._](function)(?=\s*\()/g + }, + { + 'matches': { + 1: 'namespace', + 2: 'keyword.operator', + 3: 'function.call' + }, + 'pattern': /([a-zA-Z][a-zA-Z0-9._]+)([:]{2,3})([.a-zA-Z][a-zA-Z0-9._]*(?=\s*\())\b/g + }, + + /* + * Note that we would perhaps match more builtin functions and + * variables, but there are so many that most are ommitted for now. + * See ?builtins for more info. + * + * @todo: Fix the case where we have a function like tmp.logical(). + * This should just be a function call, at the moment it's + * only partly a function all. + */ + { + 'name': 'support.function', + 'pattern': /(^|[^0-9a-zA-Z\._])(array|character|complex|data\.frame|double|integer|list|logical|matrix|numeric|vector)(?=\s*\()/g + } +]); diff --git a/js/language/ruby.js b/js/language/ruby.js new file mode 100644 index 00000000..a90fc960 --- /dev/null +++ b/js/language/ruby.js @@ -0,0 +1,227 @@ +/** + * Ruby patterns + * + * @author Matthew King + * @author Jesse Farmer + * @author actsasflinn + * @version 1.0.6 + */ + +Rainbow.extend('ruby', [ + /** + * __END__ DATA + */ + { + 'matches': { + 1: 'variable.language', + 2: { + 'language': null + } + }, + //find __END__ and consume remaining text + 'pattern': /^(__END__)\n((?:.*\n)*)/gm + }, + /** + * Strings + * 1. No support for multi-line strings + */ + { + 'name': 'string', + 'matches': { + 1: 'string.open', + 2: [{ + 'name': 'string.interpolation', + 'matches': { + 1: 'string.open', + 2: { + 'language': 'ruby' + }, + 3: 'string.close' + }, + 'pattern': /(\#\{)(.*?)(\})/g + }], + 3: 'string.close' + }, + 'pattern': /("|`)(.*?[^\\\1])?(\1)/g + }, + { + 'name': 'string', + 'pattern': /('|"|`)([^\\\1\n]|\\.)*?\1/g + }, + { + 'name': 'string', + 'pattern': /%[qQ](?=(\(|\[|\{|<|.)(.*?)(?:'|\)|\]|\}|>|\1))(?:\(\2\)|\[\2\]|\{\2\}|\<\2>|\1\2\1)/g + }, + /** + * Heredocs + * Heredocs of the form `<<'HTML' ... HTML` are unsupported. + */ + { + 'matches': { + 1: 'string', + 2: 'string', + 3: 'string' + }, + 'pattern': /(<<)(\w+).*?$([\s\S]*?^\2)/gm + }, + { + 'matches': { + 1: 'string', + 2: 'string', + 3: 'string' + }, + 'pattern': /(<<\-)(\w+).*?$([\s\S]*?\2)/gm + }, + /** + * Regular expressions + * Escaped delimiter (`/\//`) is unsupported. + */ + { + 'name': 'string.regexp', + 'matches': { + 1: 'string.regexp', + 2: { + 'name': 'string.regexp', + 'pattern': /\\(.){1}/g + }, + 3: 'string.regexp', + 4: 'string.regexp' + }, + 'pattern': /(\/)(.*?)(\/)([a-z]*)/g + }, + { + 'name': 'string.regexp', + 'matches': { + 1: 'string.regexp', + 2: { + 'name': 'string.regexp', + 'pattern': /\\(.){1}/g + }, + 3: 'string.regexp', + 4: 'string.regexp' + }, + 'pattern': /%r(?=(\(|\[|\{|<|.)(.*?)('|\)|\]|\}|>|\1))(?:\(\2\)|\[\2\]|\{\2\}|\<\2>|\1\2\1)([a-z]*)/g + }, + /** + * Comments + */ + { + 'name': 'comment', + 'pattern': /#.*$/gm + }, + { + 'name': 'comment', + 'pattern': /^\=begin[\s\S]*?\=end$/gm + }, + /** + * Symbols + */ + { + 'matches': { + 1: 'constant' + }, + 'pattern': /(\w+:)[^:]/g + }, + { + 'matches': { + 1: 'constant.symbol' + }, + 'pattern': /[^:](:(?:\w+|(?=['"](.*?)['"])(?:"\2"|'\2')))/g + }, + { + 'name': 'constant.numeric', + 'pattern': /\b(0x[\da-f]+|\d+)\b/g + }, + { + 'name': 'support.class', + 'pattern': /\b[A-Z]\w*(?=((\.|::)[A-Za-z]|\[))/g + }, + { + 'name': 'constant', + 'pattern': /\b[A-Z]\w*\b/g + }, + /** + * Keywords, variables, constants, and operators + * In Ruby some keywords are valid method names, e.g., MyClass#yield + * Don't mark those instances as "keywords" + */ + { + 'matches': { + 1: 'storage.class', + 2: 'entity.name.class', + 3: 'entity.other.inherited-class' + }, + 'pattern': /\s*(class)\s+((?:(?:::)?[A-Z]\w*)+)(?:\s+<\s+((?:(?:::)?[A-Z]\w*)+))?/g + }, + { + 'matches': { + 1: 'storage.module', + 2: 'entity.name.class' + }, + 'pattern': /\s*(module)\s+((?:(?:::)?[A-Z]\w*)+)/g + }, + { + 'name': 'variable.global', + 'pattern': /\$([a-zA-Z_]\w*)\b/g + }, + { + 'name': 'variable.class', + 'pattern': /@@([a-zA-Z_]\w*)\b/g + }, + { + 'name': 'variable.instance', + 'pattern': /@([a-zA-Z_]\w*)\b/g + }, + { + 'matches': { + 1: 'keyword.control' + }, + 'pattern': /[^\.]\b(BEGIN|begin|case|class|do|else|elsif|END|end|ensure|for|if|in|module|rescue|then|unless|until|when|while)\b(?![?!])/g + }, + { + 'matches': { + 1: 'keyword.control.pseudo-method' + }, + 'pattern': /[^\.]\b(alias|alias_method|break|next|redo|retry|return|super|undef|yield)\b(?![?!])|\bdefined\?|\bblock_given\?/g + }, + { + 'matches': { + 1: 'constant.language' + }, + 'pattern': /\b(nil|true|false)\b(?![?!])/g + }, + { + 'matches': { + 1: 'variable.language' + }, + 'pattern': /\b(__(FILE|LINE)__|self)\b(?![?!])/g + }, + { + 'matches': { + 1: 'keyword.special-method' + }, + 'pattern': /\b(require|gem|initialize|new|loop|include|extend|raise|attr_reader|attr_writer|attr_accessor|attr|catch|throw|private|module_function|public|protected)\b(?![?!])/g + }, + { + 'name': 'keyword.operator', + 'pattern': /\s\?\s|=|<<|<<=|%=|&=|\*=|\*\*=|\+=|\-=|\^=|\|{1,2}=|<<|<=>|<(?!<|=)|>(?!<|=|>)|<=|>=|===|==|=~|!=|!~|%|&|\*\*|\*|\+|\-|\/|\||~|>>/g + }, + { + 'matches': { + 1: 'keyword.operator.logical' + }, + 'pattern': /[^\.]\b(and|not|or)\b/g + }, + + /** + * Functions + * 1. No support for marking function parameters + */ + { + 'matches': { + 1: 'storage.function', + 2: 'entity.name.function' + }, + 'pattern': /(def)\s(.*?)(?=(\s|\())/g + } +], true); diff --git a/js/language/scheme.js b/js/language/scheme.js new file mode 100644 index 00000000..8cb20c00 --- /dev/null +++ b/js/language/scheme.js @@ -0,0 +1,52 @@ +/** + * Scheme patterns + * + * @author Alex Queiroz + * @version 1.0 + */ +Rainbow.extend('scheme', [ + { + /* making peace with HTML */ + 'name': 'plain', + 'pattern': />|</g + }, + { + 'name': 'comment', + 'pattern': /;.*$/gm + }, + { + 'name': 'constant.language', + 'pattern': /#t|#f|'\(\)/g + }, + { + 'name': 'constant.symbol', + 'pattern': /'[^()\s#]+/g + }, + { + 'name': 'constant.number', + 'pattern': /\b\d+(?:\.\d*)?\b/g + }, + { + 'name': 'string', + 'pattern': /".+?"/g + }, + { + 'matches': { + 1: 'storage.function', + 2: 'variable' + }, + 'pattern': /\(\s*(define)\s+\(?(\S+)/g + }, + { + 'matches': { + 1: 'keyword' + }, + 'pattern': /\(\s*(begin|define\-syntax|if|lambda|quasiquote|quote|set!|syntax\-rules|and|and\-let\*|case|cond|delay|do|else|or|let|let\*|let\-syntax|letrec|letrec\-syntax)(?=[\]()\s#])/g + }, + { + 'matches': { + 1: 'entity.function' + }, + 'pattern': /\(\s*(eqv\?|eq\?|equal\?|number\?|complex\?|real\?|rational\?|integer\?|exact\?|inexact\?|=|<|>|<=|>=|zero\?|positive\?|negative\?|odd\?|even\?|max|min|\+|\-|\*|\/|abs|quotient|remainder|modulo|gcd|lcm|numerator|denominator|floor|ceiling|truncate|round|rationalize|exp|log|sin|cos|tan|asin|acos|atan|sqrt|expt|make\-rectangular|make\-polar|real\-part|imag\-part|magnitude|angle|exact\->inexact|inexact\->exact|number\->string|string\->number|not|boolean\?|pair\?|cons|car|cdr|set\-car!|set\-cdr!|caar|cadr|cdar|cddr|caaar|caadr|cadar|caddr|cdaar|cdadr|cddar|cdddr|caaaar|caaadr|caadar|caaddr|cadaar|cadadr|caddar|cadddr|cdaaar|cdaadr|cdadar|cdaddr|cddaar|cddadr|cdddar|cddddr|null\?|list\?|list|length|append|reverse|list\-tail|list\-ref|memq|memv|member|assq|assv|assoc|symbol\?|symbol\->string|string\->symbol|char\?|char=\?|char<\?|char>\?|char<=\?|char>=\?|char\-ci=\?|char\-ci<\?|char\-ci>\?|char\-ci<=\?|char\-ci>=\?|char\-alphabetic\?|char\-numeric\?|char\-whitespace\?|char\-upper\-case\?|char\-lower\-case\?|char\->integer|integer\->char|char\-upcase|char\-downcase|string\?|make\-string|string|string\-length|string\-ref|string\-set!|string=\?|string\-ci=\?|string<\?|string>\?|string<=\?|string>=\?|string\-ci<\?|string\-ci>\?|string\-ci<=\?|string\-ci>=\?|substring|string\-append|string\->list|list\->string|string\-copy|string\-fill!|vector\?|make\-vector|vector|vector\-length|vector\-ref|vector\-set!|vector\->list|list\->vector|vector\-fill!|procedure\?|apply|map|for\-each|force|call\-with\-current\-continuation|call\/cc|values|call\-with\-values|dynamic\-wind|eval|scheme\-report\-environment|null\-environment|interaction\-environment|call\-with\-input\-file|call\-with\-output\-file|input\-port\?|output\-port\?|current\-input\-port|current\-output\-port|with\-input\-from\-file|with\-output\-to\-file|open\-input\-file|open\-output\-file|close\-input\-port|close\-output\-port|read|read\-char|peek\-char|eof\-object\?|char\-ready\?|write|display|newline|write\-char|load|transcript\-on|transcript\-off)(?=[\]()\s#])/g + } +], true); diff --git a/js/language/shell.js b/js/language/shell.js new file mode 100644 index 00000000..a01d6c32 --- /dev/null +++ b/js/language/shell.js @@ -0,0 +1,56 @@ +/** + * Shell patterns + * + * @author Matthew King + * @author Craig Campbell + * @version 1.0.3 + */ +Rainbow.extend('shell', [ + /** + * This handles the case where subshells contain quotes. + * For example: `"$(resolve_link "$name" || true)"`. + * + * Caveat: This really should match balanced parentheses, but cannot. + * @see http://stackoverflow.com/questions/133601/can-regular-expressions-be-used-to-match-nested-patterns + */ + { + 'name': 'shell', + 'matches': { + 1: { + 'language': 'shell' + } + }, + 'pattern': /\$\(([\s\S]*?)\)/gm + }, + { + 'matches': { + 2: 'string' + }, + 'pattern': /(\(|\s|\[|\=)(('|")[\s\S]*?(\3))/gm + }, + { + 'name': 'keyword.operator', + 'pattern': /<|>|&/g + }, + { + 'name': 'comment', + 'pattern': /\#[\s\S]*?$/gm + }, + { + 'name': 'storage.function', + 'pattern': /(.+?)(?=\(\)\s{0,}\{)/g + }, + /** + * Environment variables + */ + { + 'name': 'support.command', + 'pattern': /\b(echo|rm|ls|(mk|rm)dir|cd|find|cp|exit|pwd|exec|trap|source|shift|unset)/g + }, + { + 'matches': { + 1: 'keyword' + }, + 'pattern': /\b(break|case|continue|do|done|elif|else|esac|eval|export|fi|for|function|if|in|local|return|set|then|unset|until|while)(?=\(|\b)/g + } +], true); diff --git a/js/language/smalltalk.js b/js/language/smalltalk.js new file mode 100644 index 00000000..16c2b5e5 --- /dev/null +++ b/js/language/smalltalk.js @@ -0,0 +1,52 @@ +/** + * Smalltalk patterns + * + * @author Frank Shearar + * @version 1.0 + */ +Rainbow.extend('smalltalk', [ + { + 'name': 'keyword.pseudovariable', + 'pattern': /self|thisContext/g + }, + { + 'name': 'keyword.constant', + 'pattern': /false|nil|true/g + }, + { + 'name': 'string', + 'pattern': /'([^']|'')*'/g + }, + { + 'name': 'string.symbol', + 'pattern': /#\w+|#'([^']|'')*'/g + }, + { + 'name': 'string.character', + 'pattern': /\$\w+/g + }, + { + 'name': 'comment', + 'pattern': /"([^"]|"")*"/g + }, + { + 'name': 'constant.numeric', + 'pattern': /-?\d+(\.\d+)?((r-?|s)[A-Za-z0-9]+|e-?[0-9]+)?/g + }, + { + 'name': 'entity.name.class', + 'pattern': /\b[A-Z]\w*/g + }, + { + 'name': 'entity.name.function', + 'pattern': /\b[a-z]\w*:?/g + }, + { + 'name': 'entity.name.binary', + 'pattern': /(<|>|&|[=~\|\\\/!@*\-_+])+/g + }, + { + 'name': 'operator.delimiter', + 'pattern': /;[\(\)\[\]\{\}]|#\[|#\(^\./g + } +], true); diff --git a/js/rainbow.js b/js/rainbow.js new file mode 100644 index 00000000..81904534 --- /dev/null +++ b/js/rainbow.js @@ -0,0 +1,798 @@ +/** + * Copyright 2013 Craig Campbell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Rainbow is a simple code syntax highlighter + * + * @preserve @version 1.2 + * @url rainbowco.de + */ +window['Rainbow'] = (function() { + + /** + * array of replacements to process at the end + * + * @type {Object} + */ + var replacements = {}, + + /** + * an array of start and end positions of blocks to be replaced + * + * @type {Object} + */ + replacement_positions = {}, + + /** + * an array of the language patterns specified for each language + * + * @type {Object} + */ + language_patterns = {}, + + /** + * an array of languages and whether they should bypass the default patterns + * + * @type {Object} + */ + bypass_defaults = {}, + + /** + * processing level + * + * replacements are stored at this level so if there is a sub block of code + * (for example php inside of html) it runs at a different level + * + * @type {number} + */ + CURRENT_LEVEL = 0, + + /** + * constant used to refer to the default language + * + * @type {number} + */ + DEFAULT_LANGUAGE = 0, + + /** + * used as counters so we can selectively call setTimeout + * after processing a certain number of matches/replacements + * + * @type {number} + */ + match_counter = 0, + + /** + * @type {number} + */ + replacement_counter = 0, + + /** + * @type {null|string} + */ + global_class, + + /** + * @type {null|Function} + */ + onHighlight; + + /** + * cross browser get attribute for an element + * + * @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method + * + * @param {Node} el + * @param {string} attr attribute you are trying to get + * @returns {string|number} + */ + function _attr(el, attr, attrs, i) { + var result = (el.getAttribute && el.getAttribute(attr)) || 0; + + if (!result) { + attrs = el.attributes; + + for (i = 0; i < attrs.length; ++i) { + if (attrs[i].nodeName === attr) { + return attrs[i].nodeValue; + } + } + } + + return result; + } + + /** + * adds a class to a given code block + * + * @param {Element} el + * @param {string} class_name class name to add + * @returns void + */ + function _addClass(el, class_name) { + el.className += el.className ? ' ' + class_name : class_name; + } + + /** + * checks if a block has a given class + * + * @param {Element} el + * @param {string} class_name class name to check for + * @returns {boolean} + */ + function _hasClass(el, class_name) { + return (' ' + el.className + ' ').indexOf(' ' + class_name + ' ') > -1; + } + + /** + * gets the language for this block of code + * + * @param {Element} block + * @returns {string|null} + */ + function _getLanguageForBlock(block) { + + // if this doesn't have a language but the parent does then use that + // this means if for example you have:
+        // with a bunch of  blocks inside then you do not have
+        // to specify the language for each block
+        var language = _attr(block, 'data-language') || _attr(block.parentNode, 'data-language');
+
+        // this adds support for specifying language via a css class
+        // you can use the Google Code Prettify style: 
+        // or the HTML5 style: 

+        if (!language) {
+            var pattern = /\blang(?:uage)?-(\w+)/,
+                match = block.className.match(pattern) || block.parentNode.className.match(pattern);
+
+            if (match) {
+                language = match[1];
+            }
+        }
+
+        return language;
+    }
+
+    /**
+     * makes sure html entities are always used for tags
+     *
+     * @param {string} code
+     * @returns {string}
+     */
+    function _htmlEntities(code) {
+        return code.replace(//g, '>').replace(/&(?![\w\#]+;)/g, '&');
+    }
+
+    /**
+     * determines if a new match intersects with an existing one
+     *
+     * @param {number} start1    start position of existing match
+     * @param {number} end1      end position of existing match
+     * @param {number} start2    start position of new match
+     * @param {number} end2      end position of new match
+     * @returns {boolean}
+     */
+    function _intersects(start1, end1, start2, end2) {
+        if (start2 >= start1 && start2 < end1) {
+            return true;
+        }
+
+        return end2 > start1 && end2 < end1;
+    }
+
+    /**
+     * determines if two different matches have complete overlap with each other
+     *
+     * @param {number} start1   start position of existing match
+     * @param {number} end1     end position of existing match
+     * @param {number} start2   start position of new match
+     * @param {number} end2     end position of new match
+     * @returns {boolean}
+     */
+    function _hasCompleteOverlap(start1, end1, start2, end2) {
+
+        // if the starting and end positions are exactly the same
+        // then the first one should stay and this one should be ignored
+        if (start2 == start1 && end2 == end1) {
+            return false;
+        }
+
+        return start2 <= start1 && end2 >= end1;
+    }
+
+    /**
+     * determines if the match passed in falls inside of an existing match
+     * this prevents a regex pattern from matching inside of a bigger pattern
+     *
+     * @param {number} start - start position of new match
+     * @param {number} end - end position of new match
+     * @returns {boolean}
+     */
+    function _matchIsInsideOtherMatch(start, end) {
+        for (var key in replacement_positions[CURRENT_LEVEL]) {
+            key = parseInt(key, 10);
+
+            // if this block completely overlaps with another block
+            // then we should remove the other block and return false
+            if (_hasCompleteOverlap(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
+                delete replacement_positions[CURRENT_LEVEL][key];
+                delete replacements[CURRENT_LEVEL][key];
+            }
+
+            if (_intersects(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * takes a string of code and wraps it in a span tag based on the name
+     *
+     * @param {string} name     name of the pattern (ie keyword.regex)
+     * @param {string} code     block of code to wrap
+     * @returns {string}
+     */
+    function _wrapCodeInSpan(name, code) {
+        return '' + code + '';
+    }
+
+    /**
+     * finds out the position of group match for a regular expression
+     *
+     * @see http://stackoverflow.com/questions/1985594/how-to-find-index-of-groups-in-match
+     *
+     * @param {Object} match
+     * @param {number} group_number
+     * @returns {number}
+     */
+    function _indexOfGroup(match, group_number) {
+        var index = 0,
+            i;
+
+        for (i = 1; i < group_number; ++i) {
+            if (match[i]) {
+                index += match[i].length;
+            }
+        }
+
+        return index;
+    }
+
+    /**
+     * matches a regex pattern against a block of code
+     * finds all matches that should be processed and stores the positions
+     * of where they should be replaced within the string
+     *
+     * this is where pretty much all the work is done but it should not
+     * be called directly
+     *
+     * @param {RegExp} pattern
+     * @param {string} code
+     * @returns void
+     */
+    function _processPattern(regex, pattern, code, callback)
+    {
+        if (typeof regex === "undefined" || regex === null) {
+            //console.warn("undefined regular expression")
+            return callback();
+        }
+        var match = regex.exec(code);
+
+        if (!match) {
+            return callback();
+        }
+
+        ++match_counter;
+
+        // treat match 0 the same way as name
+        if (!pattern['name'] && typeof pattern['matches'][0] == 'string') {
+            pattern['name'] = pattern['matches'][0];
+            delete pattern['matches'][0];
+        }
+
+        var replacement = match[0],
+            start_pos = match.index,
+            end_pos = match[0].length + start_pos,
+
+            /**
+             * callback to process the next match of this pattern
+             */
+            processNext = function() {
+                var nextCall = function() {
+                    _processPattern(regex, pattern, code, callback);
+                };
+
+                // every 100 items we process let's call set timeout
+                // to let the ui breathe a little
+                return match_counter % 100 > 0 ? nextCall() : setTimeout(nextCall, 0);
+            };
+
+        // if this is not a child match and it falls inside of another
+        // match that already happened we should skip it and continue processing
+        if (_matchIsInsideOtherMatch(start_pos, end_pos)) {
+            return processNext();
+        }
+
+        /**
+         * callback for when a match was successfully processed
+         *
+         * @param {string} replacement
+         * @returns void
+         */
+        var onMatchSuccess = function(replacement) {
+                // if this match has a name then wrap it in a span tag
+                if (pattern['name']) {
+                    replacement = _wrapCodeInSpan(pattern['name'], replacement);
+                }
+
+                // console.log('LEVEL', CURRENT_LEVEL, 'replace', match[0], 'with', replacement, 'at position', start_pos, 'to', end_pos);
+
+                // store what needs to be replaced with what at this position
+                if (!replacements[CURRENT_LEVEL]) {
+                    replacements[CURRENT_LEVEL] = {};
+                    replacement_positions[CURRENT_LEVEL] = {};
+                }
+
+                replacements[CURRENT_LEVEL][start_pos] = {
+                    'replace': match[0],
+                    'with': replacement
+                };
+
+                // store the range of this match so we can use it for comparisons
+                // with other matches later
+                replacement_positions[CURRENT_LEVEL][start_pos] = end_pos;
+
+                // process the next match
+                processNext();
+            },
+
+            // if this pattern has sub matches for different groups in the regex
+            // then we should process them one at a time by rerunning them through
+            // this function to generate the new replacement
+            //
+            // we run through them backwards because the match position of earlier
+            // matches will not change depending on what gets replaced in later
+            // matches
+            group_keys = keys(pattern['matches']),
+
+            /**
+             * callback for processing a sub group
+             *
+             * @param {number} i
+             * @param {Array} group_keys
+             * @param {Function} callback
+             */
+            processGroup = function(i, group_keys, callback) {
+                if (i >= group_keys.length) {
+                    return callback(replacement);
+                }
+
+                var processNextGroup = function() {
+                        processGroup(++i, group_keys, callback);
+                    },
+                    block = match[group_keys[i]];
+
+                // if there is no match here then move on
+                if (!block) {
+                    return processNextGroup();
+                }
+
+                var group = pattern['matches'][group_keys[i]],
+                    language = group['language'],
+
+                    /**
+                     * process group is what group we should use to actually process
+                     * this match group
+                     *
+                     * for example if the subgroup pattern looks like this
+                     * 2: {
+                     *     'name': 'keyword',
+                     *     'pattern': /true/g
+                     * }
+                     *
+                     * then we use that as is, but if it looks like this
+                     *
+                     * 2: {
+                     *     'name': 'keyword',
+                     *     'matches': {
+                     *          'name': 'special',
+                     *          'pattern': /whatever/g
+                     *      }
+                     * }
+                     *
+                     * we treat the 'matches' part as the pattern and keep
+                     * the name around to wrap it with later
+                     */
+                    process_group = group['name'] && group['matches'] ? group['matches'] : group,
+
+                    /**
+                     * takes the code block matched at this group, replaces it
+                     * with the highlighted block, and optionally wraps it with
+                     * a span with a name
+                     *
+                     * @param {string} block
+                     * @param {string} replace_block
+                     * @param {string|null} match_name
+                     */
+                    _replaceAndContinue = function(block, replace_block, match_name) {
+                        replacement = _replaceAtPosition(_indexOfGroup(match, group_keys[i]), block, match_name ? _wrapCodeInSpan(match_name, replace_block) : replace_block, replacement);
+                        processNextGroup();
+                    };
+
+                // if this is a sublanguage go and process the block using that language
+                if (language) {
+                    return _highlightBlockForLanguage(block, language, function(code) {
+                        _replaceAndContinue(block, code);
+                    });
+                }
+
+                // if this is a string then this match is directly mapped to selector
+                // so all we have to do is wrap it in a span and continue
+                if (typeof group === 'string') {
+                    return _replaceAndContinue(block, block, group);
+                }
+
+                // the process group can be a single pattern or an array of patterns
+                // _processCodeWithPatterns always expects an array so we convert it here
+                _processCodeWithPatterns(block, process_group.length ? process_group : [process_group], function(code) {
+                    _replaceAndContinue(block, code, group['matches'] ? group['name'] : 0);
+                });
+            };
+
+        processGroup(0, group_keys, onMatchSuccess);
+    }
+
+    /**
+     * should a language bypass the default patterns?
+     *
+     * if you call Rainbow.extend() and pass true as the third argument
+     * it will bypass the defaults
+     */
+    function _bypassDefaultPatterns(language)
+    {
+        return bypass_defaults[language];
+    }
+
+    /**
+     * returns a list of regex patterns for this language
+     *
+     * @param {string} language
+     * @returns {Array}
+     */
+    function _getPatternsForLanguage(language) {
+        var patterns = language_patterns[language] || [],
+            default_patterns = language_patterns[DEFAULT_LANGUAGE] || [];
+
+        return _bypassDefaultPatterns(language) ? patterns : patterns.concat(default_patterns);
+    }
+
+    /**
+     * substring replace call to replace part of a string at a certain position
+     *
+     * @param {number} position         the position where the replacement should happen
+     * @param {string} replace          the text we want to replace
+     * @param {string} replace_with     the text we want to replace it with
+     * @param {string} code             the code we are doing the replacing in
+     * @returns {string}
+     */
+    function _replaceAtPosition(position, replace, replace_with, code) {
+        var sub_string = code.substr(position);
+        return code.substr(0, position) + sub_string.replace(replace, replace_with);
+    }
+
+   /**
+     * sorts an object by index descending
+     *
+     * @param {Object} object
+     * @return {Array}
+     */
+    function keys(object) {
+        var locations = [],
+            replacement,
+            pos;
+
+        for(var location in object) {
+            if (object.hasOwnProperty(location)) {
+                locations.push(location);
+            }
+        }
+
+        // numeric descending
+        return locations.sort(function(a, b) {
+            return b - a;
+        });
+    }
+
+    /**
+     * processes a block of code using specified patterns
+     *
+     * @param {string} code
+     * @param {Array} patterns
+     * @returns void
+     */
+    function _processCodeWithPatterns(code, patterns, callback)
+    {
+        // we have to increase the level here so that the
+        // replacements will not conflict with each other when
+        // processing sub blocks of code
+        ++CURRENT_LEVEL;
+
+        // patterns are processed one at a time through this function
+        function _workOnPatterns(patterns, i)
+        {
+            // still have patterns to process, keep going
+            if (i < patterns.length) {
+                return _processPattern(patterns[i]['pattern'], patterns[i], code, function() {
+                    _workOnPatterns(patterns, ++i);
+                });
+            }
+
+            // we are done processing the patterns
+            // process the replacements and update the DOM
+            _processReplacements(code, function(code) {
+
+                // when we are done processing replacements
+                // we are done at this level so we can go back down
+                delete replacements[CURRENT_LEVEL];
+                delete replacement_positions[CURRENT_LEVEL];
+                --CURRENT_LEVEL;
+                callback(code);
+            });
+        }
+
+        _workOnPatterns(patterns, 0);
+    }
+
+    /**
+     * process replacements in the string of code to actually update the markup
+     *
+     * @param {string} code         the code to process replacements in
+     * @param {Function} onComplete   what to do when we are done processing
+     * @returns void
+     */
+    function _processReplacements(code, onComplete) {
+
+        /**
+         * processes a single replacement
+         *
+         * @param {string} code
+         * @param {Array} positions
+         * @param {number} i
+         * @param {Function} onComplete
+         * @returns void
+         */
+        function _processReplacement(code, positions, i, onComplete) {
+            if (i < positions.length) {
+                ++replacement_counter;
+                var pos = positions[i],
+                    replacement = replacements[CURRENT_LEVEL][pos];
+                code = _replaceAtPosition(pos, replacement['replace'], replacement['with'], code);
+
+                // process next function
+                var next = function() {
+                    _processReplacement(code, positions, ++i, onComplete);
+                };
+
+                // use a timeout every 250 to not freeze up the UI
+                return replacement_counter % 250 > 0 ? next() : setTimeout(next, 0);
+            }
+
+            onComplete(code);
+        }
+
+        var string_positions = keys(replacements[CURRENT_LEVEL]);
+        _processReplacement(code, string_positions, 0, onComplete);
+    }
+
+    /**
+     * takes a string of code and highlights it according to the language specified
+     *
+     * @param {string} code
+     * @param {string} language
+     * @param {Function} onComplete
+     * @returns void
+     */
+    function _highlightBlockForLanguage(code, language, onComplete) {
+        var patterns = _getPatternsForLanguage(language);
+        _processCodeWithPatterns(_htmlEntities(code), patterns, onComplete);
+    }
+
+    /**
+     * highlight an individual code block
+     *
+     * @param {Array} code_blocks
+     * @param {number} i
+     * @returns void
+     */
+    function _highlightCodeBlock(code_blocks, i, onComplete) {
+        if (i < code_blocks.length) {
+            var block = code_blocks[i],
+                language = _getLanguageForBlock(block);
+
+            if (!_hasClass(block, 'rainbow') && language) {
+                language = language.toLowerCase();
+
+                _addClass(block, 'rainbow');
+
+                return _highlightBlockForLanguage(block.innerHTML, language, function(code) {
+                    block.innerHTML = code;
+
+                    // reset the replacement arrays
+                    replacements = {};
+                    replacement_positions = {};
+
+                    // if you have a listener attached tell it that this block is now highlighted
+                    if (onHighlight) {
+                        onHighlight(block, language);
+                    }
+
+                    // process the next block
+                    setTimeout(function() {
+                        _highlightCodeBlock(code_blocks, ++i, onComplete);
+                    }, 0);
+                });
+            }
+            return _highlightCodeBlock(code_blocks, ++i, onComplete);
+        }
+
+        if (onComplete) {
+            onComplete();
+        }
+    }
+
+    /**
+     * start highlighting all the code blocks
+     *
+     * @returns void
+     */
+    function _highlight(node, onComplete) {
+
+        // the first argument can be an Event or a DOM Element
+        // I was originally checking instanceof Event but that makes it break
+        // when using mootools
+        //
+        // @see https://github.com/ccampbell/rainbow/issues/32
+        //
+        node = node && typeof node.getElementsByTagName == 'function' ? node : document;
+
+        var pre_blocks = node.getElementsByTagName('pre'),
+            code_blocks = node.getElementsByTagName('code'),
+            i,
+            final_pre_blocks = [],
+            final_code_blocks = [];
+
+        // first loop through all pre blocks to find which ones to highlight
+        // also strip whitespace
+        for (i = 0; i < pre_blocks.length; ++i) {
+
+            // strip whitespace around code tags when they are inside of a pre tag
+            // this makes the themes look better because you can't accidentally
+            // add extra linebreaks at the start and end
+            //
+            // when the pre tag contains a code tag then strip any extra whitespace
+            // for example
+            // 
+            //      var foo = true;
+            // 
+ // + // will become + //
var foo = true;
+ // + // if you want to preserve whitespace you can use a pre tag on its own + // without a code tag inside of it + if (pre_blocks[i].getElementsByTagName('code').length) { + pre_blocks[i].innerHTML = pre_blocks[i].innerHTML.replace(/^\s+/, '').replace(/\s+$/, ''); + continue; + } + + // if the pre block has no code blocks then we are going to want to + // process it directly + final_pre_blocks.push(pre_blocks[i]); + } + + // @see http://stackoverflow.com/questions/2735067/how-to-convert-a-dom-node-list-to-an-array-in-javascript + // we are going to process all blocks + for (i = 0; i < code_blocks.length; ++i) { + final_code_blocks.push(code_blocks[i]); + } + + _highlightCodeBlock(final_code_blocks.concat(final_pre_blocks), 0, onComplete); + } + + /** + * public methods + */ + return { + + /** + * extends the language pattern matches + * + * @param {*} language name of language + * @param {*} patterns array of patterns to add on + * @param {boolean|null} bypass if true this will bypass the default language patterns + */ + extend: function(language, patterns, bypass) { + + // if there is only one argument then we assume that we want to + // extend the default language rules + if (arguments.length == 1) { + patterns = language; + language = DEFAULT_LANGUAGE; + } + + bypass_defaults[language] = bypass; + language_patterns[language] = patterns.concat(language_patterns[language] || []); + }, + + /** + * call back to let you do stuff in your app after a piece of code has been highlighted + * + * @param {Function} callback + */ + onHighlight: function(callback) { + onHighlight = callback; + }, + + /** + * method to set a global class that will be applied to all spans + * + * @param {string} class_name + */ + addClass: function(class_name) { + global_class = class_name; + }, + + /** + * starts the magic rainbow + * + * @returns void + */ + color: function() { + + // if you want to straight up highlight a string you can pass the string of code, + // the language, and a callback function + if (typeof arguments[0] == 'string') { + return _highlightBlockForLanguage(arguments[0], arguments[1], arguments[2]); + } + + // if you pass a callback function then we rerun the color function + // on all the code and call the callback function on complete + if (typeof arguments[0] == 'function') { + return _highlight(0, arguments[0]); + } + + // otherwise we use whatever node you passed in with an optional + // callback function as the second parameter + _highlight(arguments[0], arguments[1]); + } + }; +}) (); + +/** + * adds event listener to start highlighting + */ +(function() { + if (document.addEventListener) { + return document.addEventListener('DOMContentLoaded', Rainbow.color, false); + } + window.attachEvent('onload', Rainbow.color); +}) (); + +// When using Google closure compiler in advanced mode some methods +// get renamed. This keeps a public reference to these methods so they can +// still be referenced from outside this library. +Rainbow["onHighlight"] = Rainbow.onHighlight; +Rainbow["addClass"] = Rainbow.addClass; diff --git a/js/rainbow.min.js b/js/rainbow.min.js new file mode 100644 index 00000000..9cd1d811 --- /dev/null +++ b/js/rainbow.min.js @@ -0,0 +1,8 @@ +/* Rainbow v1.1.9 rainbowco.de */ +window.Rainbow=function(){function q(a){var b,c=a.getAttribute&&a.getAttribute("data-language")||0;if(!c){a=a.attributes;for(b=0;b=e[d][c])delete e[d][c],delete j[d][c];if(a>=c&&ac&&b'+b+""}function s(a,b,c,h){var f=a.exec(c);if(f){++t;!b.name&&"string"==typeof b.matches[0]&&(b.name=b.matches[0],delete b.matches[0]);var k=f[0],i=f.index,u=f[0].length+i,g=function(){function f(){s(a,b,c,h)}t%100>0?f():setTimeout(f,0)};if(C(i,u))g();else{var m=v(b.matches),l=function(a,c,h){if(a>=c.length)h(k);else{var d=f[c[a]];if(d){var e=b.matches[c[a]],i=e.language,g=e.name&&e.matches? +e.matches:e,j=function(b,d,e){var i;i=0;var g;for(g=1;g/g,">").replace(/&(?![\w\#]+;)/g, +"&"),b,c)}function o(a,b,c){if(b + +Syntax Highlighting + + +

API Test Page

+
+

code on page to begin with

+
var foo = false;
+
+ + + + + + + + diff --git a/tests/language/test.coffeescript.js b/tests/language/test.coffeescript.js new file mode 100644 index 00000000..98ccdb03 --- /dev/null +++ b/tests/language/test.coffeescript.js @@ -0,0 +1,260 @@ +/* global describe, run */ +var language = 'coffeescript'; + +describe(language, function() { + run( + language, + + 'comment', + + '# this is a comment', + + '# this is a comment' + ); + + run( + language, + + 'block comment', + + '###\n' + + 'CoffeeScript Compiler v1.3.3\n' + + 'Released under the MIT License\n' + + '###', + + '###\n' + + 'CoffeeScript Compiler v1.3.3\n' + + 'Released under the MIT License\n' + + '###' + ); + + run( + language, + + 'string', + + 'test = "this is a string"', + + 'test = "this is a string"' + ); + + run( + language, + + 'block string', + + 'html = """\n' + + ' \n' + + ' cup of coffeescript\n' + + ' \n' + + ' """', + + 'html = """\n' + + ' <strong>\n' + + ' cup of coffeescript\n' + + ' </strong>\n' + + ' """' + ); + + run( + language, + + 'function call', + + 'square = (x) -> x * x', + + 'square = (x) -> x * x' + ); + + run( + language, + + 'function call inside object', + + 'math =\n' + + ' root: Math.sqrt\n' + + ' square: square\n' + + ' cube: (x) -> x * square x', + + 'math =\n' + + ' root: Math.sqrt\n' + + ' square: square\n' + + ' cube: (x) -> x * square x' + ); + + run( + language, + + 'function call multiple arguments', + + 'race = (winner, runners...) ->\n' + + ' print winner, runners', + + 'race = (winner, runners...) ->\n' + + ' print winner, runners' + ); + + run( + language, + + 'switch statement', + + 'switch day\n' + + ' when "Mon" then go work\n' + + ' when "Tue" then go relax\n' + + ' when "Thu" then go iceFishing\n' + + ' when "Fri", "Sat"\n' + + ' if day is bingoDay\n' + + ' go bingo\n' + + ' go dancing\n' + + ' when "Sun" then go church\n' + + 'else go work', + + 'switch day\n' + + ' when "Mon" then go work\n' + + ' when "Tue" then go relax\n' + + ' when "Thu" then go iceFishing\n' + + ' when "Fri", "Sat"\n' + + ' if day is bingoDay\n' + + ' go bingo\n' + + ' go dancing\n' + + ' when "Sun" then go church\n' + + 'else go work' + ); + + run( + language, + + 'multiline regex', + + 'OPERATOR = /// ^ (\n' + + ' ?: [-=]> # function\n' + + ' | [-+*/%<>&|^!?=]= # compound assign / compare\n' + + ' | >>>=? # zero-fill right shift\n' + + ' | ([-+:])\\1 # doubles\n' + + ' | ([&|<>])\\2=? # logic / shift\n' + + ' | \\?\\. # soak access\n' + + ' | \\.{2,3} # range or splat\n' + + ') ///', + + 'OPERATOR = /// ^ (\n' + + ' ?: [-=]> # function\n' + + ' | [-+*/%<>&|^!?=]= # compound assign / compare\n' + + ' | >>>=? # zero-fill right shift\n' + + ' | ([-+:])\\1 # doubles\n' + + ' | ([&|<>])\\2=? # logic / shift\n' + + ' | \\?\\. # soak access\n' + + ' | \\.{2,3} # range or splat\n' + + ') ///' + ); + + run( + language, + + 'function inside function call', + + "task 'build:parser', 'rebuild the Jison parser', (options) ->\n" + + " require 'jison'\n" + + " code = require('./lib/grammar').parser.generate()\n" + + " dir = options.output or 'lib'\n" + + " fs.writeFile \"#{dir}/parser.js\", code", + + 'task \'build:parser\', \'rebuild the Jison parser\', (options) ->\n' + + ' require \'jison\'\n' + + ' code = require(\'./lib/grammar\').parser.generate()\n' + + ' dir = options.output or \'lib\'\n' + + ' fs.writeFile "#{dir}/parser.js", code' + ); + + run( + language, + + 'multiline function', + + 'weatherReport = (location) ->\n' + + ' # Make an Ajax request to fetch the weather...\n' + + ' [location, 72, "Mostly Sunny"]\n' + + '\n' + + ' [city, temp, forecast] = weatherReport "Berkeley, CA"', + + 'weatherReport = (location) ->\n' + + ' # Make an Ajax request to fetch the weather...\n' + + ' [location, 72, "Mostly Sunny"]\n' + + '\n' + + ' [city, temp, forecast] = weatherReport "Berkeley, CA"' + ); + + run( + language, + + 'function binding', + + 'Account = (customer, cart) ->\n' + + ' @customer = customer\n' + + ' @cart = cart\n' + + '\n' + + ' $(\'.shopping_cart\').bind \'click\', (event) =>\n' + + ' @customer.purchase @cart', + + 'Account = (customer, cart) ->\n' + + ' @customer = customer\n' + + ' @cart = cart\n' + + '\n' + + ' $(\'.shopping_cart\').bind \'click\', (event) =>\n' + + ' @customer.purchase @cart' + ); + + run( + language, + + 'direct function call', + + 'move: ->\n' + + ' alert "Galloping..."\n' + + ' super 45', + + 'move: ->\n' + + ' alert "Galloping..."\n' + + ' super 45' + ); + + run( + language, + + '@ keyword', + + 'alert @name + " moved #{meters}m."', + + 'alert @name + " moved #{meters}m."' + ); + + run( + language, + + 'class definition', + + 'class Animal', + + 'class Animal' + ); + + run( + language, + + 'child class definition', + + 'class Snake extends Animal', + + 'class Snake extends Animal' + ); + + run( + language, + + 'class instantiation', + + 'sam = new Snake "Sammy the Python"', + + 'sam = new Snake "Sammy the Python"' + ); +}); diff --git a/tests/language/test.csharp.js b/tests/language/test.csharp.js new file mode 100644 index 00000000..25c57f01 --- /dev/null +++ b/tests/language/test.csharp.js @@ -0,0 +1,198 @@ +/* global describe, run */ +var language = 'csharp'; + +describe(language, function() { + run( + language, + + 'echo', + + 'Console.WriteLine("hello world");', + + 'Console.WriteLine("hello world");' + ); + + run( + language, + + 'variable', + + 'var foo = true;', + + 'var foo = true;' + ); + + run( + language, + + 'string concatenation', + + 'string foo = "test " + "string " + "concatenation";', + + 'string foo = "test " + "string " + "concatenation";' + ); + + run( + language, + + 'typeof', + + "var is_array_object = typeof(System.Array);", + + 'var is_array_object = typeof(System.Array);' + ); + + run( + language, + + 'array stuff', + + 'string[] turtles = new string[] {\n' + + ' "leonardo",\n' + + ' "michaelangelo",\n' + + ' "donatello",\n' + + ' "raphael"\n' + + '};\n' + + '\n' + + 'bool exists = turtles[0] == "leonardo";', + + 'string[] turtles = new string[] {\n' + + ' "leonardo",\n' + + ' "michaelangelo",\n' + + ' "donatello",\n' + + ' "raphael"\n' + + '};\n' + + '\n' + + 'bool exists = turtles[0] == "leonardo";' + ); + + run( + language, + + 'namespace declaration', + + 'namespace Sonic.Database {', + + 'namespace Sonic.Database {' + ); + + run( + language, + + 'class declaration', + + 'class MyClass {}', + + 'class MyClass {}' + ); + + run( + language, + + 'abstract class declaration', + + 'abstract class MyClass {}', + + 'abstract class MyClass {}' + ); + + run( + language, + + 'sealed class declaration', + + 'sealed class TestClass\n' + + '{\n' + + '}', + + 'sealed class TestClass\n' + + '{\n' + + '}' + ); + + run( + language, + + 'child class declaration', + + 'class MyCollection : ICollection {}', + + 'class MyCollection : ICollection {}' + ); + + run( + language, + + 'test static', + + 'static void doSomethingElse() {}', + + 'static void doSomethingElse() {}' + ); + + run( + language, + + 'test magic function', + + 'protected void Page_Load(object sender, EventArgs e)\n' + + '{\n' + + ' // do whatever\n' + + '}', + + 'protected void Page_Load(object sender, EventArgs e)\n' + + '{\n' + + ' // do whatever\n' + + '}' + ); + + run( + language, + + 'test new class', + + 'new SomeClass();', + + 'new SomeClass();' + ); + + run( + language, + + 'test new namespace class', + + 'var s = new Sonic.Database.Query();', + + 'var s = new Sonic.Database.Query();' + ); + + run( + language, + + 'test static class call', + + 'var path = Sonic.App.getInstance();', + + 'var path = Sonic.App.getInstance();' + ); + + run( + language, + + 'type hint', + + 'public static string getForUser(User user, Sort sort) {}', + + 'public static string getForUser(User user, Sort sort) {}' + ); + + run( + language, + + 'generics', + + 'public IList<string> firstNames = new List<string>()', + + 'public IList<string> firstNames = new List<string>()' + ); +}); diff --git a/tests/language/test.css.js b/tests/language/test.css.js new file mode 100644 index 00000000..a7d473ad --- /dev/null +++ b/tests/language/test.css.js @@ -0,0 +1,222 @@ +/* global describe, run */ +var language = 'css'; + +describe(language, function() { + run( + language, + + 'comment', + + '/* comment */', + + '/* comment */' + ); + + run( + language, + + 'multi-line comment', + + '/**\n' + + ' * comment\n' + + ' */', + + '/**\n' + + ' * comment\n' + + ' */' + ); + + run( + language, + + 'pixels', + + 'margin:10px 20px 5px 30px;', + + 'margin:10px 20px 5px 30px;' + ); + + run( + language, + + 'cm', + + 'margin: 1cm 2cm 1.3cm 4cm;', + + 'margin: 1cm 2cm 1.3cm 4cm;' + ); + + run( + language, + + 'em', + + 'font-size: 1.2em;', + + 'font-size: 1.2em;' + ); + + run( + language, + + 'percentage', + + 'width: 100%\n' + + 'height: 100%', + + 'width: 100%\n' + + 'height: 100%' + ); + + run( + language, + + 'string single quote', + + '\'test string\'', + + '\'test string\'' + ); + + run( + language, + + 'string double quote', + + '"test string"', + + '"test string"' + ); + + run( + language, + + 'transition - vendor prefix', + + 'code span {\n' + + ' -moz-transition: color .8s ease-in;\n' + + ' -o-transition: color .8s ease-in;\n' + + ' -webkit-transition: color .8s ease-in;\n' + + ' transition: color .8s ease-in;\n' + + '}', + + 'code span {\n' + + ' -moz-transition: color .8s ease-in;\n' + + ' -o-transition: color .8s ease-in;\n' + + ' -webkit-transition: color .8s ease-in;\n' + + ' transition: color .8s ease-in;\n' + + '}' + ); + + run( + language, + + 'tag', + + 'p {', + + 'p {' + ); + + run( + language, + + 'class', + + 'p.intro {', + + 'p.intro {' + ); + + run( + language, + + 'id', + + 'p#intro {', + + 'p#intro {' + ); + + run( + language, + + 'direct descendant', + + 'p > span {', + + 'p > span {' + ); + + run( + language, + + 'scss', + + 'article {\n' + + ' &.cool {\n' + + ' p {\n' + + ' margin-top: 20px;\n' + + ' }\n' + + ' }\n' + + '}', + + 'article {\n' + + ' &.cool {\n' + + ' p {\n' + + ' margin-top: 20px;\n' + + ' }\n' + + ' }\n' + + '}' + ); + + run( + language, + + 'one line', + + 'p { color: #fff; margin-top: 10px; }', + + 'p { color: #fff; margin-top: 10px; }' + ); + + run( + language, + + 'linear gradients', + + '.gradient {\n' + + ' background: -webkit-linear-gradient(#f7f7f7, #e8e8e8);\n' + + ' background: -moz-linear-gradient(#f7f7f7, #e8e8e8);\n' + + ' background: -o-linear-gradient(#f7f7f7, #e8e8e8);\n' + + ' background: linear-gradient(#f7f7f7, #e8e8e8);\n' + + '}', + + '.gradient {\n' + + ' background: -webkit-linear-gradient(#f7f7f7, #e8e8e8);\n' + + ' background: -moz-linear-gradient(#f7f7f7, #e8e8e8);\n' + + ' background: -o-linear-gradient(#f7f7f7, #e8e8e8);\n' + + ' background: linear-gradient(#f7f7f7, #e8e8e8);\n' + + '}' + ); + + run( + language, + + 'multi line selectors', + + '.maps-headline,\n' + + '.maps-subline,\n' + + '.chart-headline,\n' + + '.chart-subline {\n' + + ' font-weight: bold;\n' + + '}', + + '.maps-headline,\n' + + '.maps-subline,\n' + + '.chart-headline,\n' + + '.chart-subline {\n' + + ' font-weight: bold;\n' + + '}' + ); +}); diff --git a/tests/language/test.d.js b/tests/language/test.d.js new file mode 100644 index 00000000..fef642a1 --- /dev/null +++ b/tests/language/test.d.js @@ -0,0 +1,168 @@ +/* global describe, run */ +var language = 'd'; + +describe(language, function() { + run( + language, + + 'echo', + + 'writeln("hello world");', + + 'writeln("hello world");' + ); + + run( + language, + + 'variable', + + 'bool foo = true;', + + 'bool foo = true;' + ); + + run( + language, + + 'string concatenation', + + 'string foo = "test " ~ "string " ~ "concatenation";', + + 'string foo = "test " ~ "string " ~ "concatenation";' + ); + + run( + language, + + 'typeof', + + "auto is_array_object = typeof(System.Array);", + + 'auto is_array_object = typeof(System.Array);' + ); + + run( + language, + + 'array stuff', + + 'string[] turtles = new string[] {\n' + + ' "leonardo",\n' + + ' "michaelangelo",\n' + + ' "donatello",\n' + + ' "raphael"\n' + + '};\n' + + '\n' + + 'bool exists = turtles[0] == "leonardo";', + + 'string[] turtles = new string[] {\n' + + ' "leonardo",\n' + + ' "michaelangelo",\n' + + ' "donatello",\n' + + ' "raphael"\n' + + '};\n' + + '\n' + + 'bool exists = turtles[0] == "leonardo";' + ); + + run( + language, + + 'module declaration', + + 'module SonicDatabase;', + + 'module SonicDatabase;' + ); + + run( + language, + + 'class declaration', + + 'class MyClass {}', + + 'class MyClass {}' + ); + + run( + language, + + 'abstract class declaration', + + 'abstract class MyClass {}', + + 'abstract class MyClass {}' + ); + + run( + language, + + 'child class declaration', + + 'class MyCollection : ICollection {}', + + 'class MyCollection : ICollection {}' + ); + + run( + language, + + 'test static', + + 'static void doSomethingElse() {}', + + 'static void doSomethingElse() {}' + ); + + run( + language, + + 'test new class', + + 'new SomeClass();', + + 'new SomeClass();' + ); + + run( + language, + + 'test new namespace class', + + 'auto s = new Sonic.Database.Query();', + + 'auto s = new Sonic.Database.Query();' + ); + + run( + language, + + 'test static class call', + + 'auto path = Sonic.App.getInstance();', + + 'auto path = Sonic.App.getInstance();' + ); + + run( + language, + + 'type hint', + + 'public static string getForUser(User user, Sort sort) {}', + + 'public static string getForUser(User user, Sort sort) {}' + ); + + run( + language, + + 'template', + + 'public List!string firstNames = new List!(string)()', + + 'public List!string firstNames = new List!(string)()' + ); +}); diff --git a/tests/language/test.generic.js b/tests/language/test.generic.js new file mode 100644 index 00000000..073b1a97 --- /dev/null +++ b/tests/language/test.generic.js @@ -0,0 +1,348 @@ +/* global describe, run */ +var language = 'generic'; + +describe(language, function() { + run( + language, + + 'string alone', + + 'foo = "this is a string"', + + 'foo = "this is a string"' + ); + + run( + language, + + 'string after equal sign', + + 'foo=\'this is a string\'', + + 'foo=\'this is a string\'' + ); + + run( + language, + + 'string in brackets', + + 'foo = ["one", "two", "three"];', + + 'foo = ["one", "two", "three"];' + ); + + run( + language, + + 'string in function call', + + "someFunction('some string', true);", + + 'someFunction(\'some string\', true);' + ); + + run( + language, + + 'concatenated string', + + "foo = 'string1' + 'string2';", + + 'foo = \'string1\' + \'string2\';' + ); + + run( + language, + + 'quoted comment with string', + + '# someone\'s comment\n' + + 'doSomething(\'js\')\n' + + 'doSomethingElse()\n' + + 'test = \'other string\'', + + '# someone\'s comment\n' + + 'doSomething(\'js\')\n' + + 'doSomethingElse()\n' + + 'test = \'other string\'' + ); + + run( + language, + + 'quoted comment with multi-line string', + + '/* someone\'s comment */\n' + + 'doSomething(\'js\')\n' + + 'doSomethingElse()\n' + + 'test = \'other string\n' + + 'string is still going\'', + + '/* someone\'s comment */\n' + + 'doSomething(\'js\')\n' + + 'doSomethingElse()\n' + + 'test = \'other string\n' + + 'string is still going\'' + ); + + run( + language, + + 'quoted comment with multi-line directly after', + + '// someone\'s comment\n' + + 'test = \'blah blah\n' + + 'blah blah blah\'', + + '// someone\'s comment\n' + + 'test = \'blah blah\n' + + 'blah blah blah\'' + ); + + run( + language, + + 'comment inline 1', + + '// this is a comment', + + '// this is a comment' + ); + + run( + language, + + 'comment inline 2', + + '// this is a comment\n' + + '//\n' + + '// other comment', + + '// this is a comment\n' + + '//\n' + + '// other comment' + ); + + run( + language, + + 'comment inline 3', + + '# this is a comment', + + '# this is a comment' + ); + + run( + language, + + 'comment inline 4', + + '/* this is a comment */', + + '/* this is a comment */' + ); + + run( + language, + + 'comment block', + + '/**\n' + + ' * test comment\n' + + ' */', + + '/**\n' + + ' * test comment\n' + + ' */' + ); + + run( + language, + + 'integer', + + '23', + + '23' + ); + + run( + language, + + 'decimal', + + '3.21', + + '3.21' + ); + + run( + language, + + 'float', + + '23.5f', + + '23.5f' + ); + + run( + language, + + 'exponential', + + '121.22e-10', + + '121.22e-10' + ); + + run( + language, + + 'hex', + + '0x0A', + + '0x0A' + ); + + run( + language, + + 'language constants', + + 'if (thing == true) {\n' + + ' thing = false;\n' + + '}\n' + + 'other_thing = null;' , + + 'if (thing == true) {\n' + + ' thing = false;\n' + + '}\n' + + 'other_thing = null;' + ); + + run( + language, + + 'comment after string', + + 'var test = "some string" // comment after string', + + 'var test = "some string" // comment after string' + ); + + run( + language, + + 'operator math', + + 'value = (25 * 2) + (14 / 2) - 10', + + 'value = (25 * 2) + (14 / 2) - 10' + ); + + run( + language, + + 'operator comparison not escaped', + + 'if ((test > 0 && test < 25) || test == 50) {\n' + + ' print "nice";\n' + + '}', + + 'if ((test > 0 && test < 25) || test == 50) {\n' + + ' print "nice";\n' + + '}' + ); + + run( + language, + + 'operator comparison escaped', + + 'if ((test > 0 && test < 25) || test == 50) {\n' + + ' print "nice";\n' + + '}', + + 'if ((test > 0 && test < 25) || test == 50) {\n' + + ' print "nice";\n' + + '}' + ); + + run( + language, + + 'function call', + + 'someFunction(arg);', + + 'someFunction(arg);' + ); + + run( + language, + + 'function definition', + + 'function someFunction(arg) {\n' + + ' return strtolower(arg);\n' + + '}', + + 'function someFunction(arg) {\n' + + ' return strtolower(arg);\n' + + '}' + ); + + skip( + language, + + 'comment after website string', + + 'var test = "http://website.com/index.html"; // sweet website', + + 'var test = "http://website.com/index.html"; // sweet website' + ); + + run( + language, + + 'escaped string single quote', + + 'var test = \'someone\\\'s string\'', + + 'var test = \'someone\\\'s string\'' + ); + + run( + language, + + 'escaped string double quote', + + 'var test = \"someone\\\"s string\"', + + 'var test = "someone\\\"s string"' + ); + + run( + language, + + 'string concatenation with +', + + 'time=h+":"+m+":"+s;', + + 'time=h+":"+m+":"+s;' + ); + + run( + language, + + 'string concatenation with .', + + 'NV_CSS.\'/app.css\'', + + 'NV_CSS.\'/app.css\'' + ); +}); diff --git a/tests/language/test.haskell.js b/tests/language/test.haskell.js new file mode 100644 index 00000000..b98c83c4 --- /dev/null +++ b/tests/language/test.haskell.js @@ -0,0 +1,157 @@ +/* global describe, run */ +var language = 'haskell'; + +describe(language, function() { + run( + language, + '-- comments', + '-- fn2 :: String -> [String]', + '-- fn2 :: String -> [String]' + ); + + run( + language, + '{-- comments --}', + '{-- this is a comment --}', + '{-- this is a comment --}' + ); + + run( + language, + '{-- comments with multiple lines --}', + '{--\n' + + ' this is a comment\n' + + '--}', + '{--\n this is a comment\n--}' + ); + + run( + language, + 'module declaration', + 'module A where', + 'module A where' + ); + + run( + language, + 'module with exported functions and comments', + 'module A (\n' + + ' B,\n' + + ' C(..),\n' + + ' fn1,\n' + + ' fn2, -- fn2 :: String -> [String]\n' + + ' fn3\n' + + ') where', + 'module A (\n B,\n C(..),\n fn1,\n fn2, -- fn2 :: String -> [String]\n fn3\n) where' + ); + + run( + language, + 'import', + 'import Data.List qualified DL\n' + + 'import Prelude hiding ((*))', + 'import Data.List qualified DL\nimport Prelude hiding ((*))' + ); + + run( + language, + 'function declaration', + 'fn :: String -> [(String,Int)]', + 'fn :: String -> [(String,Int)]' + ); + + run( + language, + 'IO function declaration', + 'getLine :: IO String\n' + + 'getLine = do c <- getChar\n' + + ' if c == \'\\n\' then return ""\n' + + ' else do s <- getLine\n' + + ' return (c:s)', + 'getLine :: IO String\ngetLine = do c <- getChar\n if c == \'\\n\' then return ""\n else do s <- getLine\n return (c:s)' + ); + + run( + language, + 'infix declaration', + 'infix 4 >*', + 'infix 4 >*' + ); + + run( + language, + 'let declaration', + 'let a\' = s :: String\n' + + 'in a\' \"some string\"', + 'let a\' = s :: String\nin a\' "some string"' + ); + + run( + language, + 'if declaration', + 'if a == 0\n' + + 'then A\n' + + 'else B', + 'if a == 0\nthen A\nelse B' + ); + + run( + language, + 'data types decaration', + 'data S = S1 { x :: Int }\n' + + ' | S2 { x :: Int }\n' + + ' deriving (Show, Eq)\n\n' + + 'newtype NString = [String]\n\n' + + 'type String = [Char]', + 'data S = S1 { x :: Int }\n | S2 { x :: Int }\n deriving (Show, Eq)\n\nnewtype NString = [String]\n\ntype String = [Char]' + ); + + run( + language, + 'classes', + 'class Eq a where\n' + + ' (==), (/=) :: a -> a -> Bool\n\n' + + ' x /= y = not (x == y)\n' + + ' x == y = not (x /= y)', + 'class Eq a where\n (==), (/=) :: a -> a -> Bool\n\n x /= y = not (x == y)\n x == y = not (x /= y)' + ); + + run( + language, + 'instances', + 'instance Monad IO where\n' + + ' fail s = ioError (userError s)', + 'instance Monad IO where\n fail s = ioError (userError s)' + ); + + run( + language, + 'list comprehensions', + '[ x | x <- x, x <- x ]', + '[ x | x <- x, x <- x ]' + ); + + run( + language, + 'case statement', + 'case e of\n' + + ' A -> Just a\n' + + ' _ -> Nothing', + 'case e of\n A -> Just a\n _ -> Nothing' + ); + + run( + language, + 'C Preprocessors', + '#ifndef __NHC__\n' + + 'exitWith :: ExitCode -> IO a\n' + + 'exitWith ExitSuccess = throwIO ExitSuccess\n' + + 'exitWith code@(ExitFailure n)\n' + + ' | n /= 0 = throwIO code\n' + + '#ifdef __GLASGOW_HASKELL__\n' + + ' | otherwise = ioError (IOError Nothing InvalidArgument "exitWith" "ExitFailure 0" Nothing Nothing)\n' + + '#endif\n' + + '#endif /* ! __NHC__ */', + '#ifndef __NHC__\nexitWith :: ExitCode -> IO a\nexitWith ExitSuccess = throwIO ExitSuccess\nexitWith code@(ExitFailure n)\n | n /= 0 = throwIO code\n#ifdef __GLASGOW_HASKELL__\n | otherwise = ioError (IOError Nothing InvalidArgument "exitWith" "ExitFailure 0" Nothing Nothing)\n#endif\n#endif /* ! __NHC__ */' + ); +}); diff --git a/tests/language/test.html.js b/tests/language/test.html.js new file mode 100644 index 00000000..5df646f3 --- /dev/null +++ b/tests/language/test.html.js @@ -0,0 +1,240 @@ +/* global describe, run */ +var language = 'html'; + +describe(language, function() { + run( + language, + + 'comment', + + '', + + '<!-- this is a comment -->' + ); + + + run( + language, + + 'multi-line comment', + + '', + + '<!-- this is a comment\n' + + 'on two lines -->' + ); + + + run( + language, + + 'paragraph', + + '

this is a paragraph

', + + '<p class="test">this is a paragraph</p>' + ); + + + run( + language, + + 'inline php', + + '
    \n' + + ' \n' + + '
  • title; ?>
  • \n' + + ' \n' + + '
', + + '<ul class="articles">\n' + + ' <?php foreach ($articles as $article): ?>\n' + + ' <li><?php echo $article->title; ?></li>\n' + + ' <?php endforeach; ?>\n' + + '</ul>' + ); + + run( + language, + + 'php short tag', + + '<? foreach ($users as $key => $user): ?>\n' + + '

<?= $user->getBio() ?>

\n' + + '<? endforeach ?>', + + '<? foreach ($users as $key => $user): ?>\n' + + ' <p><?= $user->getBio() ?></p>\n' + + '<? endforeach ?>' + ); + + run( + language, + + 'xml declaration', + + '', + + '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' + ); + + + run( + language, + + 'inline css 1', + + '', + + '<style type="text/css">\n' + + ' body span.blah {\n' + + ' background: #000;\n' + + ' color: #fff;\n' + + ' }\n' + + '</style>' + ); + + + run( + language, + + 'inline css 2', + + '', + + '<style>\n' + + ' body span.blah {\n' + + ' background: #000;\n' + + ' color: #fff;\n' + + ' }\n' + + '</style>' + ); + + + run( + language, + + 'inline js 1', + + '', + + '<script type="text/javascript">\n' + + ' function prettyCool() {\n' + + ' doSomeJQueryOrWhatever();\n' + + ' }\n' + + '</script>' + ); + + + run( + language, + + 'inline js 2', + + '', + + '<script>\n' + + ' function prettyCool() {\n' + + ' doSomeJQueryOrWhatever();\n' + + ' }\n' + + '</script>' + ); + + + run( + language, + + 'js include', + + '', + + '<script src="http://somewebsite.com/some-script.js" type="text/javascript"></script>' + ); + + run( + language, + + 'attribute no quotes', + + '

test

', + + '<p class=test>test</p>' + ); + + run( + language, + + 'attribute alone', + + '', + + '<input type="checkbox" name="whatever" checked>' + ); + + run( + language, + + 'attribute middle', + + '', + + '<input checked type="checkbox">' + ); + + run( + language, + + 'attribute alone self close', + + '', + + '<input type="checkbox" name="whatever" checked />' + ); + + run( + language, + + 'attribute camel case', + + '', + + '<button onClick="test()">Click me</button>' + ); + + run( + language, + + 'string inside tags', + + '
def openFile(path):\n' +
+        'file = open(path, "r")\n' +
+        'content = file.read()\n' +
+        'file.close()\n' +
+        'return content
', + + '<pre><code data-language="python">def openFile(path):\n' + + 'file = open(path, "r")\n' + + 'content = file.read()\n' + + 'file.close()\n' + + 'return content</code></pre>' + ); +}); diff --git a/tests/language/test.java.js b/tests/language/test.java.js new file mode 100644 index 00000000..6b149209 --- /dev/null +++ b/tests/language/test.java.js @@ -0,0 +1,109 @@ +/* global describe, run */ +var language = 'java'; + +describe(language, function() { + run( + language, + "package declaration", + 'package com.example.rainbow;', + 'package com.example.rainbow;' + ); + + run( + language, + "import statement", + 'import com.example.rainbow.util.RainbowUtil;', + 'import com.example.rainbow.util.RainbowUtil;' + ); + + run( + language, + "multi-line comment", + '/**\n * This is a Javadoc style comment. It is pretty awesome.\n */', + '/**\n * This is a Javadoc style comment. It is pretty awesome.\n */' + ); + + run( + language, + "single-line comment", + '// This is a good comment.', + '// This is a good comment.' + ); + + run( + language, + "complicated class declaration", + 'public class Rainbow> extends Spectrum implements HasColors, IsPretty {', + 'public class Rainbow<T, List<? extends T>> extends Spectrum implements HasColors, IsPretty {' + ); + + run( + language, + "simple class declaration", + 'public class Rainbow {', + 'public class Rainbow {' + ); + + run( + language, + "constant declaration", + 'private static final int RESOLUTION = 7;', + 'private static final int RESOLUTION = 7;' + ); + + run( + language, + "field declaration", + 'private final String name;', + 'private final String name;' + ); + + run( + language, + "method declaration", + 'public void shine() {', + 'public void shine() {' + ); + + run( + language, + "simple annotation", + '@Override', + '@Override' + ); + + run( + language, + "complex annotation", + '@RequestMapping( value = "/rainbow", method = Method.POST )', + '@RequestMapping( value = "/rainbow", method = Method.POST )' + ); + + run( + language, + "string concatenation", + '"I found " + numberOfTurtles + " turtles."', + '"I found " + numberOfTurtles + " turtles."' + ); + + run( + language, + "local method invocation", + 'wait(1000L)', + 'wait(1000L)' + ); + + run( + language, + "static method invocation", + 'System.out.println("Hello, world!");', + 'System.out.println("Hello, world!");' + ); + + run( + language, + "variable assignment", + 'int numberOfColors = (int) Math.ceil( Math.random() * 256 );', + 'int numberOfColors = (int) Math.ceil( Math.random() * 256 );' + ); +}); diff --git a/tests/language/test.javascript.js b/tests/language/test.javascript.js new file mode 100644 index 00000000..12223f47 --- /dev/null +++ b/tests/language/test.javascript.js @@ -0,0 +1,224 @@ +/* global describe, run */ +var language = 'javascript'; + +describe(language, function() { + run( + language, + + 'selector 1', + + '$.get()', + + '$.get()' + ); + + run( + language, + + 'selector 2', + + ' $(\'.some_class\').show()', + + ' $(\'.some_class\').show()' + ); + + run( + language, + + 'window', + + 'console.log(window.scrollX)', + + 'console.log(window.scrollX)' + ); + + run( + language, + + 'document', + + 'document.getElementById(\'#some_id\')', + + 'document.getElementById(\'#some_id\')' + ); + + run( + language, + + 'regex', + + 'var pattern = /\\((.*?)\\)/g', + + 'var pattern = /\\((.*?)\\)/g' + ); + + run( + language, + + 'regex 2', + + 'var pattern = /true/', + + 'var pattern = /true/' + ); + + run( + language, + + 'string no regex', + + 'var test = "http://website.com/could/match/regex/i";', + + 'var test = "http://website.com/could/match/regex/i";' + ); + + run( + language, + + 'single line comment vs. regex', + + 'var Animal = function() { /* some comment */ };', + + 'var Animal = function() { /* some comment */ };' + ); + + run( + language, + + 'class instantiation', + + 'var animal = new Animal();', + + 'var animal = new Animal();' + ); + + run( + language, + + 'inline function', + + 'var foo = true,\n' + + ' something = function() {\n' + + ' // do something\n' + + ' };', + + 'var foo = true,\n' + + ' something = function() {\n' + + ' // do something\n' + + ' };' + ); + + run( + language, + + 'inline function beginning of line', + + 'something = function() {\n' + + ' // do something\n' + + '};', + + 'something = function() {\n' + + ' // do something\n' + + '};' + ); + + run( + language, + + 'functions in object', + + 'window.Rainbow = {\n' + + ' color: function() {\n' + + ' // do something\n' + + ' },\n' + + '\n' + + ' other: function() {}\n' + + '};', + + 'window.Rainbow = {\n' + + ' color: function() {\n' + + ' // do something\n' + + ' },\n' + + '\n' + + ' other: function() {}\n' + + '};' + ); + + run( + language, + + 'JSON 1', + + '{\n' + + ' "generated_in": "0.0423",\n' + + ' "stat": "fail"\n' + + ' "err": {\n' + + ' "code": "1",\n' + + ' "expl": "The user id or name was either not valid or not provided.",\n' + + ' "msg": "User not found"\n' + + ' }\n' + + '}', + + '{\n' + + ' "generated_in": "0.0423",\n' + + ' "stat": "fail"\n' + + ' "err": {\n' + + ' "code": "1",\n' + + ' "expl": "The user id or name was either not valid or not provided.",\n' + + ' "msg": "User not found"\n' + + ' }\n' + + '}' + ); + + run( + language, + + 'JSON 2', + + '{\n' + + ' "generated_in":"0.0423",\n' + + ' "stat":"fail"\n' + + ' "err":{\n' + + ' "code":"1",\n' + + ' "expl":"The user id or name was either not valid or not provided.",\n' + + ' "msg":"User not found"\n' + + ' }\n' + + '}', + + '{\n' + + ' "generated_in":"0.0423",\n' + + ' "stat":"fail"\n' + + ' "err":{\n' + + ' "code":"1",\n' + + ' "expl":"The user id or name was either not valid or not provided.",\n' + + ' "msg":"User not found"\n' + + ' }\n' + + '}' + ); + + run( + language, + + 'multiple var declarations', + + 'var language = getLanguage(source);\n' + + 'var parseAndHighlight = function() {};\n' + + 'var parseAndHighlight2 = function() {};', + + 'var language = getLanguage(source);\n' + + 'var parseAndHighlight = function() {};\n' + + 'var parseAndHighlight2 = function() {};' + ); + + run( + language, + + 'quotes inside curly brackets', + + '{\' \'}\n' + + 'var str = \'something\';', + + '{\' \'}\n' + + 'var str = \'something\';' + ); +}); diff --git a/tests/language/test.php.js b/tests/language/test.php.js new file mode 100644 index 00000000..93c859ca --- /dev/null +++ b/tests/language/test.php.js @@ -0,0 +1,407 @@ +/* global describe, run */ +var language = 'php'; + +describe(language, function() { + run( + language, + + 'echo', + + 'echo \'hello world\';', + + 'echo \'hello world\';' + ); + + run( + language, + + 'variable', + + '$foo = true;', + + '$foo = true;' + ); + + skip( + language, + + 'variable variable', + + '$$foo = true;', + + '$$foo = true;' + ); + + run( + language, + + 'string concatenation', + + "$foo = 'test' . 'string' . 'concatenation';", + + '$foo = \'test\' . \'string\' . \'concatenation\';' + ); + + run( + language, + + 'include 1', + + "include 'App.php';", + + 'include \'App.php\';' + ); + + run( + language, + + 'include 2', + + "include_once('App.php');", + + 'include_once(\'App.php\');' + ); + + run( + language, + + 'instanceof', + + "$is_array_object = $collection instanceof ArrayObject;", + + '$is_array_object = $collection instanceof ArrayObject;' + ); + + run( + language, + + 'instanceof namespace class', + + "$is_user = $object instanceof App\\User;", + + '$is_user = $object instanceof App\\User;' + ); + + run( + language, + + 'array stuff', + + '$turtles = array(\n' + + ' \'leonardo\',\n' + + ' \'michaelangelo\',\n' + + ' \'donatello\',\n' + + ' \'raphael\'\n' + + ');\n' + + '\n' + + '$exists = array_key_exists(0, $turtles);', + + '$turtles = array(\n' + + ' \'leonardo\',\n' + + ' \'michaelangelo\',\n' + + ' \'donatello\',\n' + + ' \'raphael\'\n' + + ');\n' + + '\n' + + '$exists = array_key_exists(0, $turtles);' + ); + + run( + language, + + 'php tag', + + '<?php echo $foo; ?>', + + '<?php echo $foo; ?>' + ); + + run( + language, + + 'php tag 2', + + '<?php echo \'?>\'; ?>', + + '<?php echo \'?>\'; ?>' + ); + + run( + language, + + 'namespace declaration', + + 'namespace Sonic\\Database;', + + 'namespace Sonic\\Database;' + ); + + run( + language, + + 'use declaration', + + 'use Sonic;', + + 'use Sonic;' + ); + + run( + language, + + 'class declaration', + + 'class MyClass {}', + + 'class MyClass {}' + ); + + run( + language, + + 'trait declaration', + + 'trait MyClass {}', + + 'trait MyClass {}' + ); + + run( + language, + + 'interface declaration', + + 'interface IMyClass {}', + + 'interface IMyClass {}' + ); + + run( + language, + + 'abstract class declaration', + + 'abstract class MyClass {}', + + 'abstract class MyClass {}' + ); + + run( + language, + + 'final class declaration', + + 'final class TestClass\n' + + '{\n' + + '}', + + 'final class TestClass\n' + + '{\n' + + '}' + ); + + run( + language, + + 'class with an implementation declaration', + + 'class Collection implements IList {}', + + 'class Collection implements IList {}' + ); + + run( + language, + + 'child class declaration', + + 'class Collection extends ArrayObject {}', + + 'class Collection extends ArrayObject {}' + ); + + run( + language, + + 'child class with an implementation declaration', + + 'class Collection extends ArrayObject implements IList {}', + + 'class Collection extends ArrayObject implements IList {}' + ); + + run( + language, + + 'final child class declaration', + + 'final class TestClass extends \\Some\\Other\\Class {}', + + 'final class TestClass extends \\Some\\Other\\Class {}' + ); + + run( + language, + + 'test static', + + 'self::_doSomething();\n' + + 'static::_doSomethingElse();', + + 'self::_doSomething();\n' + + 'static::_doSomethingElse();' + ); + + run( + language, + + 'test magic function', + + 'function __autoload($class)\n' + + '{\n' + + ' // do whatever\n' + + '}', + + 'function __autoload($class)\n' + + '{\n' + + ' // do whatever\n' + + '}' + ); + + run( + language, + + 'test magic method', + + 'class SomeThing\n' + + '{\n' + + ' protected $_foo;\n' + + '\n' + + ' public function __construct($foo)\n' + + ' {\n' + + ' $this->_foo = $foo;\n' + + ' }\n' + + '}', + + 'class SomeThing\n' + + '{\n' + + ' protected $_foo;\n' + + '\n' + + ' public function __construct($foo)\n' + + ' {\n' + + ' $this->_foo = $foo;\n' + + ' }\n' + + '}' + ); + + run( + language, + + 'test new class', + + 'new SomeClass();', + + 'new SomeClass();' + ); + + run( + language, + + 'test new namespace class', + + 'new Sonic\\Database\\Query();', + + 'new Sonic\\Database\\Query();' + ); + + run( + language, + + 'test new class without parenthesis', + + 'new Sonic\\Controller;', + + 'new Sonic\\Controller;' + ); + + run( + language, + + 'test static class call', + + '$path = Sonic\\App::getInstance()->getPath();', + + '$path = Sonic\\App::getInstance()->getPath();' + ); + + run( + language, + + 'constant language', + + 'true; TRUE;', + + 'true; TRUE;' + ); + + run( + language, + + 'constant', + + 'TEST_CONSTANT', + + 'TEST_CONSTANT' + ); + + run( + language, + + 'constant 2', + + '(TEST_CONSTANT_2)', + + '(TEST_CONSTANT_2)' + ); + + run( + language, + + 'class constant', + + '$version = Sonic\\App::VERSION', + + '$version = Sonic\\App::VERSION' + ); + + run( + language, + + 'static variable access', + + '$foo = Sonic\\App::$static_property;', + + '$foo = Sonic\\App::$static_property;' + ); + + run( + language, + + 'type hint', + + 'public static function getForUser(User $user, Sort $sort) {}', + + 'public static function getForUser(User $user, Sort $sort) {}' + ); + + + run( + language, + + 'type hint with namespace', + + 'public static function getForUser(\\SomeApp\\User $user) {}', + + 'public static function getForUser(\\SomeApp\\User $user) {}' + ); +}); diff --git a/tests/language/test.python.js b/tests/language/test.python.js new file mode 100644 index 00000000..468a9bbe --- /dev/null +++ b/tests/language/test.python.js @@ -0,0 +1,210 @@ +/* global describe, run */ +var language = 'python'; + +describe(language, function() { + run( + language, + + 'no self', + + 'print self.something', + + 'print self.something' + ); + + run( + language, + + 'comment', + + '# this is a comment', + + '# this is a comment' + ); + + run( + language, + + 'language constants', + + 'var1 = None\n' + + 'var2 = True\n' + + 'someFunction(var3=False)', + + 'var1 = None\n' + + 'var2 = True\n' + + 'someFunction(var3=False)' + ); + + run( + language, + + 'object', + + 'object', + + 'object' + ); + + run( + language, + + 'import', + + 'from SomePackage import SomeThing', + + 'from SomePackage import SomeThing' + ); + + run( + language, + + 'class', + + 'class Something(object):\n' + + ' pass', + + 'class Something(object):\n' + + ' pass' + ); + + run( + language, + + 'special method', + + 'def __init__(self, some_var):\n' + + ' pass', + + 'def __init__(self, some_var):\n' + + ' pass' + ); + + run( + language, + + 'function', + + 'def openFile(path):\n' + + ' file = open(path, "r")\n' + + ' content = file.read()\n' + + ' file.close()\n' + + ' return content', + + 'def openFile(path):\n' + + ' file = open(path, "r")\n' + + ' content = file.read()\n' + + ' file.close()\n' + + ' return content' + ); + + run( + language, + + 'decorator', + + '@makebold\n' + + '@makeitalic\n' + + 'def hello():\n' + + ' return "hello world"', + + '@makebold\n' + + '@makeitalic\n' + + 'def hello():\n' + + ' return "hello world"' + ); + + run( + language, + + '__main__', + + 'if __name__ == \'__main__\':\n' + + ' pass', + + 'if __name__ == \'__main__\':\n' + + ' pass' + ); + + run( + language, + + 'try catch', + + 'try:\n' + + ' import cPickle as pickle\n' + + 'except ImportError:\n' + + ' import pickle', + + 'try:\n' + + ' import cPickle as pickle\n' + + 'except ImportError:\n' + + ' import pickle' + ); + + run( + language, + + 'docstring single line double quotes', + + '"""docstring test"""', + + '"""docstring test"""' + ); + + run( + language, + + 'docstring single line single quotes', + + "'''docstring test'''", + + '\'\'\'docstring test\'\'\'' + ); + + run( + language, + + 'docstring multiline', + + '"""test\n' + + 'multiline\n' + + 'yes"""', + + '"""test\n' + + 'multiline\n' + + 'yes"""' + ); + + run( + language, + + 'decorator with dot', + + '@tornado.web.asynchronous', + + '@tornado.web.asynchronous' + ); + + run( + language, + + 'multiple docstrings', + + '"""\n' + + 'x\n' + + '"""\n' + + '2 + 2\n' + + '"""\n' + + 'y\n' + + '"""', + + '"""\n' + + 'x\n' + + '"""\n' + + '2 + 2\n' + + '"""\n' + + 'y\n' + + '"""' + ); +}); diff --git a/tests/language/test.r.js b/tests/language/test.r.js new file mode 100644 index 00000000..ae47c66f --- /dev/null +++ b/tests/language/test.r.js @@ -0,0 +1,126 @@ +/* global describe, run */ +var language = 'r'; + +describe(language, function() { + run( + language, + + 'comments', + + '# A comment\n' + + 'a <- "b" # Another comment', + + '# A comment\n' + + 'a <- "b" # Another comment' + ); + + run( + language, + + 'assignment', + + 'foo.bar <- "foo"\n' + + 'baz1 = 1.62e-4', + + 'foo.bar <- "foo"\n' + + 'baz1 = 1.62e-4' + ); + + run( + language, + + 'constants', + + 'baz <- NA\n' + + 'my.pi <- pi\n' + + 'all.letters <- c(LETTERS, letters)\n' + + 'xrange <- c(-Inf, TRUE)', + + 'baz <- NA\n' + + 'my.pi <- pi\n' + + 'all.letters <- c(LETTERS, letters)\n' + + 'xrange <- c(-Inf, TRUE)' + ); + + run( + language, + + 'operators', + + 'beta.hat <- solve(t(X) %*% X) %*% t(X) %*% y\n' + + 'bound.rect <- grid::rectGrob()\n' + + 'my_seq <- 1:10\n' + + 'is_in_seq <- c(2, 7, 23) %in% my_seq\n' + + 'plot(y ~ x, type = "l")', + + 'beta.hat <- solve(t(X) %*% X) %*% t(X) %*% y\n' + + 'bound.rect <- grid::rectGrob()\n' + + 'my_seq <- 1:10\n' + + 'is_in_seq <- c(2, 7, 23) %in% my_seq\n' + + 'plot(y ~ x, type = "l")' + ); + + /** + * Note that the second function is intentionally not a function call, + * just testing that the regex is matching only 'function' and not .+function + */ + run( + language, + + 'function creation', + + 'square <- function(x) x * x\n' + + 'square2 <- testfunction(x) x * x\n' + + 'area <- function (r) {\n' + + ' pi * r^2\n' + + '}', + + 'square <- function(x) x * x\n' + + 'square2 <- testfunction(x) x * x\n' + + 'area <- function (r) {\n' + + ' pi * r^2\n' + + '}' + ); + + skip( + language, + + 'variable', + + 'tmp <- 1\n' + + 'another.tmp <- 2\n' + + 'this.is.a.var <- 3', + + 'tmp <- 1\n' + + 'another.tmp <- 2\n' + + 'this.is.a.var <- 3' + ); + + skip( + language, + + 'subsetting', + + 'tmp[1]\n' + + 'tmp[["test"]]', + + 'tmp[1]\n' + + 'tmp[["test"]]' + ); + + skip( + language, + + 'support functions', + + 'logical(10)\n' + + 'test.logical(10)\n' + + 'data.frame(a = 1:10, b = 15:24)\n' + + 'complex(real = 1, imaginary = 0.5)', + + 'logical(10)\n' + + 'test.logical(10)\n' + + 'data.frame(a = 1:10, b = 15:24)\n' + + 'complex(real = 1, imaginary = 0.5)' + ); +}); diff --git a/tests/language/test.ruby.js b/tests/language/test.ruby.js new file mode 100644 index 00000000..3582cb42 --- /dev/null +++ b/tests/language/test.ruby.js @@ -0,0 +1,57 @@ +/* global describe, run */ +var language = 'ruby'; + +describe(language, function() { + + run( + language, + + 'multiple (non-greedy) strings', + + '"me" && "you"\n', + + '"me"' + + ' && ' + + '"you"\n' + ); + + run( + language, + + 'interpolated strings', + + '"chapter #{x+5}"\n', + + '"chapter ' + + '#{' + + 'x+' + + '5}' + + '"\n' + ); + + run( + language, + + 'string in brackets', + + 'foo = ["one", "two", "three"];', + + 'foo = ['+ + '"one", '+ + '"two", ' + + '"three"];' + ); + + run( + language, + + '__END__', + + 'class Test;end;\n__END__\nthis is just text\ntrue\n', + + 'class Test;' + + 'end;\n__END__\n' + + 'this is just text\ntrue\n' + ); + +}); diff --git a/tests/language/test.smalltalk.js b/tests/language/test.smalltalk.js new file mode 100644 index 00000000..383ce495 --- /dev/null +++ b/tests/language/test.smalltalk.js @@ -0,0 +1,304 @@ +/* global describe, run */ +var language = 'smalltalk'; + +describe(language, function() { + run( + language, + + 'constant true', + + 'true', + + 'true' + ); + + run( + language, + + 'constant false', + + 'false', + + 'false' + ); + + run( + language, + + 'constant nil', + + 'nil', + + 'nil' + ); + + run( + language, + + 'self pseudovariable', + + 'self', + + 'self' + ); + + run( + language, + + 'thisContext pseudovariable', + + 'thisContext', + + 'thisContext' + ); + + run( + language, + + 'two-character operator !!', + + '!!', + + '!!' + ); + + run( + language, + + 'two-character operator //', + + '//', + + '//' + ); + + run( + language, + + '| delimiter', + + '|', + + '|' + ); + + run( + language, + + '|| binary selector', + + '||', + + '||' + ); + + run( + language, + + 'HTML-unfriendly operator', + + '&', + + '&' + ); + + run( + language, + + 'three-character operator', + + '>>=', + + '>>=' + ); + + run( + language, + + 'String-like Symbol', + + "#'this is a symbol'", + + "#'this is a symbol'" + ); + + run( + language, + + 'Symbol', + + "#thisIsaSymbol0", + + "#thisIsaSymbol0" + ); + + run( + language, + + 'String', + + "'This is a string'", + + '\'This is a string\'' + ); + + run( + language, + + 'Comment', + + '"This is a comment"', + + '"This is a comment"' + ); + + run( + language, + + 'Comment in between message sends', + + 'self "this is a comment" foo', + + 'self "this is a comment" foo' + ); + + run( + language, + + 'Integer', + + '987654321', + + '987654321' + ); + + run( + language, + + 'Negative integer', + + '-987654321', + + '-987654321' + ); + + run( + language, + + 'Exponent integer', + + '987654321e10', + + '987654321e10' + ); + + run( + language, + + 'Negative exponent integer', + + '-987654321e10', + + '-987654321e10' + ); + + run( + language, + + 'Radix Integer', + + '16r987654321deadbeef', + + '16r987654321deadbeef' + ); + + run( + language, + + 'Negative radix Integer', + + '16r-987654321deadbeef', + + '16r-987654321deadbeef' + ); + + run( + language, + + 'Float', + + '987654321.0', + + '987654321.0' + ); + + run( + language, + + 'Negative float', + + '-987654321.0', + + '-987654321.0' + ); + + run( + language, + + 'Exponent float', + + '1.0e10', + + '1.0e10' + ); + + run( + language, + + 'Negative exponent float', + + '1.0e-10', + + '1.0e-10' + ); + + run( + language, + + 'Negative exponent negative float', + + '-1.0e-10', + + '-1.0e-10' + ); + + run( + language, + + 'Scaled decimal', + + '1.0s10', + + '1.0s10' + ); + + run( + language, + + 'Class name, normal', + + 'Class', + + 'Class' + ); + + run( + language, + + 'Class name, with digits', + + 'Class0zero', + + 'Class0zero' + ); +}); diff --git a/tests/libs/chai-1.6.0.js b/tests/libs/chai-1.6.0.js new file mode 100644 index 00000000..629e199b --- /dev/null +++ b/tests/libs/chai-1.6.0.js @@ -0,0 +1,4251 @@ +;(function(){ + +/** + * Require the given path. + * + * @param {String} path + * @return {Object} exports + * @api public + */ + +function require(path, parent, orig) { + var resolved = require.resolve(path); + + // lookup failed + if (null == resolved) { + orig = orig || path; + parent = parent || 'root'; + var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); + err.path = orig; + err.parent = parent; + err.require = true; + throw err; + } + + var module = require.modules[resolved]; + + // perform real require() + // by invoking the module's + // registered function + if (!module.exports) { + module.exports = {}; + module.client = module.component = true; + module.call(this, module.exports, require.relative(resolved), module); + } + + return module.exports; +} + +/** + * Registered modules. + */ + +require.modules = {}; + +/** + * Registered aliases. + */ + +require.aliases = {}; + +/** + * Resolve `path`. + * + * Lookup: + * + * - PATH/index.js + * - PATH.js + * - PATH + * + * @param {String} path + * @return {String} path or null + * @api private + */ + +require.resolve = function(path) { + if (path.charAt(0) === '/') path = path.slice(1); + var index = path + '/index.js'; + + var paths = [ + path, + path + '.js', + path + '.json', + path + '/index.js', + path + '/index.json' + ]; + + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + if (require.modules.hasOwnProperty(path)) return path; + } + + if (require.aliases.hasOwnProperty(index)) { + return require.aliases[index]; + } +}; + +/** + * Normalize `path` relative to the current path. + * + * @param {String} curr + * @param {String} path + * @return {String} + * @api private + */ + +require.normalize = function(curr, path) { + var segs = []; + + if ('.' != path.charAt(0)) return path; + + curr = curr.split('/'); + path = path.split('/'); + + for (var i = 0; i < path.length; ++i) { + if ('..' == path[i]) { + curr.pop(); + } else if ('.' != path[i] && '' != path[i]) { + segs.push(path[i]); + } + } + + return curr.concat(segs).join('/'); +}; + +/** + * Register module at `path` with callback `definition`. + * + * @param {String} path + * @param {Function} definition + * @api private + */ + +require.register = function(path, definition) { + require.modules[path] = definition; +}; + +/** + * Alias a module definition. + * + * @param {String} from + * @param {String} to + * @api private + */ + +require.alias = function(from, to) { + if (!require.modules.hasOwnProperty(from)) { + throw new Error('Failed to alias "' + from + '", it does not exist'); + } + require.aliases[to] = from; +}; + +/** + * Return a require function relative to the `parent` path. + * + * @param {String} parent + * @return {Function} + * @api private + */ + +require.relative = function(parent) { + var p = require.normalize(parent, '..'); + + /** + * lastIndexOf helper. + */ + + function lastIndexOf(arr, obj) { + var i = arr.length; + while (i--) { + if (arr[i] === obj) return i; + } + return -1; + } + + /** + * The relative require() itself. + */ + + function localRequire(path) { + var resolved = localRequire.resolve(path); + return require(resolved, parent, path); + } + + /** + * Resolve relative to the parent. + */ + + localRequire.resolve = function(path) { + var c = path.charAt(0); + if ('/' == c) return path.slice(1); + if ('.' == c) return require.normalize(p, path); + + // resolve deps by returning + // the dep in the nearest "deps" + // directory + var segs = parent.split('/'); + var i = lastIndexOf(segs, 'deps') + 1; + if (!i) i = 0; + path = segs.slice(0, i + 1).join('/') + '/deps/' + path; + return path; + }; + + /** + * Check if module is defined at `path`. + */ + + localRequire.exists = function(path) { + return require.modules.hasOwnProperty(localRequire.resolve(path)); + }; + + return localRequire; +}; +require.register("chai/index.js", function(exports, require, module){ +module.exports = require('./lib/chai'); + +}); +require.register("chai/lib/chai.js", function(exports, require, module){ +/*! + * chai + * Copyright(c) 2011-2013 Jake Luer + * MIT Licensed + */ + +var used = [] + , exports = module.exports = {}; + +/*! + * Chai version + */ + +exports.version = '1.6.0'; + +/*! + * Primary `Assertion` prototype + */ + +exports.Assertion = require('./chai/assertion'); + +/*! + * Assertion Error + */ + +exports.AssertionError = require('./chai/error'); + +/*! + * Utils for plugins (not exported) + */ + +var util = require('./chai/utils'); + +/** + * # .use(function) + * + * Provides a way to extend the internals of Chai + * + * @param {Function} + * @returns {this} for chaining + * @api public + */ + +exports.use = function (fn) { + if (!~used.indexOf(fn)) { + fn(this, util); + used.push(fn); + } + + return this; +}; + +/*! + * Core Assertions + */ + +var core = require('./chai/core/assertions'); +exports.use(core); + +/*! + * Expect interface + */ + +var expect = require('./chai/interface/expect'); +exports.use(expect); + +/*! + * Should interface + */ + +var should = require('./chai/interface/should'); +exports.use(should); + +/*! + * Assert interface + */ + +var assert = require('./chai/interface/assert'); +exports.use(assert); + +}); +require.register("chai/lib/chai/assertion.js", function(exports, require, module){ +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2013 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies. + */ + +var AssertionError = require('./error') + , util = require('./utils') + , flag = util.flag; + +/*! + * Module export. + */ + +module.exports = Assertion; + + +/*! + * Assertion Constructor + * + * Creates object for chaining. + * + * @api private + */ + +function Assertion (obj, msg, stack) { + flag(this, 'ssfi', stack || arguments.callee); + flag(this, 'object', obj); + flag(this, 'message', msg); +} + +/*! + * ### Assertion.includeStack + * + * User configurable property, influences whether stack trace + * is included in Assertion error message. Default of false + * suppresses stack trace in the error message + * + * Assertion.includeStack = true; // enable stack on error + * + * @api public + */ + +Assertion.includeStack = false; + +/*! + * ### Assertion.showDiff + * + * User configurable property, influences whether or not + * the `showDiff` flag should be included in the thrown + * AssertionErrors. `false` will always be `false`; `true` + * will be true when the assertion has requested a diff + * be shown. + * + * @api public + */ + +Assertion.showDiff = true; + +Assertion.addProperty = function (name, fn) { + util.addProperty(this.prototype, name, fn); +}; + +Assertion.addMethod = function (name, fn) { + util.addMethod(this.prototype, name, fn); +}; + +Assertion.addChainableMethod = function (name, fn, chainingBehavior) { + util.addChainableMethod(this.prototype, name, fn, chainingBehavior); +}; + +Assertion.overwriteProperty = function (name, fn) { + util.overwriteProperty(this.prototype, name, fn); +}; + +Assertion.overwriteMethod = function (name, fn) { + util.overwriteMethod(this.prototype, name, fn); +}; + +/*! + * ### .assert(expression, message, negateMessage, expected, actual) + * + * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. + * + * @name assert + * @param {Philosophical} expression to be tested + * @param {String} message to display if fails + * @param {String} negatedMessage to display if negated expression fails + * @param {Mixed} expected value (remember to check for negation) + * @param {Mixed} actual (optional) will default to `this.obj` + * @api private + */ + +Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) { + var ok = util.test(this, arguments); + if (true !== showDiff) showDiff = false; + if (true !== Assertion.showDiff) showDiff = false; + + if (!ok) { + var msg = util.getMessage(this, arguments) + , actual = util.getActual(this, arguments); + throw new AssertionError({ + message: msg + , actual: actual + , expected: expected + , stackStartFunction: (Assertion.includeStack) ? this.assert : flag(this, 'ssfi') + , showDiff: showDiff + }); + } +}; + +/*! + * ### ._obj + * + * Quick reference to stored `actual` value for plugin developers. + * + * @api private + */ + +Object.defineProperty(Assertion.prototype, '_obj', + { get: function () { + return flag(this, 'object'); + } + , set: function (val) { + flag(this, 'object', val); + } +}); + +}); +require.register("chai/lib/chai/error.js", function(exports, require, module){ +/*! + * chai + * Copyright(c) 2011-2013 Jake Luer + * MIT Licensed + */ + +/*! + * Main export + */ + +module.exports = AssertionError; + +/** + * # AssertionError (constructor) + * + * Create a new assertion error based on the Javascript + * `Error` prototype. + * + * **Options** + * - message + * - actual + * - expected + * - operator + * - startStackFunction + * + * @param {Object} options + * @api public + */ + +function AssertionError (options) { + options = options || {}; + this.message = options.message; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + this.showDiff = options.showDiff; + + if (options.stackStartFunction && Error.captureStackTrace) { + var stackStartFunction = options.stackStartFunction; + Error.captureStackTrace(this, stackStartFunction); + } +} + +/*! + * Inherit from Error + */ + +AssertionError.prototype = Object.create(Error.prototype); +AssertionError.prototype.name = 'AssertionError'; +AssertionError.prototype.constructor = AssertionError; + +/** + * # toString() + * + * Override default to string method + */ + +AssertionError.prototype.toString = function() { + return this.message; +}; + +}); +require.register("chai/lib/chai/core/assertions.js", function(exports, require, module){ +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2013 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, _) { + var Assertion = chai.Assertion + , toString = Object.prototype.toString + , flag = _.flag; + + /** + * ### Language Chains + * + * The following are provide as chainable getters to + * improve the readability of your assertions. They + * do not provide an testing capability unless they + * have been overwritten by a plugin. + * + * **Chains** + * + * - to + * - be + * - been + * - is + * - that + * - and + * - have + * - with + * - at + * - of + * - same + * + * @name language chains + * @api public + */ + + [ 'to', 'be', 'been' + , 'is', 'and', 'have' + , 'with', 'that', 'at' + , 'of', 'same' ].forEach(function (chain) { + Assertion.addProperty(chain, function () { + return this; + }); + }); + + /** + * ### .not + * + * Negates any of assertions following in the chain. + * + * expect(foo).to.not.equal('bar'); + * expect(goodFn).to.not.throw(Error); + * expect({ foo: 'baz' }).to.have.property('foo') + * .and.not.equal('bar'); + * + * @name not + * @api public + */ + + Assertion.addProperty('not', function () { + flag(this, 'negate', true); + }); + + /** + * ### .deep + * + * Sets the `deep` flag, later used by the `equal` and + * `property` assertions. + * + * expect(foo).to.deep.equal({ bar: 'baz' }); + * expect({ foo: { bar: { baz: 'quux' } } }) + * .to.have.deep.property('foo.bar.baz', 'quux'); + * + * @name deep + * @api public + */ + + Assertion.addProperty('deep', function () { + flag(this, 'deep', true); + }); + + /** + * ### .a(type) + * + * The `a` and `an` assertions are aliases that can be + * used either as language chains or to assert a value's + * type. + * + * // typeof + * expect('test').to.be.a('string'); + * expect({ foo: 'bar' }).to.be.an('object'); + * expect(null).to.be.a('null'); + * expect(undefined).to.be.an('undefined'); + * + * // language chain + * expect(foo).to.be.an.instanceof(Foo); + * + * @name a + * @alias an + * @param {String} type + * @param {String} message _optional_ + * @api public + */ + + function an (type, msg) { + if (msg) flag(this, 'message', msg); + type = type.toLowerCase(); + var obj = flag(this, 'object') + , article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a '; + + this.assert( + type === _.type(obj) + , 'expected #{this} to be ' + article + type + , 'expected #{this} not to be ' + article + type + ); + } + + Assertion.addChainableMethod('an', an); + Assertion.addChainableMethod('a', an); + + /** + * ### .include(value) + * + * The `include` and `contain` assertions can be used as either property + * based language chains or as methods to assert the inclusion of an object + * in an array or a substring in a string. When used as language chains, + * they toggle the `contain` flag for the `keys` assertion. + * + * expect([1,2,3]).to.include(2); + * expect('foobar').to.contain('foo'); + * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); + * + * @name include + * @alias contain + * @param {Object|String|Number} obj + * @param {String} message _optional_ + * @api public + */ + + function includeChainingBehavior () { + flag(this, 'contains', true); + } + + function include (val, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + this.assert( + ~obj.indexOf(val) + , 'expected #{this} to include ' + _.inspect(val) + , 'expected #{this} to not include ' + _.inspect(val)); + } + + Assertion.addChainableMethod('include', include, includeChainingBehavior); + Assertion.addChainableMethod('contain', include, includeChainingBehavior); + + /** + * ### .ok + * + * Asserts that the target is truthy. + * + * expect('everthing').to.be.ok; + * expect(1).to.be.ok; + * expect(false).to.not.be.ok; + * expect(undefined).to.not.be.ok; + * expect(null).to.not.be.ok; + * + * @name ok + * @api public + */ + + Assertion.addProperty('ok', function () { + this.assert( + flag(this, 'object') + , 'expected #{this} to be truthy' + , 'expected #{this} to be falsy'); + }); + + /** + * ### .true + * + * Asserts that the target is `true`. + * + * expect(true).to.be.true; + * expect(1).to.not.be.true; + * + * @name true + * @api public + */ + + Assertion.addProperty('true', function () { + this.assert( + true === flag(this, 'object') + , 'expected #{this} to be true' + , 'expected #{this} to be false' + , this.negate ? false : true + ); + }); + + /** + * ### .false + * + * Asserts that the target is `false`. + * + * expect(false).to.be.false; + * expect(0).to.not.be.false; + * + * @name false + * @api public + */ + + Assertion.addProperty('false', function () { + this.assert( + false === flag(this, 'object') + , 'expected #{this} to be false' + , 'expected #{this} to be true' + , this.negate ? true : false + ); + }); + + /** + * ### .null + * + * Asserts that the target is `null`. + * + * expect(null).to.be.null; + * expect(undefined).not.to.be.null; + * + * @name null + * @api public + */ + + Assertion.addProperty('null', function () { + this.assert( + null === flag(this, 'object') + , 'expected #{this} to be null' + , 'expected #{this} not to be null' + ); + }); + + /** + * ### .undefined + * + * Asserts that the target is `undefined`. + * + * expect(undefined).to.be.undefined; + * expect(null).to.not.be.undefined; + * + * @name undefined + * @api public + */ + + Assertion.addProperty('undefined', function () { + this.assert( + undefined === flag(this, 'object') + , 'expected #{this} to be undefined' + , 'expected #{this} not to be undefined' + ); + }); + + /** + * ### .exist + * + * Asserts that the target is neither `null` nor `undefined`. + * + * var foo = 'hi' + * , bar = null + * , baz; + * + * expect(foo).to.exist; + * expect(bar).to.not.exist; + * expect(baz).to.not.exist; + * + * @name exist + * @api public + */ + + Assertion.addProperty('exist', function () { + this.assert( + null != flag(this, 'object') + , 'expected #{this} to exist' + , 'expected #{this} to not exist' + ); + }); + + + /** + * ### .empty + * + * Asserts that the target's length is `0`. For arrays, it checks + * the `length` property. For objects, it gets the count of + * enumerable keys. + * + * expect([]).to.be.empty; + * expect('').to.be.empty; + * expect({}).to.be.empty; + * + * @name empty + * @api public + */ + + Assertion.addProperty('empty', function () { + var obj = flag(this, 'object') + , expected = obj; + + if (Array.isArray(obj) || 'string' === typeof object) { + expected = obj.length; + } else if (typeof obj === 'object') { + expected = Object.keys(obj).length; + } + + this.assert( + !expected + , 'expected #{this} to be empty' + , 'expected #{this} not to be empty' + ); + }); + + /** + * ### .arguments + * + * Asserts that the target is an arguments object. + * + * function test () { + * expect(arguments).to.be.arguments; + * } + * + * @name arguments + * @alias Arguments + * @api public + */ + + function checkArguments () { + var obj = flag(this, 'object') + , type = Object.prototype.toString.call(obj); + this.assert( + '[object Arguments]' === type + , 'expected #{this} to be arguments but got ' + type + , 'expected #{this} to not be arguments' + ); + } + + Assertion.addProperty('arguments', checkArguments); + Assertion.addProperty('Arguments', checkArguments); + + /** + * ### .equal(value) + * + * Asserts that the target is strictly equal (`===`) to `value`. + * Alternately, if the `deep` flag is set, asserts that + * the target is deeply equal to `value`. + * + * expect('hello').to.equal('hello'); + * expect(42).to.equal(42); + * expect(1).to.not.equal(true); + * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); + * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); + * + * @name equal + * @alias equals + * @alias eq + * @alias deep.equal + * @param {Mixed} value + * @param {String} message _optional_ + * @api public + */ + + function assertEqual (val, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'deep')) { + return this.eql(val); + } else { + this.assert( + val === obj + , 'expected #{this} to equal #{exp}' + , 'expected #{this} to not equal #{exp}' + , val + , this._obj + , true + ); + } + } + + Assertion.addMethod('equal', assertEqual); + Assertion.addMethod('equals', assertEqual); + Assertion.addMethod('eq', assertEqual); + + /** + * ### .eql(value) + * + * Asserts that the target is deeply equal to `value`. + * + * expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); + * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]); + * + * @name eql + * @alias eqls + * @param {Mixed} value + * @param {String} message _optional_ + * @api public + */ + + function assertEql(obj, msg) { + if (msg) flag(this, 'message', msg); + this.assert( + _.eql(obj, flag(this, 'object')) + , 'expected #{this} to deeply equal #{exp}' + , 'expected #{this} to not deeply equal #{exp}' + , obj + , this._obj + , true + ); + } + + Assertion.addMethod('eql', assertEql); + Assertion.addMethod('eqls', assertEql); + + /** + * ### .above(value) + * + * Asserts that the target is greater than `value`. + * + * expect(10).to.be.above(5); + * + * Can also be used in conjunction with `length` to + * assert a minimum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.above(2); + * expect([ 1, 2, 3 ]).to.have.length.above(2); + * + * @name above + * @alias gt + * @alias greaterThan + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertAbove (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len > n + , 'expected #{this} to have a length above #{exp} but got #{act}' + , 'expected #{this} to not have a length above #{exp}' + , n + , len + ); + } else { + this.assert( + obj > n + , 'expected #{this} to be above ' + n + , 'expected #{this} to be at most ' + n + ); + } + } + + Assertion.addMethod('above', assertAbove); + Assertion.addMethod('gt', assertAbove); + Assertion.addMethod('greaterThan', assertAbove); + + /** + * ### .least(value) + * + * Asserts that the target is greater than or equal to `value`. + * + * expect(10).to.be.at.least(10); + * + * Can also be used in conjunction with `length` to + * assert a minimum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.of.at.least(2); + * expect([ 1, 2, 3 ]).to.have.length.of.at.least(3); + * + * @name least + * @alias gte + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertLeast (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len >= n + , 'expected #{this} to have a length at least #{exp} but got #{act}' + , 'expected #{this} to have a length below #{exp}' + , n + , len + ); + } else { + this.assert( + obj >= n + , 'expected #{this} to be at least ' + n + , 'expected #{this} to be below ' + n + ); + } + } + + Assertion.addMethod('least', assertLeast); + Assertion.addMethod('gte', assertLeast); + + /** + * ### .below(value) + * + * Asserts that the target is less than `value`. + * + * expect(5).to.be.below(10); + * + * Can also be used in conjunction with `length` to + * assert a maximum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.below(4); + * expect([ 1, 2, 3 ]).to.have.length.below(4); + * + * @name below + * @alias lt + * @alias lessThan + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertBelow (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len < n + , 'expected #{this} to have a length below #{exp} but got #{act}' + , 'expected #{this} to not have a length below #{exp}' + , n + , len + ); + } else { + this.assert( + obj < n + , 'expected #{this} to be below ' + n + , 'expected #{this} to be at least ' + n + ); + } + } + + Assertion.addMethod('below', assertBelow); + Assertion.addMethod('lt', assertBelow); + Assertion.addMethod('lessThan', assertBelow); + + /** + * ### .most(value) + * + * Asserts that the target is less than or equal to `value`. + * + * expect(5).to.be.at.most(5); + * + * Can also be used in conjunction with `length` to + * assert a maximum length. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.of.at.most(4); + * expect([ 1, 2, 3 ]).to.have.length.of.at.most(3); + * + * @name most + * @alias lte + * @param {Number} value + * @param {String} message _optional_ + * @api public + */ + + function assertMost (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len <= n + , 'expected #{this} to have a length at most #{exp} but got #{act}' + , 'expected #{this} to have a length above #{exp}' + , n + , len + ); + } else { + this.assert( + obj <= n + , 'expected #{this} to be at most ' + n + , 'expected #{this} to be above ' + n + ); + } + } + + Assertion.addMethod('most', assertMost); + Assertion.addMethod('lte', assertMost); + + /** + * ### .within(start, finish) + * + * Asserts that the target is within a range. + * + * expect(7).to.be.within(5,10); + * + * Can also be used in conjunction with `length` to + * assert a length range. The benefit being a + * more informative error message than if the length + * was supplied directly. + * + * expect('foo').to.have.length.within(2,4); + * expect([ 1, 2, 3 ]).to.have.length.within(2,4); + * + * @name within + * @param {Number} start lowerbound inclusive + * @param {Number} finish upperbound inclusive + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('within', function (start, finish, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , range = start + '..' + finish; + if (flag(this, 'doLength')) { + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + this.assert( + len >= start && len <= finish + , 'expected #{this} to have a length within ' + range + , 'expected #{this} to not have a length within ' + range + ); + } else { + this.assert( + obj >= start && obj <= finish + , 'expected #{this} to be within ' + range + , 'expected #{this} to not be within ' + range + ); + } + }); + + /** + * ### .instanceof(constructor) + * + * Asserts that the target is an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , Chai = new Tea('chai'); + * + * expect(Chai).to.be.an.instanceof(Tea); + * expect([ 1, 2, 3 ]).to.be.instanceof(Array); + * + * @name instanceof + * @param {Constructor} constructor + * @param {String} message _optional_ + * @alias instanceOf + * @api public + */ + + function assertInstanceOf (constructor, msg) { + if (msg) flag(this, 'message', msg); + var name = _.getName(constructor); + this.assert( + flag(this, 'object') instanceof constructor + , 'expected #{this} to be an instance of ' + name + , 'expected #{this} to not be an instance of ' + name + ); + }; + + Assertion.addMethod('instanceof', assertInstanceOf); + Assertion.addMethod('instanceOf', assertInstanceOf); + + /** + * ### .property(name, [value]) + * + * Asserts that the target has a property `name`, optionally asserting that + * the value of that property is strictly equal to `value`. + * If the `deep` flag is set, you can use dot- and bracket-notation for deep + * references into objects and arrays. + * + * // simple referencing + * var obj = { foo: 'bar' }; + * expect(obj).to.have.property('foo'); + * expect(obj).to.have.property('foo', 'bar'); + * + * // deep referencing + * var deepObj = { + * green: { tea: 'matcha' } + * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] + * }; + + * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); + * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); + * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); + * + * You can also use an array as the starting point of a `deep.property` + * assertion, or traverse nested arrays. + * + * var arr = [ + * [ 'chai', 'matcha', 'konacha' ] + * , [ { tea: 'chai' } + * , { tea: 'matcha' } + * , { tea: 'konacha' } ] + * ]; + * + * expect(arr).to.have.deep.property('[0][1]', 'matcha'); + * expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); + * + * Furthermore, `property` changes the subject of the assertion + * to be the value of that property from the original object. This + * permits for further chainable assertions on that property. + * + * expect(obj).to.have.property('foo') + * .that.is.a('string'); + * expect(deepObj).to.have.property('green') + * .that.is.an('object') + * .that.deep.equals({ tea: 'matcha' }); + * expect(deepObj).to.have.property('teas') + * .that.is.an('array') + * .with.deep.property('[2]') + * .that.deep.equals({ tea: 'konacha' }); + * + * @name property + * @alias deep.property + * @param {String} name + * @param {Mixed} value (optional) + * @param {String} message _optional_ + * @returns value of property for chaining + * @api public + */ + + Assertion.addMethod('property', function (name, val, msg) { + if (msg) flag(this, 'message', msg); + + var descriptor = flag(this, 'deep') ? 'deep property ' : 'property ' + , negate = flag(this, 'negate') + , obj = flag(this, 'object') + , value = flag(this, 'deep') + ? _.getPathValue(name, obj) + : obj[name]; + + if (negate && undefined !== val) { + if (undefined === value) { + msg = (msg != null) ? msg + ': ' : ''; + throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name)); + } + } else { + this.assert( + undefined !== value + , 'expected #{this} to have a ' + descriptor + _.inspect(name) + , 'expected #{this} to not have ' + descriptor + _.inspect(name)); + } + + if (undefined !== val) { + this.assert( + val === value + , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' + , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}' + , val + , value + ); + } + + flag(this, 'object', value); + }); + + + /** + * ### .ownProperty(name) + * + * Asserts that the target has an own property `name`. + * + * expect('test').to.have.ownProperty('length'); + * + * @name ownProperty + * @alias haveOwnProperty + * @param {String} name + * @param {String} message _optional_ + * @api public + */ + + function assertOwnProperty (name, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + obj.hasOwnProperty(name) + , 'expected #{this} to have own property ' + _.inspect(name) + , 'expected #{this} to not have own property ' + _.inspect(name) + ); + } + + Assertion.addMethod('ownProperty', assertOwnProperty); + Assertion.addMethod('haveOwnProperty', assertOwnProperty); + + /** + * ### .length(value) + * + * Asserts that the target's `length` property has + * the expected value. + * + * expect([ 1, 2, 3]).to.have.length(3); + * expect('foobar').to.have.length(6); + * + * Can also be used as a chain precursor to a value + * comparison for the length property. + * + * expect('foo').to.have.length.above(2); + * expect([ 1, 2, 3 ]).to.have.length.above(2); + * expect('foo').to.have.length.below(4); + * expect([ 1, 2, 3 ]).to.have.length.below(4); + * expect('foo').to.have.length.within(2,4); + * expect([ 1, 2, 3 ]).to.have.length.within(2,4); + * + * @name length + * @alias lengthOf + * @param {Number} length + * @param {String} message _optional_ + * @api public + */ + + function assertLengthChain () { + flag(this, 'doLength', true); + } + + function assertLength (n, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).to.have.property('length'); + var len = obj.length; + + this.assert( + len == n + , 'expected #{this} to have a length of #{exp} but got #{act}' + , 'expected #{this} to not have a length of #{act}' + , n + , len + ); + } + + Assertion.addChainableMethod('length', assertLength, assertLengthChain); + Assertion.addMethod('lengthOf', assertLength, assertLengthChain); + + /** + * ### .match(regexp) + * + * Asserts that the target matches a regular expression. + * + * expect('foobar').to.match(/^foo/); + * + * @name match + * @param {RegExp} RegularExpression + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('match', function (re, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + re.exec(obj) + , 'expected #{this} to match ' + re + , 'expected #{this} not to match ' + re + ); + }); + + /** + * ### .string(string) + * + * Asserts that the string target contains another string. + * + * expect('foobar').to.have.string('bar'); + * + * @name string + * @param {String} string + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('string', function (str, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).is.a('string'); + + this.assert( + ~obj.indexOf(str) + , 'expected #{this} to contain ' + _.inspect(str) + , 'expected #{this} to not contain ' + _.inspect(str) + ); + }); + + + /** + * ### .keys(key1, [key2], [...]) + * + * Asserts that the target has exactly the given keys, or + * asserts the inclusion of some keys when using the + * `include` or `contain` modifiers. + * + * expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']); + * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.keys('foo', 'bar'); + * + * @name keys + * @alias key + * @param {String...|Array} keys + * @api public + */ + + function assertKeys (keys) { + var obj = flag(this, 'object') + , str + , ok = true; + + keys = keys instanceof Array + ? keys + : Array.prototype.slice.call(arguments); + + if (!keys.length) throw new Error('keys required'); + + var actual = Object.keys(obj) + , len = keys.length; + + // Inclusion + ok = keys.every(function(key){ + return ~actual.indexOf(key); + }); + + // Strict + if (!flag(this, 'negate') && !flag(this, 'contains')) { + ok = ok && keys.length == actual.length; + } + + // Key string + if (len > 1) { + keys = keys.map(function(key){ + return _.inspect(key); + }); + var last = keys.pop(); + str = keys.join(', ') + ', and ' + last; + } else { + str = _.inspect(keys[0]); + } + + // Form + str = (len > 1 ? 'keys ' : 'key ') + str; + + // Have / include + str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; + + // Assertion + this.assert( + ok + , 'expected #{this} to ' + str + , 'expected #{this} to not ' + str + ); + } + + Assertion.addMethod('keys', assertKeys); + Assertion.addMethod('key', assertKeys); + + /** + * ### .throw(constructor) + * + * Asserts that the function target will throw a specific error, or specific type of error + * (as determined using `instanceof`), optionally with a RegExp or string inclusion test + * for the error's message. + * + * var err = new ReferenceError('This is a bad function.'); + * var fn = function () { throw err; } + * expect(fn).to.throw(ReferenceError); + * expect(fn).to.throw(Error); + * expect(fn).to.throw(/bad function/); + * expect(fn).to.not.throw('good function'); + * expect(fn).to.throw(ReferenceError, /bad function/); + * expect(fn).to.throw(err); + * expect(fn).to.not.throw(new RangeError('Out of range.')); + * + * Please note that when a throw expectation is negated, it will check each + * parameter independently, starting with error constructor type. The appropriate way + * to check for the existence of a type of error but for a message that does not match + * is to use `and`. + * + * expect(fn).to.throw(ReferenceError) + * .and.not.throw(/good function/); + * + * @name throw + * @alias throws + * @alias Throw + * @param {ErrorConstructor} constructor + * @param {String|RegExp} expected error message + * @param {String} message _optional_ + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @api public + */ + + function assertThrows (constructor, errMsg, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + new Assertion(obj, msg).is.a('function'); + + var thrown = false + , desiredError = null + , name = null + , thrownError = null; + + if (arguments.length === 0) { + errMsg = null; + constructor = null; + } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) { + errMsg = constructor; + constructor = null; + } else if (constructor && constructor instanceof Error) { + desiredError = constructor; + constructor = null; + errMsg = null; + } else if (typeof constructor === 'function') { + name = (new constructor()).name; + } else { + constructor = null; + } + + try { + obj(); + } catch (err) { + // first, check desired error + if (desiredError) { + this.assert( + err === desiredError + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp}' + , desiredError + , err + ); + + return this; + } + // next, check constructor + if (constructor) { + this.assert( + err instanceof constructor + , 'expected #{this} to throw #{exp} but #{act} was thrown' + , 'expected #{this} to not throw #{exp} but #{act} was thrown' + , name + , err + ); + + if (!errMsg) return this; + } + // next, check message + var message = 'object' === _.type(err) && "message" in err + ? err.message + : '' + err; + + if ((message != null) && errMsg && errMsg instanceof RegExp) { + this.assert( + errMsg.exec(message) + , 'expected #{this} to throw error matching #{exp} but got #{act}' + , 'expected #{this} to throw error not matching #{exp}' + , errMsg + , message + ); + + return this; + } else if ((message != null) && errMsg && 'string' === typeof errMsg) { + this.assert( + ~message.indexOf(errMsg) + , 'expected #{this} to throw error including #{exp} but got #{act}' + , 'expected #{this} to throw error not including #{act}' + , errMsg + , message + ); + + return this; + } else { + thrown = true; + thrownError = err; + } + } + + var actuallyGot = '' + , expectedThrown = name !== null + ? name + : desiredError + ? '#{exp}' //_.inspect(desiredError) + : 'an error'; + + if (thrown) { + actuallyGot = ' but #{act} was thrown' + } + + this.assert( + thrown === true + , 'expected #{this} to throw ' + expectedThrown + actuallyGot + , 'expected #{this} to not throw ' + expectedThrown + actuallyGot + , desiredError + , thrownError + ); + }; + + Assertion.addMethod('throw', assertThrows); + Assertion.addMethod('throws', assertThrows); + Assertion.addMethod('Throw', assertThrows); + + /** + * ### .respondTo(method) + * + * Asserts that the object or class target will respond to a method. + * + * Klass.prototype.bar = function(){}; + * expect(Klass).to.respondTo('bar'); + * expect(obj).to.respondTo('bar'); + * + * To check if a constructor will respond to a static function, + * set the `itself` flag. + * + * Klass.baz = function(){}; + * expect(Klass).itself.to.respondTo('baz'); + * + * @name respondTo + * @param {String} method + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('respondTo', function (method, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object') + , itself = flag(this, 'itself') + , context = ('function' === _.type(obj) && !itself) + ? obj.prototype[method] + : obj[method]; + + this.assert( + 'function' === typeof context + , 'expected #{this} to respond to ' + _.inspect(method) + , 'expected #{this} to not respond to ' + _.inspect(method) + ); + }); + + /** + * ### .itself + * + * Sets the `itself` flag, later used by the `respondTo` assertion. + * + * function Foo() {} + * Foo.bar = function() {} + * Foo.prototype.baz = function() {} + * + * expect(Foo).itself.to.respondTo('bar'); + * expect(Foo).itself.not.to.respondTo('baz'); + * + * @name itself + * @api public + */ + + Assertion.addProperty('itself', function () { + flag(this, 'itself', true); + }); + + /** + * ### .satisfy(method) + * + * Asserts that the target passes a given truth test. + * + * expect(1).to.satisfy(function(num) { return num > 0; }); + * + * @name satisfy + * @param {Function} matcher + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('satisfy', function (matcher, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + matcher(obj) + , 'expected #{this} to satisfy ' + _.objDisplay(matcher) + , 'expected #{this} to not satisfy' + _.objDisplay(matcher) + , this.negate ? false : true + , matcher(obj) + ); + }); + + /** + * ### .closeTo(expected, delta) + * + * Asserts that the target is equal `expected`, to within a +/- `delta` range. + * + * expect(1.5).to.be.closeTo(1, 0.5); + * + * @name closeTo + * @param {Number} expected + * @param {Number} delta + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('closeTo', function (expected, delta, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + this.assert( + Math.abs(obj - expected) <= delta + , 'expected #{this} to be close to ' + expected + ' +/- ' + delta + , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta + ); + }); + + function isSubsetOf(subset, superset) { + return subset.every(function(elem) { + return superset.indexOf(elem) !== -1; + }) + } + + /** + * ### .members + * + * Asserts that the target is a superset of `set`, + * or that the target and `set` have the same members. + * + * expect([1, 2, 3]).to.include.members([3, 2]); + * expect([1, 2, 3]).to.not.include.members([3, 2, 8]); + * + * expect([4, 2]).to.have.members([2, 4]); + * expect([5, 2]).to.not.have.members([5, 2, 1]); + * + * @name members + * @param {Array} set + * @param {String} message _optional_ + * @api public + */ + + Assertion.addMethod('members', function (subset, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + + new Assertion(obj).to.be.an('array'); + new Assertion(subset).to.be.an('array'); + + if (flag(this, 'contains')) { + return this.assert( + isSubsetOf(subset, obj) + , 'expected #{this} to be a superset of #{act}' + , 'expected #{this} to not be a superset of #{act}' + , obj + , subset + ); + } + + this.assert( + isSubsetOf(obj, subset) && isSubsetOf(subset, obj) + , 'expected #{this} to have the same members as #{act}' + , 'expected #{this} to not have the same members as #{act}' + , obj + , subset + ); + }); +}; + +}); +require.register("chai/lib/chai/interface/assert.js", function(exports, require, module){ +/*! + * chai + * Copyright(c) 2011-2013 Jake Luer + * MIT Licensed + */ + + +module.exports = function (chai, util) { + + /*! + * Chai dependencies. + */ + + var Assertion = chai.Assertion + , flag = util.flag; + + /*! + * Module export. + */ + + /** + * ### assert(expression, message) + * + * Write your own test expressions. + * + * assert('foo' !== 'bar', 'foo is not bar'); + * assert(Array.isArray([]), 'empty arrays are arrays'); + * + * @param {Mixed} expression to test for truthiness + * @param {String} message to display on error + * @name assert + * @api public + */ + + var assert = chai.assert = function (express, errmsg) { + var test = new Assertion(null); + test.assert( + express + , errmsg + , '[ negation message unavailable ]' + ); + }; + + /** + * ### .fail(actual, expected, [message], [operator]) + * + * Throw a failure. Node.js `assert` module-compatible. + * + * @name fail + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @param {String} operator + * @api public + */ + + assert.fail = function (actual, expected, message, operator) { + throw new chai.AssertionError({ + actual: actual + , expected: expected + , message: message + , operator: operator + , stackStartFunction: assert.fail + }); + }; + + /** + * ### .ok(object, [message]) + * + * Asserts that `object` is truthy. + * + * assert.ok('everything', 'everything is ok'); + * assert.ok(false, 'this will fail'); + * + * @name ok + * @param {Mixed} object to test + * @param {String} message + * @api public + */ + + assert.ok = function (val, msg) { + new Assertion(val, msg).is.ok; + }; + + /** + * ### .equal(actual, expected, [message]) + * + * Asserts non-strict equality (`==`) of `actual` and `expected`. + * + * assert.equal(3, '3', '== coerces values to strings'); + * + * @name equal + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.equal = function (act, exp, msg) { + var test = new Assertion(act, msg); + + test.assert( + exp == flag(test, 'object') + , 'expected #{this} to equal #{exp}' + , 'expected #{this} to not equal #{act}' + , exp + , act + ); + }; + + /** + * ### .notEqual(actual, expected, [message]) + * + * Asserts non-strict inequality (`!=`) of `actual` and `expected`. + * + * assert.notEqual(3, 4, 'these numbers are not equal'); + * + * @name notEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notEqual = function (act, exp, msg) { + var test = new Assertion(act, msg); + + test.assert( + exp != flag(test, 'object') + , 'expected #{this} to not equal #{exp}' + , 'expected #{this} to equal #{act}' + , exp + , act + ); + }; + + /** + * ### .strictEqual(actual, expected, [message]) + * + * Asserts strict equality (`===`) of `actual` and `expected`. + * + * assert.strictEqual(true, true, 'these booleans are strictly equal'); + * + * @name strictEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.strictEqual = function (act, exp, msg) { + new Assertion(act, msg).to.equal(exp); + }; + + /** + * ### .notStrictEqual(actual, expected, [message]) + * + * Asserts strict inequality (`!==`) of `actual` and `expected`. + * + * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); + * + * @name notStrictEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notStrictEqual = function (act, exp, msg) { + new Assertion(act, msg).to.not.equal(exp); + }; + + /** + * ### .deepEqual(actual, expected, [message]) + * + * Asserts that `actual` is deeply equal to `expected`. + * + * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); + * + * @name deepEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.deepEqual = function (act, exp, msg) { + new Assertion(act, msg).to.eql(exp); + }; + + /** + * ### .notDeepEqual(actual, expected, [message]) + * + * Assert that `actual` is not deeply equal to `expected`. + * + * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); + * + * @name notDeepEqual + * @param {Mixed} actual + * @param {Mixed} expected + * @param {String} message + * @api public + */ + + assert.notDeepEqual = function (act, exp, msg) { + new Assertion(act, msg).to.not.eql(exp); + }; + + /** + * ### .isTrue(value, [message]) + * + * Asserts that `value` is true. + * + * var teaServed = true; + * assert.isTrue(teaServed, 'the tea has been served'); + * + * @name isTrue + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isTrue = function (val, msg) { + new Assertion(val, msg).is['true']; + }; + + /** + * ### .isFalse(value, [message]) + * + * Asserts that `value` is false. + * + * var teaServed = false; + * assert.isFalse(teaServed, 'no tea yet? hmm...'); + * + * @name isFalse + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isFalse = function (val, msg) { + new Assertion(val, msg).is['false']; + }; + + /** + * ### .isNull(value, [message]) + * + * Asserts that `value` is null. + * + * assert.isNull(err, 'there was no error'); + * + * @name isNull + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNull = function (val, msg) { + new Assertion(val, msg).to.equal(null); + }; + + /** + * ### .isNotNull(value, [message]) + * + * Asserts that `value` is not null. + * + * var tea = 'tasty chai'; + * assert.isNotNull(tea, 'great, time for tea!'); + * + * @name isNotNull + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotNull = function (val, msg) { + new Assertion(val, msg).to.not.equal(null); + }; + + /** + * ### .isUndefined(value, [message]) + * + * Asserts that `value` is `undefined`. + * + * var tea; + * assert.isUndefined(tea, 'no tea defined'); + * + * @name isUndefined + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isUndefined = function (val, msg) { + new Assertion(val, msg).to.equal(undefined); + }; + + /** + * ### .isDefined(value, [message]) + * + * Asserts that `value` is not `undefined`. + * + * var tea = 'cup of chai'; + * assert.isDefined(tea, 'tea has been defined'); + * + * @name isUndefined + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isDefined = function (val, msg) { + new Assertion(val, msg).to.not.equal(undefined); + }; + + /** + * ### .isFunction(value, [message]) + * + * Asserts that `value` is a function. + * + * function serveTea() { return 'cup of tea'; }; + * assert.isFunction(serveTea, 'great, we can have tea now'); + * + * @name isFunction + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isFunction = function (val, msg) { + new Assertion(val, msg).to.be.a('function'); + }; + + /** + * ### .isNotFunction(value, [message]) + * + * Asserts that `value` is _not_ a function. + * + * var serveTea = [ 'heat', 'pour', 'sip' ]; + * assert.isNotFunction(serveTea, 'great, we have listed the steps'); + * + * @name isNotFunction + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotFunction = function (val, msg) { + new Assertion(val, msg).to.not.be.a('function'); + }; + + /** + * ### .isObject(value, [message]) + * + * Asserts that `value` is an object (as revealed by + * `Object.prototype.toString`). + * + * var selection = { name: 'Chai', serve: 'with spices' }; + * assert.isObject(selection, 'tea selection is an object'); + * + * @name isObject + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isObject = function (val, msg) { + new Assertion(val, msg).to.be.a('object'); + }; + + /** + * ### .isNotObject(value, [message]) + * + * Asserts that `value` is _not_ an object. + * + * var selection = 'chai' + * assert.isObject(selection, 'tea selection is not an object'); + * assert.isObject(null, 'null is not an object'); + * + * @name isNotObject + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotObject = function (val, msg) { + new Assertion(val, msg).to.not.be.a('object'); + }; + + /** + * ### .isArray(value, [message]) + * + * Asserts that `value` is an array. + * + * var menu = [ 'green', 'chai', 'oolong' ]; + * assert.isArray(menu, 'what kind of tea do we want?'); + * + * @name isArray + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isArray = function (val, msg) { + new Assertion(val, msg).to.be.an('array'); + }; + + /** + * ### .isNotArray(value, [message]) + * + * Asserts that `value` is _not_ an array. + * + * var menu = 'green|chai|oolong'; + * assert.isNotArray(menu, 'what kind of tea do we want?'); + * + * @name isNotArray + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotArray = function (val, msg) { + new Assertion(val, msg).to.not.be.an('array'); + }; + + /** + * ### .isString(value, [message]) + * + * Asserts that `value` is a string. + * + * var teaOrder = 'chai'; + * assert.isString(teaOrder, 'order placed'); + * + * @name isString + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isString = function (val, msg) { + new Assertion(val, msg).to.be.a('string'); + }; + + /** + * ### .isNotString(value, [message]) + * + * Asserts that `value` is _not_ a string. + * + * var teaOrder = 4; + * assert.isNotString(teaOrder, 'order placed'); + * + * @name isNotString + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotString = function (val, msg) { + new Assertion(val, msg).to.not.be.a('string'); + }; + + /** + * ### .isNumber(value, [message]) + * + * Asserts that `value` is a number. + * + * var cups = 2; + * assert.isNumber(cups, 'how many cups'); + * + * @name isNumber + * @param {Number} value + * @param {String} message + * @api public + */ + + assert.isNumber = function (val, msg) { + new Assertion(val, msg).to.be.a('number'); + }; + + /** + * ### .isNotNumber(value, [message]) + * + * Asserts that `value` is _not_ a number. + * + * var cups = '2 cups please'; + * assert.isNotNumber(cups, 'how many cups'); + * + * @name isNotNumber + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotNumber = function (val, msg) { + new Assertion(val, msg).to.not.be.a('number'); + }; + + /** + * ### .isBoolean(value, [message]) + * + * Asserts that `value` is a boolean. + * + * var teaReady = true + * , teaServed = false; + * + * assert.isBoolean(teaReady, 'is the tea ready'); + * assert.isBoolean(teaServed, 'has tea been served'); + * + * @name isBoolean + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isBoolean = function (val, msg) { + new Assertion(val, msg).to.be.a('boolean'); + }; + + /** + * ### .isNotBoolean(value, [message]) + * + * Asserts that `value` is _not_ a boolean. + * + * var teaReady = 'yep' + * , teaServed = 'nope'; + * + * assert.isNotBoolean(teaReady, 'is the tea ready'); + * assert.isNotBoolean(teaServed, 'has tea been served'); + * + * @name isNotBoolean + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.isNotBoolean = function (val, msg) { + new Assertion(val, msg).to.not.be.a('boolean'); + }; + + /** + * ### .typeOf(value, name, [message]) + * + * Asserts that `value`'s type is `name`, as determined by + * `Object.prototype.toString`. + * + * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); + * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); + * assert.typeOf('tea', 'string', 'we have a string'); + * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); + * assert.typeOf(null, 'null', 'we have a null'); + * assert.typeOf(undefined, 'undefined', 'we have an undefined'); + * + * @name typeOf + * @param {Mixed} value + * @param {String} name + * @param {String} message + * @api public + */ + + assert.typeOf = function (val, type, msg) { + new Assertion(val, msg).to.be.a(type); + }; + + /** + * ### .notTypeOf(value, name, [message]) + * + * Asserts that `value`'s type is _not_ `name`, as determined by + * `Object.prototype.toString`. + * + * assert.notTypeOf('tea', 'number', 'strings are not numbers'); + * + * @name notTypeOf + * @param {Mixed} value + * @param {String} typeof name + * @param {String} message + * @api public + */ + + assert.notTypeOf = function (val, type, msg) { + new Assertion(val, msg).to.not.be.a(type); + }; + + /** + * ### .instanceOf(object, constructor, [message]) + * + * Asserts that `value` is an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , chai = new Tea('chai'); + * + * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); + * + * @name instanceOf + * @param {Object} object + * @param {Constructor} constructor + * @param {String} message + * @api public + */ + + assert.instanceOf = function (val, type, msg) { + new Assertion(val, msg).to.be.instanceOf(type); + }; + + /** + * ### .notInstanceOf(object, constructor, [message]) + * + * Asserts `value` is not an instance of `constructor`. + * + * var Tea = function (name) { this.name = name; } + * , chai = new String('chai'); + * + * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); + * + * @name notInstanceOf + * @param {Object} object + * @param {Constructor} constructor + * @param {String} message + * @api public + */ + + assert.notInstanceOf = function (val, type, msg) { + new Assertion(val, msg).to.not.be.instanceOf(type); + }; + + /** + * ### .include(haystack, needle, [message]) + * + * Asserts that `haystack` includes `needle`. Works + * for strings and arrays. + * + * assert.include('foobar', 'bar', 'foobar contains string "bar"'); + * assert.include([ 1, 2, 3 ], 3, 'array contains value'); + * + * @name include + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @api public + */ + + assert.include = function (exp, inc, msg) { + var obj = new Assertion(exp, msg); + + if (Array.isArray(exp)) { + obj.to.include(inc); + } else if ('string' === typeof exp) { + obj.to.contain.string(inc); + } else { + throw new chai.AssertionError({ + message: 'expected an array or string' + , stackStartFunction: assert.include + }); + } + }; + + /** + * ### .notInclude(haystack, needle, [message]) + * + * Asserts that `haystack` does not include `needle`. Works + * for strings and arrays. + *i + * assert.notInclude('foobar', 'baz', 'string not include substring'); + * assert.notInclude([ 1, 2, 3 ], 4, 'array not include contain value'); + * + * @name notInclude + * @param {Array|String} haystack + * @param {Mixed} needle + * @param {String} message + * @api public + */ + + assert.notInclude = function (exp, inc, msg) { + var obj = new Assertion(exp, msg); + + if (Array.isArray(exp)) { + obj.to.not.include(inc); + } else if ('string' === typeof exp) { + obj.to.not.contain.string(inc); + } else { + throw new chai.AssertionError({ + message: 'expected an array or string' + , stackStartFunction: assert.include + }); + } + }; + + /** + * ### .match(value, regexp, [message]) + * + * Asserts that `value` matches the regular expression `regexp`. + * + * assert.match('foobar', /^foo/, 'regexp matches'); + * + * @name match + * @param {Mixed} value + * @param {RegExp} regexp + * @param {String} message + * @api public + */ + + assert.match = function (exp, re, msg) { + new Assertion(exp, msg).to.match(re); + }; + + /** + * ### .notMatch(value, regexp, [message]) + * + * Asserts that `value` does not match the regular expression `regexp`. + * + * assert.notMatch('foobar', /^foo/, 'regexp does not match'); + * + * @name notMatch + * @param {Mixed} value + * @param {RegExp} regexp + * @param {String} message + * @api public + */ + + assert.notMatch = function (exp, re, msg) { + new Assertion(exp, msg).to.not.match(re); + }; + + /** + * ### .property(object, property, [message]) + * + * Asserts that `object` has a property named by `property`. + * + * assert.property({ tea: { green: 'matcha' }}, 'tea'); + * + * @name property + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.property = function (obj, prop, msg) { + new Assertion(obj, msg).to.have.property(prop); + }; + + /** + * ### .notProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a property named by `property`. + * + * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); + * + * @name notProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.notProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.not.have.property(prop); + }; + + /** + * ### .deepProperty(object, property, [message]) + * + * Asserts that `object` has a property named by `property`, which can be a + * string using dot- and bracket-notation for deep reference. + * + * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); + * + * @name deepProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.deepProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.have.deep.property(prop); + }; + + /** + * ### .notDeepProperty(object, property, [message]) + * + * Asserts that `object` does _not_ have a property named by `property`, which + * can be a string using dot- and bracket-notation for deep reference. + * + * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); + * + * @name notDeepProperty + * @param {Object} object + * @param {String} property + * @param {String} message + * @api public + */ + + assert.notDeepProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.not.have.deep.property(prop); + }; + + /** + * ### .propertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with value given + * by `value`. + * + * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); + * + * @name propertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.propertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.have.property(prop, val); + }; + + /** + * ### .propertyNotVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property`, but with a value + * different from that given by `value`. + * + * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); + * + * @name propertyNotVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.propertyNotVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.not.have.property(prop, val); + }; + + /** + * ### .deepPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with value given + * by `value`. `property` can use dot- and bracket-notation for deep + * reference. + * + * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); + * + * @name deepPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.deepPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.have.deep.property(prop, val); + }; + + /** + * ### .deepPropertyNotVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property`, but with a value + * different from that given by `value`. `property` can use dot- and + * bracket-notation for deep reference. + * + * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); + * + * @name deepPropertyNotVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @api public + */ + + assert.deepPropertyNotVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.not.have.deep.property(prop, val); + }; + + /** + * ### .lengthOf(object, length, [message]) + * + * Asserts that `object` has a `length` property with the expected value. + * + * assert.lengthOf([1,2,3], 3, 'array has length of 3'); + * assert.lengthOf('foobar', 5, 'string has length of 6'); + * + * @name lengthOf + * @param {Mixed} object + * @param {Number} length + * @param {String} message + * @api public + */ + + assert.lengthOf = function (exp, len, msg) { + new Assertion(exp, msg).to.have.length(len); + }; + + /** + * ### .throws(function, [constructor/string/regexp], [string/regexp], [message]) + * + * Asserts that `function` will throw an error that is an instance of + * `constructor`, or alternately that it will throw an error with message + * matching `regexp`. + * + * assert.throw(fn, 'function throws a reference error'); + * assert.throw(fn, /function throws a reference error/); + * assert.throw(fn, ReferenceError); + * assert.throw(fn, ReferenceError, 'function throws a reference error'); + * assert.throw(fn, ReferenceError, /function throws a reference error/); + * + * @name throws + * @alias throw + * @alias Throw + * @param {Function} function + * @param {ErrorConstructor} constructor + * @param {RegExp} regexp + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @api public + */ + + assert.Throw = function (fn, errt, errs, msg) { + if ('string' === typeof errt || errt instanceof RegExp) { + errs = errt; + errt = null; + } + + new Assertion(fn, msg).to.Throw(errt, errs); + }; + + /** + * ### .doesNotThrow(function, [constructor/regexp], [message]) + * + * Asserts that `function` will _not_ throw an error that is an instance of + * `constructor`, or alternately that it will not throw an error with message + * matching `regexp`. + * + * assert.doesNotThrow(fn, Error, 'function does not throw'); + * + * @name doesNotThrow + * @param {Function} function + * @param {ErrorConstructor} constructor + * @param {RegExp} regexp + * @param {String} message + * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types + * @api public + */ + + assert.doesNotThrow = function (fn, type, msg) { + if ('string' === typeof type) { + msg = type; + type = null; + } + + new Assertion(fn, msg).to.not.Throw(type); + }; + + /** + * ### .operator(val1, operator, val2, [message]) + * + * Compares two values using `operator`. + * + * assert.operator(1, '<', 2, 'everything is ok'); + * assert.operator(1, '>', 2, 'this will fail'); + * + * @name operator + * @param {Mixed} val1 + * @param {String} operator + * @param {Mixed} val2 + * @param {String} message + * @api public + */ + + assert.operator = function (val, operator, val2, msg) { + if (!~['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) { + throw new Error('Invalid operator "' + operator + '"'); + } + var test = new Assertion(eval(val + operator + val2), msg); + test.assert( + true === flag(test, 'object') + , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) + , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) ); + }; + + /** + * ### .closeTo(actual, expected, delta, [message]) + * + * Asserts that the target is equal `expected`, to within a +/- `delta` range. + * + * assert.closeTo(1.5, 1, 0.5, 'numbers are close'); + * + * @name closeTo + * @param {Number} actual + * @param {Number} expected + * @param {Number} delta + * @param {String} message + * @api public + */ + + assert.closeTo = function (act, exp, delta, msg) { + new Assertion(act, msg).to.be.closeTo(exp, delta); + }; + + /** + * ### .sameMembers(set1, set2, [message]) + * + * Asserts that `set1` and `set2` have the same members. + * Order is not taken into account. + * + * assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members'); + * + * @name sameMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @api public + */ + + assert.sameMembers = function (set1, set2, msg) { + new Assertion(set1, msg).to.have.same.members(set2); + } + + /** + * ### .includeMembers(superset, subset, [message]) + * + * Asserts that `subset` is included in `superset`. + * Order is not taken into account. + * + * assert.includeMembers([ 1, 2, 3 ], [ 2, 1 ], 'include members'); + * + * @name includeMembers + * @param {Array} superset + * @param {Array} subset + * @param {String} message + * @api public + */ + + assert.includeMembers = function (superset, subset, msg) { + new Assertion(superset, msg).to.include.members(subset); + } + + /*! + * Undocumented / untested + */ + + assert.ifError = function (val, msg) { + new Assertion(val, msg).to.not.be.ok; + }; + + /*! + * Aliases. + */ + + (function alias(name, as){ + assert[as] = assert[name]; + return alias; + }) + ('Throw', 'throw') + ('Throw', 'throws'); +}; + +}); +require.register("chai/lib/chai/interface/expect.js", function(exports, require, module){ +/*! + * chai + * Copyright(c) 2011-2013 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + chai.expect = function (val, message) { + return new chai.Assertion(val, message); + }; +}; + + +}); +require.register("chai/lib/chai/interface/should.js", function(exports, require, module){ +/*! + * chai + * Copyright(c) 2011-2013 Jake Luer + * MIT Licensed + */ + +module.exports = function (chai, util) { + var Assertion = chai.Assertion; + + function loadShould () { + // modify Object.prototype to have `should` + Object.defineProperty(Object.prototype, 'should', + { + set: function (value) { + // See https://github.com/chaijs/chai/issues/86: this makes + // `whatever.should = someValue` actually set `someValue`, which is + // especially useful for `global.should = require('chai').should()`. + // + // Note that we have to use [[DefineProperty]] instead of [[Put]] + // since otherwise we would trigger this very setter! + Object.defineProperty(this, 'should', { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } + , get: function(){ + if (this instanceof String || this instanceof Number) { + return new Assertion(this.constructor(this)); + } else if (this instanceof Boolean) { + return new Assertion(this == true); + } + return new Assertion(this); + } + , configurable: true + }); + + var should = {}; + + should.equal = function (val1, val2, msg) { + new Assertion(val1, msg).to.equal(val2); + }; + + should.Throw = function (fn, errt, errs, msg) { + new Assertion(fn, msg).to.Throw(errt, errs); + }; + + should.exist = function (val, msg) { + new Assertion(val, msg).to.exist; + } + + // negation + should.not = {} + + should.not.equal = function (val1, val2, msg) { + new Assertion(val1, msg).to.not.equal(val2); + }; + + should.not.Throw = function (fn, errt, errs, msg) { + new Assertion(fn, msg).to.not.Throw(errt, errs); + }; + + should.not.exist = function (val, msg) { + new Assertion(val, msg).to.not.exist; + } + + should['throw'] = should['Throw']; + should.not['throw'] = should.not['Throw']; + + return should; + }; + + chai.should = loadShould; + chai.Should = loadShould; +}; + +}); +require.register("chai/lib/chai/utils/addChainableMethod.js", function(exports, require, module){ +/*! + * Chai - addChainingMethod utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependencies + */ + +var transferFlags = require('./transferFlags'); + +/*! + * Module variables + */ + +// Check whether `__proto__` is supported +var hasProtoSupport = '__proto__' in Object; + +// Without `__proto__` support, this module will need to add properties to a function. +// However, some Function.prototype methods cannot be overwritten, +// and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69). +var excludeNames = /^(?:length|name|arguments|caller)$/; + +// Cache `Function` properties +var call = Function.prototype.call, + apply = Function.prototype.apply; + +/** + * ### addChainableMethod (ctx, name, method, chainingBehavior) + * + * Adds a method to an object, such that the method can also be chained. + * + * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.equal(str); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); + * + * The result can then be used as both a method assertion, executing both `method` and + * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. + * + * expect(fooStr).to.be.foo('bar'); + * expect(fooStr).to.be.foo.equal('foo'); + * + * @param {Object} ctx object to which the method is added + * @param {String} name of method to add + * @param {Function} method function to be used for `name`, when called + * @param {Function} chainingBehavior function to be called every time the property is accessed + * @name addChainableMethod + * @api public + */ + +module.exports = function (ctx, name, method, chainingBehavior) { + if (typeof chainingBehavior !== 'function') + chainingBehavior = function () { }; + + Object.defineProperty(ctx, name, + { get: function () { + chainingBehavior.call(this); + + var assert = function () { + var result = method.apply(this, arguments); + return result === undefined ? this : result; + }; + + // Use `__proto__` if available + if (hasProtoSupport) { + // Inherit all properties from the object by replacing the `Function` prototype + var prototype = assert.__proto__ = Object.create(this); + // Restore the `call` and `apply` methods from `Function` + prototype.call = call; + prototype.apply = apply; + } + // Otherwise, redefine all properties (slow!) + else { + var asserterNames = Object.getOwnPropertyNames(ctx); + asserterNames.forEach(function (asserterName) { + if (!excludeNames.test(asserterName)) { + var pd = Object.getOwnPropertyDescriptor(ctx, asserterName); + Object.defineProperty(assert, asserterName, pd); + } + }); + } + + transferFlags(this, assert); + return assert; + } + , configurable: true + }); +}; + +}); +require.register("chai/lib/chai/utils/addMethod.js", function(exports, require, module){ +/*! + * Chai - addMethod utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * ### .addMethod (ctx, name, method) + * + * Adds a method to the prototype of an object. + * + * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.equal(str); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addMethod('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(fooStr).to.be.foo('bar'); + * + * @param {Object} ctx object to which the method is added + * @param {String} name of method to add + * @param {Function} method function to be used for name + * @name addMethod + * @api public + */ + +module.exports = function (ctx, name, method) { + ctx[name] = function () { + var result = method.apply(this, arguments); + return result === undefined ? this : result; + }; +}; + +}); +require.register("chai/lib/chai/utils/addProperty.js", function(exports, require, module){ +/*! + * Chai - addProperty utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * ### addProperty (ctx, name, getter) + * + * Adds a property to the prototype of an object. + * + * utils.addProperty(chai.Assertion.prototype, 'foo', function () { + * var obj = utils.flag(this, 'object'); + * new chai.Assertion(obj).to.be.instanceof(Foo); + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.addProperty('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.be.foo; + * + * @param {Object} ctx object to which the property is added + * @param {String} name of property to add + * @param {Function} getter function to be used for name + * @name addProperty + * @api public + */ + +module.exports = function (ctx, name, getter) { + Object.defineProperty(ctx, name, + { get: function () { + var result = getter.call(this); + return result === undefined ? this : result; + } + , configurable: true + }); +}; + +}); +require.register("chai/lib/chai/utils/eql.js", function(exports, require, module){ +// This is (almost) directly from Node.js assert +// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/assert.js + +module.exports = _deepEqual; + +var getEnumerableProperties = require('./getEnumerableProperties'); + +// for the browser +var Buffer; +try { + Buffer = require('buffer').Buffer; +} catch (ex) { + Buffer = { + isBuffer: function () { return false; } + }; +} + +function _deepEqual(actual, expected, memos) { + + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (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, memos); + } +} + +function isUndefinedOrNull(value) { + return value === null || value === undefined; +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b, memos) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + + // check if we have already compared a and b + var i; + if (memos) { + for(i = 0; i < memos.length; i++) { + if ((memos[i][0] === a && memos[i][1] === b) || + (memos[i][0] === b && memos[i][1] === a)) + return true; + } + } else { + memos = []; + } + + //~~~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 _deepEqual(a, b, memos); + } + try { + var ka = getEnumerableProperties(a), + kb = getEnumerableProperties(b), + key; + } 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; + } + + // remember objects we have compared to guard against circular references + memos.push([ a, b ]); + + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key], memos)) return false; + } + + return true; +} + +}); +require.register("chai/lib/chai/utils/flag.js", function(exports, require, module){ +/*! + * Chai - flag utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * ### flag(object ,key, [value]) + * + * Get or set a flag value on an object. If a + * value is provided it will be set, else it will + * return the currently set value or `undefined` if + * the value is not set. + * + * utils.flag(this, 'foo', 'bar'); // setter + * utils.flag(this, 'foo'); // getter, returns `bar` + * + * @param {Object} object (constructed Assertion + * @param {String} key + * @param {Mixed} value (optional) + * @name flag + * @api private + */ + +module.exports = function (obj, key, value) { + var flags = obj.__flags || (obj.__flags = Object.create(null)); + if (arguments.length === 3) { + flags[key] = value; + } else { + return flags[key]; + } +}; + +}); +require.register("chai/lib/chai/utils/getActual.js", function(exports, require, module){ +/*! + * Chai - getActual utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * # getActual(object, [actual]) + * + * Returns the `actual` value for an Assertion + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + */ + +module.exports = function (obj, args) { + var actual = args[4]; + return 'undefined' !== typeof actual ? actual : obj._obj; +}; + +}); +require.register("chai/lib/chai/utils/getEnumerableProperties.js", function(exports, require, module){ +/*! + * Chai - getEnumerableProperties utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * ### .getEnumerableProperties(object) + * + * This allows the retrieval of enumerable property names of an object, + * inherited or not. + * + * @param {Object} object + * @returns {Array} + * @name getEnumerableProperties + * @api public + */ + +module.exports = function getEnumerableProperties(object) { + var result = []; + for (var name in object) { + result.push(name); + } + return result; +}; + +}); +require.register("chai/lib/chai/utils/getMessage.js", function(exports, require, module){ +/*! + * Chai - message composition utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var flag = require('./flag') + , getActual = require('./getActual') + , inspect = require('./inspect') + , objDisplay = require('./objDisplay'); + +/** + * ### .getMessage(object, message, negateMessage) + * + * Construct the error message based on flags + * and template tags. Template tags will return + * a stringified inspection of the object referenced. + * + * Messsage template tags: + * - `#{this}` current asserted object + * - `#{act}` actual value + * - `#{exp}` expected value + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + * @name getMessage + * @api public + */ + +module.exports = function (obj, args) { + var negate = flag(obj, 'negate') + , val = flag(obj, 'object') + , expected = args[3] + , actual = getActual(obj, args) + , msg = negate ? args[2] : args[1] + , flagMsg = flag(obj, 'message'); + + msg = msg || ''; + msg = msg + .replace(/#{this}/g, objDisplay(val)) + .replace(/#{act}/g, objDisplay(actual)) + .replace(/#{exp}/g, objDisplay(expected)); + + return flagMsg ? flagMsg + ': ' + msg : msg; +}; + +}); +require.register("chai/lib/chai/utils/getName.js", function(exports, require, module){ +/*! + * Chai - getName utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * # getName(func) + * + * Gets the name of a function, in a cross-browser way. + * + * @param {Function} a function (usually a constructor) + */ + +module.exports = function (func) { + if (func.name) return func.name; + + var match = /^\s?function ([^(]*)\(/.exec(func); + return match && match[1] ? match[1] : ""; +}; + +}); +require.register("chai/lib/chai/utils/getPathValue.js", function(exports, require, module){ +/*! + * Chai - getPathValue utility + * Copyright(c) 2012-2013 Jake Luer + * @see https://github.com/logicalparadox/filtr + * MIT Licensed + */ + +/** + * ### .getPathValue(path, object) + * + * This allows the retrieval of values in an + * object given a string path. + * + * var obj = { + * prop1: { + * arr: ['a', 'b', 'c'] + * , str: 'Hello' + * } + * , prop2: { + * arr: [ { nested: 'Universe' } ] + * , str: 'Hello again!' + * } + * } + * + * The following would be the results. + * + * getPathValue('prop1.str', obj); // Hello + * getPathValue('prop1.att[2]', obj); // b + * getPathValue('prop2.arr[0].nested', obj); // Universe + * + * @param {String} path + * @param {Object} object + * @returns {Object} value or `undefined` + * @name getPathValue + * @api public + */ + +var getPathValue = module.exports = function (path, obj) { + var parsed = parsePath(path); + return _getPathValue(parsed, obj); +}; + +/*! + * ## parsePath(path) + * + * Helper function used to parse string object + * paths. Use in conjunction with `_getPathValue`. + * + * var parsed = parsePath('myobject.property.subprop'); + * + * ### Paths: + * + * * Can be as near infinitely deep and nested + * * Arrays are also valid using the formal `myobject.document[3].property`. + * + * @param {String} path + * @returns {Object} parsed + * @api private + */ + +function parsePath (path) { + var str = path.replace(/\[/g, '.[') + , parts = str.match(/(\\\.|[^.]+?)+/g); + return parts.map(function (value) { + var re = /\[(\d+)\]$/ + , mArr = re.exec(value) + if (mArr) return { i: parseFloat(mArr[1]) }; + else return { p: value }; + }); +}; + +/*! + * ## _getPathValue(parsed, obj) + * + * Helper companion function for `.parsePath` that returns + * the value located at the parsed address. + * + * var value = getPathValue(parsed, obj); + * + * @param {Object} parsed definition from `parsePath`. + * @param {Object} object to search against + * @returns {Object|Undefined} value + * @api private + */ + +function _getPathValue (parsed, obj) { + var tmp = obj + , res; + for (var i = 0, l = parsed.length; i < l; i++) { + var part = parsed[i]; + if (tmp) { + if ('undefined' !== typeof part.p) + tmp = tmp[part.p]; + else if ('undefined' !== typeof part.i) + tmp = tmp[part.i]; + if (i == (l - 1)) res = tmp; + } else { + res = undefined; + } + } + return res; +}; + +}); +require.register("chai/lib/chai/utils/getProperties.js", function(exports, require, module){ +/*! + * Chai - getProperties utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * ### .getProperties(object) + * + * This allows the retrieval of property names of an object, enumerable or not, + * inherited or not. + * + * @param {Object} object + * @returns {Array} + * @name getProperties + * @api public + */ + +module.exports = function getProperties(object) { + var result = Object.getOwnPropertyNames(subject); + + function addProperty(property) { + if (result.indexOf(property) === -1) { + result.push(property); + } + } + + var proto = Object.getPrototypeOf(subject); + while (proto !== null) { + Object.getOwnPropertyNames(proto).forEach(addProperty); + proto = Object.getPrototypeOf(proto); + } + + return result; +}; + +}); +require.register("chai/lib/chai/utils/index.js", function(exports, require, module){ +/*! + * chai + * Copyright(c) 2011 Jake Luer + * MIT Licensed + */ + +/*! + * Main exports + */ + +var exports = module.exports = {}; + +/*! + * test utility + */ + +exports.test = require('./test'); + +/*! + * type utility + */ + +exports.type = require('./type'); + +/*! + * message utility + */ + +exports.getMessage = require('./getMessage'); + +/*! + * actual utility + */ + +exports.getActual = require('./getActual'); + +/*! + * Inspect util + */ + +exports.inspect = require('./inspect'); + +/*! + * Object Display util + */ + +exports.objDisplay = require('./objDisplay'); + +/*! + * Flag utility + */ + +exports.flag = require('./flag'); + +/*! + * Flag transferring utility + */ + +exports.transferFlags = require('./transferFlags'); + +/*! + * Deep equal utility + */ + +exports.eql = require('./eql'); + +/*! + * Deep path value + */ + +exports.getPathValue = require('./getPathValue'); + +/*! + * Function name + */ + +exports.getName = require('./getName'); + +/*! + * add Property + */ + +exports.addProperty = require('./addProperty'); + +/*! + * add Method + */ + +exports.addMethod = require('./addMethod'); + +/*! + * overwrite Property + */ + +exports.overwriteProperty = require('./overwriteProperty'); + +/*! + * overwrite Method + */ + +exports.overwriteMethod = require('./overwriteMethod'); + +/*! + * Add a chainable method + */ + +exports.addChainableMethod = require('./addChainableMethod'); + + +}); +require.register("chai/lib/chai/utils/inspect.js", function(exports, require, module){ +// This is (almost) directly from Node.js utils +// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js + +var getName = require('./getName'); +var getProperties = require('./getProperties'); +var getEnumerableProperties = require('./getEnumerableProperties'); + +module.exports = inspect; + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Boolean} showHidden Flag that shows hidden (not enumerable) + * properties of objects. + * @param {Number} depth Depth in which to descend in object. Default is 2. + * @param {Boolean} colors Flag to turn on ANSI escape codes to color the + * output. Default is false (no coloring). + */ +function inspect(obj, showHidden, depth, colors) { + var ctx = { + showHidden: showHidden, + seen: [], + stylize: function (str) { return str; } + }; + return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); +} + +// https://gist.github.com/1044128/ +var getOuterHTML = function(element) { + if ('outerHTML' in element) return element.outerHTML; + var ns = "http://www.w3.org/1999/xhtml"; + var container = document.createElementNS(ns, '_'); + var elemProto = (window.HTMLElement || window.Element).prototype; + var xmlSerializer = new XMLSerializer(); + var html; + if (document.xmlVersion) { + return xmlSerializer.serializeToString(element); + } else { + container.appendChild(element.cloneNode(false)); + html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); + container.innerHTML = ''; + return html; + } +}; + +// Returns true if object is a DOM element. +var isDOMElement = function (object) { + if (typeof HTMLElement === 'object') { + return object instanceof HTMLElement; + } else { + return object && + typeof object === 'object' && + object.nodeType === 1 && + typeof object.nodeName === 'string'; + } +}; + +function formatValue(ctx, 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.inspect !== exports.inspect && + // 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 + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // If it's DOM elem, get outer HTML. + if (isDOMElement(value)) { + return getOuterHTML(value); + } + + // Look up the keys of the object. + var visibleKeys = getEnumerableProperties(value); + var keys = ctx.showHidden ? getProperties(value) : visibleKeys; + + // Some type of object without properties can be shortcutted. + // In IE, errors have a single `stack` property, or if they are vanilla `Error`, + // a `stack` plus `description` property; ignore those for consistency. + if (keys.length === 0 || (isError(value) && ( + (keys.length === 1 && keys[0] === 'stack') || + (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') + ))) { + if (typeof value === 'function') { + var name = getName(value); + var nameSuffix = name ? ': ' + name : ''; + return ctx.stylize('[Function' + nameSuffix + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (typeof value === 'function') { + var name = getName(value); + var nameSuffix = name ? ': ' + name : ''; + base = ' [Function' + nameSuffix + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + return formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + switch (typeof value) { + case 'undefined': + return ctx.stylize('undefined', 'undefined'); + + case 'string': + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + + case 'number': + return ctx.stylize('' + value, 'number'); + + case 'boolean': + return ctx.stylize('' + value, 'boolean'); + } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return ctx.stylize('null', 'null'); + } +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (Object.prototype.hasOwnProperty.call(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str; + if (value.__lookupGetter__) { + if (value.__lookupGetter__(key)) { + if (value.__lookupSetter__(key)) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (value.__lookupSetter__(key)) { + str = ctx.stylize('[Setter]', 'special'); + } + } + } + if (visibleKeys.indexOf(key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(value[key]) < 0) { + if (recurseTimes === null) { + str = formatValue(ctx, value[key], null); + } else { + str = formatValue(ctx, value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (typeof name === 'undefined') { + if (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 = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + +function isArray(ar) { + return Array.isArray(ar) || + (typeof ar === 'object' && objectToString(ar) === '[object Array]'); +} + +function isRegExp(re) { + return typeof re === 'object' && objectToString(re) === '[object RegExp]'; +} + +function isDate(d) { + return typeof d === 'object' && objectToString(d) === '[object Date]'; +} + +function isError(e) { + return typeof e === 'object' && objectToString(e) === '[object Error]'; +} + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +}); +require.register("chai/lib/chai/utils/objDisplay.js", function(exports, require, module){ +/*! + * Chai - flag utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var inspect = require('./inspect'); + +/** + * ### .objDisplay (object) + * + * Determines if an object or an array matches + * criteria to be inspected in-line for error + * messages or should be truncated. + * + * @param {Mixed} javascript object to inspect + * @name objDisplay + * @api public + */ + +module.exports = function (obj) { + var str = inspect(obj) + , type = Object.prototype.toString.call(obj); + + if (str.length >= 40) { + if (type === '[object Function]') { + return !obj.name || obj.name === '' + ? '[Function]' + : '[Function: ' + obj.name + ']'; + } else if (type === '[object Array]') { + return '[ Array(' + obj.length + ') ]'; + } else if (type === '[object Object]') { + var keys = Object.keys(obj) + , kstr = keys.length > 2 + ? keys.splice(0, 2).join(', ') + ', ...' + : keys.join(', '); + return '{ Object (' + kstr + ') }'; + } else { + return str; + } + } else { + return str; + } +}; + +}); +require.register("chai/lib/chai/utils/overwriteMethod.js", function(exports, require, module){ +/*! + * Chai - overwriteMethod utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * ### overwriteMethod (ctx, name, fn) + * + * Overwites an already existing method and provides + * access to previous function. Must return function + * to be used for name. + * + * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { + * return function (str) { + * var obj = utils.flag(this, 'object'); + * if (obj instanceof Foo) { + * new chai.Assertion(obj.value).to.equal(str); + * } else { + * _super.apply(this, arguments); + * } + * } + * }); + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteMethod('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.equal('bar'); + * + * @param {Object} ctx object whose method is to be overwritten + * @param {String} name of method to overwrite + * @param {Function} method function that returns a function to be used for name + * @name overwriteMethod + * @api public + */ + +module.exports = function (ctx, name, method) { + var _method = ctx[name] + , _super = function () { return this; }; + + if (_method && 'function' === typeof _method) + _super = _method; + + ctx[name] = function () { + var result = method(_super).apply(this, arguments); + return result === undefined ? this : result; + } +}; + +}); +require.register("chai/lib/chai/utils/overwriteProperty.js", function(exports, require, module){ +/*! + * Chai - overwriteProperty utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * ### overwriteProperty (ctx, name, fn) + * + * Overwites an already existing property getter and provides + * access to previous value. Must return function to use as getter. + * + * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { + * return function () { + * var obj = utils.flag(this, 'object'); + * if (obj instanceof Foo) { + * new chai.Assertion(obj.name).to.equal('bar'); + * } else { + * _super.call(this); + * } + * } + * }); + * + * + * Can also be accessed directly from `chai.Assertion`. + * + * chai.Assertion.overwriteProperty('foo', fn); + * + * Then can be used as any other assertion. + * + * expect(myFoo).to.be.ok; + * + * @param {Object} ctx object whose property is to be overwritten + * @param {String} name of property to overwrite + * @param {Function} getter function that returns a getter function to be used for name + * @name overwriteProperty + * @api public + */ + +module.exports = function (ctx, name, getter) { + var _get = Object.getOwnPropertyDescriptor(ctx, name) + , _super = function () {}; + + if (_get && 'function' === typeof _get.get) + _super = _get.get + + Object.defineProperty(ctx, name, + { get: function () { + var result = getter(_super).call(this); + return result === undefined ? this : result; + } + , configurable: true + }); +}; + +}); +require.register("chai/lib/chai/utils/test.js", function(exports, require, module){ +/*! + * Chai - test utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/*! + * Module dependancies + */ + +var flag = require('./flag'); + +/** + * # test(object, expression) + * + * Test and object for expression. + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + */ + +module.exports = function (obj, args) { + var negate = flag(obj, 'negate') + , expr = args[0]; + return negate ? !expr : expr; +}; + +}); +require.register("chai/lib/chai/utils/transferFlags.js", function(exports, require, module){ +/*! + * Chai - transferFlags utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/** + * ### transferFlags(assertion, object, includeAll = true) + * + * Transfer all the flags for `assertion` to `object`. If + * `includeAll` is set to `false`, then the base Chai + * assertion flags (namely `object`, `ssfi`, and `message`) + * will not be transferred. + * + * + * var newAssertion = new Assertion(); + * utils.transferFlags(assertion, newAssertion); + * + * var anotherAsseriton = new Assertion(myObj); + * utils.transferFlags(assertion, anotherAssertion, false); + * + * @param {Assertion} assertion the assertion to transfer the flags from + * @param {Object} object the object to transfer the flags too; usually a new assertion + * @param {Boolean} includeAll + * @name getAllFlags + * @api private + */ + +module.exports = function (assertion, object, includeAll) { + var flags = assertion.__flags || (assertion.__flags = Object.create(null)); + + if (!object.__flags) { + object.__flags = Object.create(null); + } + + includeAll = arguments.length === 3 ? includeAll : true; + + for (var flag in flags) { + if (includeAll || + (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { + object.__flags[flag] = flags[flag]; + } + } +}; + +}); +require.register("chai/lib/chai/utils/type.js", function(exports, require, module){ +/*! + * Chai - type utility + * Copyright(c) 2012-2013 Jake Luer + * MIT Licensed + */ + +/*! + * Detectable javascript natives + */ + +var natives = { + '[object Arguments]': 'arguments' + , '[object Array]': 'array' + , '[object Date]': 'date' + , '[object Function]': 'function' + , '[object Number]': 'number' + , '[object RegExp]': 'regexp' + , '[object String]': 'string' +}; + +/** + * ### type(object) + * + * Better implementation of `typeof` detection that can + * be used cross-browser. Handles the inconsistencies of + * Array, `null`, and `undefined` detection. + * + * utils.type({}) // 'object' + * utils.type(null) // `null' + * utils.type(undefined) // `undefined` + * utils.type([]) // `array` + * + * @param {Mixed} object to detect type of + * @name type + * @api private + */ + +module.exports = function (obj) { + var str = Object.prototype.toString.call(obj); + if (natives[str]) return natives[str]; + if (obj === null) return 'null'; + if (obj === undefined) return 'undefined'; + if (obj === Object(obj)) return 'object'; + return typeof obj; +}; + +}); +require.alias("chai/index.js", "chai/index.js"); + +if (typeof exports == "object") { + module.exports = require("chai"); +} else if (typeof define == "function" && define.amd) { + define(function(){ return require("chai"); }); +} else { + this["chai"] = require("chai"); +}})(); diff --git a/tests/libs/mocha-1.9.0.css b/tests/libs/mocha-1.9.0.css new file mode 100644 index 00000000..883d44bb --- /dev/null +++ b/tests/libs/mocha-1.9.0.css @@ -0,0 +1,246 @@ +@charset "utf-8"; + +body { + font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 60px 50px; +} + +#mocha ul, #mocha li { + margin: 0; + padding: 0; +} + +#mocha ul { + list-style: none; +} + +#mocha h1, #mocha h2 { + margin: 0; +} + +#mocha h1 { + margin-top: 15px; + font-size: 1em; + font-weight: 200; +} + +#mocha h1 a { + text-decoration: none; + color: inherit; +} + +#mocha h1 a:hover { + text-decoration: underline; +} + +#mocha .suite .suite h1 { + margin-top: 0; + font-size: .8em; +} + +.hidden { + display: none; +} + +#mocha h2 { + font-size: 12px; + font-weight: normal; + cursor: pointer; +} + +#mocha .suite { + margin-left: 15px; +} + +#mocha .test { + margin-left: 15px; + overflow: hidden; +} + +#mocha .test.pending:hover h2::after { + content: '(pending)'; + font-family: arial; +} + +#mocha .test.pass.medium .duration { + background: #C09853; +} + +#mocha .test.pass.slow .duration { + background: #B94A48; +} + +#mocha .test.pass::before { + content: '✓'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #00d6b2; +} + +#mocha .test.pass .duration { + font-size: 9px; + margin-left: 5px; + padding: 2px 5px; + color: white; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; + border-radius: 5px; +} + +#mocha .test.pass.fast .duration { + display: none; +} + +#mocha .test.pending { + color: #0b97c4; +} + +#mocha .test.pending::before { + content: '◦'; + color: #0b97c4; +} + +#mocha .test.fail { + color: #c00; +} + +#mocha .test.fail pre { + color: black; +} + +#mocha .test.fail::before { + content: '✖'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #c00; +} + +#mocha .test pre.error { + color: #c00; + max-height: 300px; + overflow: auto; +} + +#mocha .test pre { + display: block; + float: left; + clear: left; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + border-bottom-color: #ddd; + -webkit-border-radius: 3px; + -webkit-box-shadow: 0 1px 3px #eee; + -moz-border-radius: 3px; + -moz-box-shadow: 0 1px 3px #eee; +} + +#mocha .test h2 { + position: relative; +} + +#mocha .test a.replay { + position: absolute; + top: 3px; + right: 0; + text-decoration: none; + vertical-align: middle; + display: block; + width: 15px; + height: 15px; + line-height: 15px; + text-align: center; + background: #eee; + font-size: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + -webkit-transition: opacity 200ms; + -moz-transition: opacity 200ms; + transition: opacity 200ms; + opacity: 0.3; + color: #888; +} + +#mocha .test:hover a.replay { + opacity: 1; +} + +#mocha-report.pass .test.fail { + display: none; +} + +#mocha-report.fail .test.pass { + display: none; +} + +#mocha-error { + color: #c00; + font-size: 1.5 em; + font-weight: 100; + letter-spacing: 1px; +} + +#mocha-stats { + position: fixed; + top: 15px; + right: 10px; + font-size: 12px; + margin: 0; + color: #888; +} + +#mocha-stats .progress { + float: right; + padding-top: 0; +} + +#mocha-stats em { + color: black; +} + +#mocha-stats a { + text-decoration: none; + color: inherit; +} + +#mocha-stats a:hover { + border-bottom: 1px solid #eee; +} + +#mocha-stats li { + display: inline-block; + margin: 0 5px; + list-style: none; + padding-top: 11px; +} + +#mocha-stats canvas { + width: 40px; + height: 40px; +} + +code .comment { color: #ddd } +code .init { color: #2F6FAD } +code .string { color: #5890AD } +code .keyword { color: #8A6343 } +code .number { color: #2F6FAD } + +@media screen and (max-device-width: 480px) { + body { + padding: 60px 0px; + } + + #stats { + position: absolute; + } +} diff --git a/tests/libs/mocha-1.9.0.js b/tests/libs/mocha-1.9.0.js new file mode 100644 index 00000000..aed04d99 --- /dev/null +++ b/tests/libs/mocha-1.9.0.js @@ -0,0 +1,5338 @@ +;(function(){ + +// CommonJS require() + +function require(p){ + var path = require.resolve(p) + , mod = require.modules[path]; + if (!mod) throw new Error('failed to require "' + p + '"'); + if (!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] && reg + || require.modules[index] && index + || orig; + }; + +require.register = function (path, fn){ + require.modules[path] = fn; + }; + +require.relative = function (parent) { + return function(p){ + if ('.' != p.charAt(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]; + if ('..' == seg) path.pop(); + else if ('.' != seg) path.push(seg); + } + + return require(path.join('/')); + }; + }; + + +require.register("browser/debug.js", function(module, exports, require){ + +module.exports = function(type){ + return function(){ + } +}; + +}); // module: browser/debug.js + +require.register("browser/diff.js", function(module, exports, require){ +/* See license.txt for terms of usage */ + +/* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ +var JsDiff = (function() { + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + } + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, "&"); + n = n.replace(//g, ">"); + n = n.replace(/"/g, """); + + return n; + } + + + var fbDiff = function(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + }; + fbDiff.prototype = { + diff: function(oldString, newString) { + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString == oldString) { + return [{ value: newString }]; + } + if (!newString) { + return [{ value: oldString, removed: true }]; + } + if (!oldString) { + return [{ value: newString, added: true }]; + } + + newString = this.tokenize(newString); + oldString = this.tokenize(oldString); + + var newLen = newString.length, oldLen = oldString.length; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0 + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { + return bestPath[0].components; + } + + for (var editLength = 1; editLength <= maxEditLength; editLength++) { + for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { + var basePath; + var addPath = bestPath[diagonalPath-1], + removePath = bestPath[diagonalPath+1]; + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath-1] = undefined; + } + + var canAdd = addPath && addPath.newPos+1 < newLen; + var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; + if (!canAdd && !canRemove) { + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { + basePath = clonePath(removePath); + this.pushComponent(basePath.components, oldString[oldPos], undefined, true); + } else { + basePath = clonePath(addPath); + basePath.newPos++; + this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); + } + + var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); + + if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { + return basePath.components; + } else { + bestPath[diagonalPath] = basePath; + } + } + } + }, + + pushComponent: function(components, value, added, removed) { + var last = components[components.length-1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length-1] = + {value: this.join(last.value, value), added: added, removed: removed }; + } else { + components.push({value: value, added: added, removed: removed }); + } + }, + extractCommon: function(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath; + while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { + newPos++; + oldPos++; + + this.pushComponent(basePath.components, newString[newPos], undefined, undefined); + } + basePath.newPos = newPos; + return oldPos; + }, + + equals: function(left, right) { + var reWhitespace = /\S/; + if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { + return true; + } else { + return left == right; + } + }, + join: function(left, right) { + return left + right; + }, + tokenize: function(value) { + return value; + } + }; + + var CharDiff = new fbDiff(); + + var WordDiff = new fbDiff(true); + WordDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\s+|\b)/)); + }; + + var CssDiff = new fbDiff(true); + CssDiff.tokenize = function(value) { + return removeEmpty(value.split(/([{}:;,]|\s+)/)); + }; + + var LineDiff = new fbDiff(); + LineDiff.tokenize = function(value) { + return value.split(/^/m); + }; + + return { + diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, + diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, + diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, + + diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, + + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + var ret = []; + + ret.push("Index: " + fileName); + ret.push("==================================================================="); + ret.push("--- " + fileName + (typeof oldHeader === "undefined" ? "" : "\t" + oldHeader)); + ret.push("+++ " + fileName + (typeof newHeader === "undefined" ? "" : "\t" + newHeader)); + + var diff = LineDiff.diff(oldStr, newStr); + if (!diff[diff.length-1].value) { + diff.pop(); // Remove trailing newline add + } + diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return lines.map(function(entry) { return ' ' + entry; }); + } + function eofNL(curRange, i, current) { + var last = diff[diff.length-2], + isLast = i === diff.length-2, + isLastOfType = i === diff.length-3 && (current.added === !last.added || current.removed === !last.removed); + + // Figure out if this is the last line for the given file and missing NL + if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { + curRange.push('\\ No newline at end of file'); + } + } + + var oldRangeStart = 0, newRangeStart = 0, curRange = [], + oldLine = 1, newLine = 1; + for (var i = 0; i < diff.length; i++) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, "").split("\n"); + current.lines = lines; + + if (current.added || current.removed) { + if (!oldRangeStart) { + var prev = diff[i-1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = contextLines(prev.lines.slice(-4)); + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; })); + eofNL(curRange, i, current); + + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= 8 && i < diff.length-2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, 4); + ret.push( + "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize) + + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize) + + " @@"); + ret.push.apply(ret, curRange); + ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); + if (lines.length <= 4) { + eofNL(ret, i, current); + } + + oldRangeStart = 0; newRangeStart = 0; curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + } + + return ret.join('\n') + '\n'; + }, + + convertChangesToXML: function(changes){ + var ret = []; + for ( var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(""); + } else if (change.removed) { + ret.push(""); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(""); + } else if (change.removed) { + ret.push(""); + } + } + return ret.join(""); + } + }; +})(); + +if (typeof module !== "undefined") { + module.exports = JsDiff; +} + +}); // module: browser/diff.js + +require.register("browser/events.js", function(module, exports, require){ + +/** + * Module exports. + */ + +exports.EventEmitter = EventEmitter; + +/** + * Check if `obj` is an array. + */ + +function isArray(obj) { + return '[object Array]' == {}.toString.call(obj); +} + +/** + * Event emitter constructor. + * + * @api public + */ + +function EventEmitter(){}; + +/** + * Adds a listener. + * + * @api public + */ + +EventEmitter.prototype.on = function (name, fn) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = fn; + } else if (isArray(this.$events[name])) { + this.$events[name].push(fn); + } else { + this.$events[name] = [this.$events[name], fn]; + } + + return this; +}; + +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +/** + * Adds a volatile listener. + * + * @api public + */ + +EventEmitter.prototype.once = function (name, fn) { + var self = this; + + function on () { + self.removeListener(name, on); + fn.apply(this, arguments); + }; + + on.listener = fn; + this.on(name, on); + + return this; +}; + +/** + * Removes a listener. + * + * @api public + */ + +EventEmitter.prototype.removeListener = function (name, fn) { + if (this.$events && this.$events[name]) { + var list = this.$events[name]; + + if (isArray(list)) { + var pos = -1; + + for (var i = 0, l = list.length; i < l; i++) { + if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { + pos = i; + break; + } + } + + if (pos < 0) { + return this; + } + + list.splice(pos, 1); + + if (!list.length) { + delete this.$events[name]; + } + } else if (list === fn || (list.listener && list.listener === fn)) { + delete this.$events[name]; + } + } + + return this; +}; + +/** + * Removes all listeners for an event. + * + * @api public + */ + +EventEmitter.prototype.removeAllListeners = function (name) { + if (name === undefined) { + this.$events = {}; + return this; + } + + if (this.$events && this.$events[name]) { + this.$events[name] = null; + } + + return this; +}; + +/** + * Gets all listeners for a certain event. + * + * @api public + */ + +EventEmitter.prototype.listeners = function (name) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = []; + } + + if (!isArray(this.$events[name])) { + this.$events[name] = [this.$events[name]]; + } + + return this.$events[name]; +}; + +/** + * Emits an event. + * + * @api public + */ + +EventEmitter.prototype.emit = function (name) { + if (!this.$events) { + return false; + } + + var handler = this.$events[name]; + + if (!handler) { + return false; + } + + var args = [].slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (isArray(handler)) { + var listeners = handler.slice(); + + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + } else { + return false; + } + + return true; +}; +}); // module: browser/events.js + +require.register("browser/fs.js", function(module, exports, require){ + +}); // module: browser/fs.js + +require.register("browser/path.js", function(module, exports, require){ + +}); // module: browser/path.js + +require.register("browser/progress.js", function(module, exports, require){ + +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ + +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.size = function(n){ + this._size = n; + return this; +}; + +/** + * Set text to `str`. + * + * @param {String} str + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.text = function(str){ + this._text = str; + return this; +}; + +/** + * Set font size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.fontSize = function(n){ + this._fontSize = n; + return this; +}; + +/** + * Set font `family`. + * + * @param {String} family + * @return {Progress} for chaining + */ + +Progress.prototype.font = function(family){ + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + */ + +Progress.prototype.update = function(n){ + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} for chaining + */ + +Progress.prototype.draw = function(ctx){ + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + + return this; +}; + +}); // module: browser/progress.js + +require.register("browser/tty.js", function(module, exports, require){ + +exports.isatty = function(){ + return true; +}; + +exports.getWindowSize = function(){ + return [window.innerHeight, window.innerWidth]; +}; +}); // module: browser/tty.js + +require.register("context.js", function(module, exports, require){ + +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @api private + */ + +function Context(){} + +/** + * Set or get the context `Runnable` to `runnable`. + * + * @param {Runnable} runnable + * @return {Context} + * @api private + */ + +Context.prototype.runnable = function(runnable){ + if (0 == arguments.length) return this._runnable; + this.test = this._runnable = runnable; + return this; +}; + +/** + * Set test timeout `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.timeout = function(ms){ + this.runnable().timeout(ms); + return this; +}; + +/** + * Set test slowness threshold `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.slow = function(ms){ + this.runnable().slow(ms); + return this; +}; + +/** + * Inspect the context void of `._runnable`. + * + * @return {String} + * @api private + */ + +Context.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_runnable' == key) return; + if ('test' == key) return; + return val; + }, 2); +}; + +}); // module: context.js + +require.register("hook.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +function F(){}; +F.prototype = Runnable.prototype; +Hook.prototype = new F; +Hook.prototype.constructor = Hook; + + +/** + * Get or set the test `err`. + * + * @param {Error} err + * @return {Error} + * @api public + */ + +Hook.prototype.error = function(err){ + if (0 == arguments.length) { + var err = this._error; + this._error = null; + return err; + } + + this._error = err; +}; + +}); // module: hook.js + +require.register("interfaces/bdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * BDD-style interface: + * + * describe('Array', function(){ + * describe('#indexOf()', function(){ + * it('should return -1 when not present', function(){ + * + * }); + * + * it('should return the index when present', function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = context.context = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Pending describe. + */ + + context.xdescribe = + context.xcontext = + context.describe.skip = function(title, fn){ + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + }; + + /** + * Exclusive suite. + */ + + context.describe.only = function(title, fn){ + var suite = context.describe(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = context.specify = function(title, fn){ + var suite = suites[0]; + if (suite.pending) var fn = null; + var test = new Test(title, fn); + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.it.only = function(title, fn){ + var test = context.it(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.xit = + context.xspecify = + context.it.skip = function(title){ + context.it(title); + }; + }); +}; + +}); // module: interfaces/bdd.js + +require.register("interfaces/exports.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function(){ + * + * }, + * + * 'should return the correct index when the value is present': function(){ + * + * } + * } + * }; + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('require', visit); + + function visit(obj) { + var suite; + for (var key in obj) { + if ('function' == typeof obj[key]) { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + suites[0].addTest(new Test(key, fn)); + } + } else { + var suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key]); + suites.shift(); + } + } + } +}; + +}); // module: interfaces/exports.js + +require.register("interfaces/index.js", function(module, exports, require){ + +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); + +}); // module: interfaces/index.js + +require.register("interfaces/qunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function(){ + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function(){ + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function(){ + * ok('foo'.length == 3); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; + +}); // module: interfaces/qunit.js + +require.register("interfaces/tdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * suite('Array', function(){ + * suite('#indexOf()', function(){ + * suiteSetup(function(){ + * + * }); + * + * test('should return -1 when not present', function(){ + * + * }); + * + * test('should return the index when present', function(){ + * + * }); + * + * suiteTeardown(function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before each test case. + */ + + context.setup = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.teardown = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Execute before the suite. + */ + + context.suiteSetup = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after the suite. + */ + + context.suiteTeardown = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.suite = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + var test = new Test(title, fn); + suites[0].addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn){ + var test = context.test(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.test.skip = function(title){ + context.test(title); + }; + }); +}; + +}); // module: interfaces/tdd.js + +require.register("mocha.js", function(module, exports, require){ +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('browser/path') + , utils = require('./utils'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * Expose internals. + */ + +exports.utils = utils; +exports.interfaces = require('./interfaces'); +exports.reporters = require('./reporters'); +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @param {String} name + * @return {String} + * @api private + */ + +function image(name) { + return __dirname + '/../images/' + name + '.png'; +} + +/** + * Setup mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `bail` bail on the first test failure + * - `slow` milliseconds to wait before considering a test slow + * - `ignoreLeaks` ignore global leaks + * - `grep` string or regexp to filter tests with + * + * @param {Object} options + * @api public + */ + +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + this.grep(options.grep); + this.suite = new exports.Suite('', new exports.Context); + this.ui(options.ui); + this.bail(options.bail); + this.reporter(options.reporter); + if (options.timeout) this.timeout(options.timeout); + if (options.slow) this.slow(options.slow); +} + +/** + * Enable or disable bailing on the first failure. + * + * @param {Boolean} [bail] + * @api public + */ + +Mocha.prototype.bail = function(bail){ + if (0 == arguments.length) bail = true; + this.suite.bail(bail); + return this; +}; + +/** + * Add test `file`. + * + * @param {String} file + * @api public + */ + +Mocha.prototype.addFile = function(file){ + this.files.push(file); + return this; +}; + +/** + * Set reporter to `reporter`, defaults to "dot". + * + * @param {String|Function} reporter name or constructor + * @api public + */ + +Mocha.prototype.reporter = function(reporter){ + if ('function' == typeof reporter) { + this._reporter = reporter; + } else { + reporter = reporter || 'dot'; + try { + this._reporter = require('./reporters/' + reporter); + } catch (err) { + this._reporter = require(reporter); + } + if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); + } + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @param {String} bdd + * @api public + */ + +Mocha.prototype.ui = function(name){ + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) throw new Error('invalid interface "' + name + '"'); + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ + +Mocha.prototype.loadFiles = function(fn){ + var self = this; + var suite = this.suite; + var pending = this.files.length; + this.files.forEach(function(file){ + file = path.resolve(file); + suite.emit('pre-require', global, file, self); + suite.emit('require', require(file), file, self); + suite.emit('post-require', global, file, self); + --pending || (fn && fn()); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ + +Mocha.prototype._growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + name: 'mocha' + , title: 'Passed' + , image: image('ok') + }); + } + }); +}; + +/** + * Add regexp to grep, if `re` is a string it is escaped. + * + * @param {RegExp|String} re + * @return {Mocha} + * @api public + */ + +Mocha.prototype.grep = function(re){ + this.options.grep = 'string' == typeof re + ? new RegExp(utils.escapeRegexp(re)) + : re; + return this; +}; + +/** + * Invert `.grep()` matches. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.invert = function(){ + this.options.invert = true; + return this; +}; + +/** + * Ignore global leaks. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.ignoreLeaks = function(){ + this.options.ignoreLeaks = true; + return this; +}; + +/** + * Enable global leak checking. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.checkLeaks = function(){ + this.options.ignoreLeaks = false; + return this; +}; + +/** + * Enable growl support. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.growl = function(){ + this.options.growl = true; + return this; +}; + +/** + * Ignore `globals` array or string. + * + * @param {Array|String} globals + * @return {Mocha} + * @api public + */ + +Mocha.prototype.globals = function(globals){ + this.options.globals = (this.options.globals || []).concat(globals); + return this; +}; + +/** + * Set the timeout in milliseconds. + * + * @param {Number} timeout + * @return {Mocha} + * @api public + */ + +Mocha.prototype.timeout = function(timeout){ + this.suite.timeout(timeout); + return this; +}; + +/** + * Set slowness threshold in milliseconds. + * + * @param {Number} slow + * @return {Mocha} + * @api public + */ + +Mocha.prototype.slow = function(slow){ + this.suite.slow(slow); + return this; +}; + +/** + * Makes all tests async (accepting a callback) + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.asyncOnly = function(){ + this.options.asyncOnly = true; + return this; +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @param {Function} fn + * @return {Runner} + * @api public + */ + +Mocha.prototype.run = function(fn){ + if (this.files.length) this.loadFiles(); + var suite = this.suite; + var options = this.options; + var runner = new exports.Runner(suite); + var reporter = new this._reporter(runner); + runner.ignoreLeaks = false !== options.ignoreLeaks; + runner.asyncOnly = options.asyncOnly; + if (options.grep) runner.grep(options.grep, options.invert); + if (options.globals) runner.globals(options.globals); + if (options.growl) this._growl(runner, reporter); + return runner.run(fn); +}; + +}); // module: mocha.js + +require.register("ms.js", function(module, exports, require){ + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; + +/** + * Parse or format the given `val`. + * + * @param {String|Number} val + * @return {String|Number} + * @api public + */ + +module.exports = function(val){ + if ('string' == typeof val) return parse(val); + return format(val); +} + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); + if (!m) return; + var n = parseFloat(m[1]); + var type = (m[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'y': + return n * 31557600000; + case 'days': + case 'day': + case 'd': + return n * 86400000; + case 'hours': + case 'hour': + case 'h': + return n * 3600000; + case 'minutes': + case 'minute': + case 'm': + return n * 60000; + case 'seconds': + case 'second': + case 's': + return n * 1000; + case 'ms': + return n; + } +} + +/** + * Format the given `ms`. + * + * @param {Number} ms + * @return {String} + * @api public + */ + +function format(ms) { + if (ms == d) return Math.round(ms / d) + ' day'; + if (ms > d) return Math.round(ms / d) + ' days'; + if (ms == h) return Math.round(ms / h) + ' hour'; + if (ms > h) return Math.round(ms / h) + ' hours'; + if (ms == m) return Math.round(ms / m) + ' minute'; + if (ms > m) return Math.round(ms / m) + ' minutes'; + if (ms == s) return Math.round(ms / s) + ' second'; + if (ms > s) return Math.round(ms / s) + ' seconds'; + return ms + ' ms'; +} +}); // module: ms.js + +require.register("reporters/base.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var tty = require('browser/tty') + , diff = require('browser/diff') + , ms = require('../ms'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Enable coloring by default. + */ + +exports.useColors = isatty; + +/** + * Default color map. + */ + +exports.colors = { + 'pass': 90 + , 'fail': 31 + , 'bright pass': 92 + , 'bright fail': 91 + , 'bright yellow': 93 + , 'pending': 36 + , 'suite': 0 + , 'error title': 0 + , 'error message': 31 + , 'error stack': 90 + , 'checkmark': 32 + , 'fast': 90 + , 'medium': 33 + , 'slow': 31 + , 'green': 32 + , 'light': 90 + , 'diff gutter': 90 + , 'diff added': 42 + , 'diff removed': 41 +}; + +/** + * Default symbol map. + */ + +exports.symbols = { + ok: '✓', + err: '✖', + dot: '․' +}; + +// With node.js on Windows: use symbols available in terminal default fonts +if ('win32' == process.platform) { + exports.symbols.ok = '\u221A'; + exports.symbols.err = '\u00D7'; + exports.symbols.dot = '.'; +} + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {String} type + * @param {String} str + * @return {String} + * @api private + */ + +var color = exports.color = function(type, str) { + if (!exports.useColors) return str; + return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; +}; + +/** + * Expose term window size, with some + * defaults for when stderr is not a tty. + */ + +exports.window = { + width: isatty + ? process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1] + : 75 +}; + +/** + * Expose some basic cursor interactions + * that are common among reporters. + */ + +exports.cursor = { + hide: function(){ + process.stdout.write('\u001b[?25l'); + }, + + show: function(){ + process.stdout.write('\u001b[?25h'); + }, + + deleteLine: function(){ + process.stdout.write('\u001b[2K'); + }, + + beginningOfLine: function(){ + process.stdout.write('\u001b[0G'); + }, + + CR: function(){ + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } +}; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures){ + console.error(); + failures.forEach(function(test, i){ + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var err = test.err + , message = err.message || '' + , stack = err.stack || message + , index = stack.indexOf(message) + message.length + , msg = stack.slice(0, index) + , actual = err.actual + , expected = err.expected + , escape = true; + + // explicitly show diff + if (err.showDiff) { + escape = false; + err.actual = actual = JSON.stringify(actual, null, 2); + err.expected = expected = JSON.stringify(expected, null, 2); + } + + // actual / expected diff + if ('string' == typeof actual && 'string' == typeof expected) { + var len = Math.max(actual.length, expected.length); + + if (len < 20) msg = errorDiff(err, 'Chars', escape); + else msg = errorDiff(err, 'Words', escape); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + + fmt = color('error title', ' %s) %s:\n%s') + + color('error stack', '\n%s\n'); + } + + // indent stack trace without msg + stack = stack.slice(index ? index + 1 : index) + .replace(/^/gm, ' '); + + console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var self = this + , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } + , failures = this.failures = []; + + if (!runner) return; + this.runner = runner; + + runner.stats = stats; + + runner.on('start', function(){ + stats.start = new Date; + }); + + runner.on('suite', function(suite){ + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function(test){ + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test){ + stats.passes = stats.passes || 0; + + var medium = test.slow() / 2; + test.speed = test.duration > test.slow() + ? 'slow' + : test.duration > medium + ? 'medium' + : 'fast'; + + stats.passes++; + }); + + runner.on('fail', function(test, err){ + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function(){ + stats.end = new Date; + stats.duration = new Date - stats.start; + }); + + runner.on('pending', function(){ + stats.pending++; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ + +Base.prototype.epilogue = function(){ + var stats = this.stats + , fmt + , tests; + + console.log(); + + function pluralize(n) { + return 1 == n ? 'test' : 'tests'; + } + + // failure + if (stats.failures) { + fmt = color('bright fail', ' ' + exports.symbols.err) + + color('fail', ' %d of %d %s failed') + + color('light', ':') + + console.error(fmt, + stats.failures, + this.runner.total, + pluralize(this.runner.total)); + + Base.list(this.failures); + console.error(); + return; + } + + // pass + fmt = color('bright pass', ' ') + + color('green', ' %d %s complete') + + color('light', ' (%s)'); + + console.log(fmt, + stats.tests || 0, + pluralize(stats.tests), + ms(stats.duration)); + + // pending + if (stats.pending) { + fmt = color('pending', ' ') + + color('pending', ' %d %s pending'); + + console.log(fmt, stats.pending, pluralize(stats.pending)); + } + + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @param {String} str + * @param {String} len + * @return {String} + * @api private + */ + +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Return a character diff for `err`. + * + * @param {Error} err + * @return {String} + * @api private + */ + +function errorDiff(err, type, escape) { + return diff['diff' + type](err.actual, err.expected).map(function(str){ + if (escape) { + str.value = str.value + .replace(/\t/g, '') + .replace(/\r/g, '') + .replace(/\n/g, '\n'); + } + if (str.added) return colorLines('diff added', str.value); + if (str.removed) return colorLines('diff removed', str.value); + return str.value; + }).join(''); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @param {String} name + * @param {String} str + * @return {String} + * @api private + */ + +function colorLines(name, str) { + return str.split('\n').map(function(str){ + return color(name, str); + }).join('\n'); +} + +}); // module: reporters/base.js + +require.register("reporters/doc.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Doc(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite){ + if (suite.root) return; + ++indents; + console.log('%s
', indent()); + ++indents; + console.log('%s

%s

', indent(), utils.escape(suite.title)); + console.log('%s
', indent()); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + console.log('%s
', indent()); + --indents; + console.log('%s
', indent()); + --indents; + }); + + runner.on('pass', function(test){ + console.log('%s
%s
', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + }); +} + +}); // module: reporters/doc.js + +require.register("reporters/dot.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Dot(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , n = 0; + + runner.on('start', function(){ + process.stdout.write('\n '); + }); + + runner.on('pending', function(test){ + process.stdout.write(color('pending', Base.symbols.dot)); + }); + + runner.on('pass', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + if ('slow' == test.speed) { + process.stdout.write(color('bright yellow', Base.symbols.dot)); + } else { + process.stdout.write(color(test.speed, Base.symbols.dot)); + } + }); + + runner.on('fail', function(test, err){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('fail', Base.symbols.dot)); + }); + + runner.on('end', function(){ + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Dot.prototype = new F; +Dot.prototype.constructor = Dot; + +}); // module: reporters/dot.js + +require.register("reporters/html-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov') + , fs = require('browser/fs'); + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTMLCov(runner) { + var jade = require('jade') + , file = __dirname + '/templates/coverage.jade' + , str = fs.readFileSync(file, 'utf8') + , fn = jade.compile(str, { filename: file }) + , self = this; + + JSONCov.call(this, runner, false); + + runner.on('end', function(){ + process.stdout.write(fn({ + cov: self.cov + , coverageClass: coverageClass + })); + }); +} + +/** + * Return coverage class for `n`. + * + * @return {String} + * @api private + */ + +function coverageClass(n) { + if (n >= 75) return 'high'; + if (n >= 50) return 'medium'; + if (n >= 25) return 'low'; + return 'terrible'; +} +}); // module: reporters/html-cov.js + +require.register("reporters/html.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , Progress = require('../browser/progress') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `Doc`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = ''; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTML(runner, root) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , stat = fragment(statsTemplate) + , items = stat.getElementsByTagName('li') + , passes = items[1].getElementsByTagName('em')[0] + , passesLink = items[1].getElementsByTagName('a')[0] + , failures = items[2].getElementsByTagName('em')[0] + , failuresLink = items[2].getElementsByTagName('a')[0] + , duration = items[3].getElementsByTagName('em')[0] + , canvas = stat.getElementsByTagName('canvas')[0] + , report = fragment('
    ') + , stack = [report] + , progress + , ctx + + root = root || document.getElementById('mocha'); + + if (canvas.getContext) { + var ratio = window.devicePixelRatio || 1; + canvas.style.width = canvas.width; + canvas.style.height = canvas.height; + canvas.width *= ratio; + canvas.height *= ratio; + ctx = canvas.getContext('2d'); + ctx.scale(ratio, ratio); + progress = new Progress; + } + + if (!root) return error('#mocha div missing, add it to your document'); + + // pass toggle + on(passesLink, 'click', function(){ + unhide(); + var name = /pass/.test(report.className) ? '' : ' pass'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test pass'); + }); + + // failure toggle + on(failuresLink, 'click', function(){ + unhide(); + var name = /fail/.test(report.className) ? '' : ' fail'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test fail'); + }); + + root.appendChild(stat); + root.appendChild(report); + + if (progress) progress.size(40); + + runner.on('suite', function(suite){ + if (suite.root) return; + + // suite + var url = '?grep=' + encodeURIComponent(suite.fullTitle()); + var el = fragment('
  • %s

  • ', url, escape(suite.title)); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('ul')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + stack.shift(); + }); + + runner.on('fail', function(test, err){ + if ('hook' == test.type) runner.emit('test end', test); + }); + + runner.on('test end', function(test){ + // TODO: add to stats + var percent = stats.tests / this.total * 100 | 0; + if (progress) progress.update(percent).draw(ctx); + + // update stats + var ms = new Date - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + + // test + if ('passed' == test.state) { + var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration, encodeURIComponent(test.fullTitle())); + } else if (test.pending) { + var el = fragment('
  • %e

  • ', test.title); + } else { + var el = fragment('
  • %e

  • ', test.title, encodeURIComponent(test.fullTitle())); + var str = test.err.stack || test.err.toString(); + + // FF / Opera do not add the message + if (!~str.indexOf(test.err.message)) { + str = test.err.message + '\n' + str; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == str) str = test.err.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { + str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; + } + + el.appendChild(fragment('
    %e
    ', str)); + } + + // toggle code + // TODO: defer + if (!test.pending) { + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function(){ + pre.style.display = 'none' == pre.style.display + ? 'block' + : 'none'; + }); + + var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. + if (stack[0]) stack[0].appendChild(el); + }); +} + +/** + * Display error `msg`. + */ + +function error(msg) { + document.body.appendChild(fragment('
    %s
    ', msg)); +} + +/** + * Return a DOM fragment from `html`. + */ + +function fragment(html) { + var args = arguments + , div = document.createElement('div') + , i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type){ + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + } + }); + + return div.firstChild; +} + +/** + * Check for suites that do not have elements + * with `classname`, and hide them. + */ + +function hideSuitesWithout(classname) { + var suites = document.getElementsByClassName('suite'); + for (var i = 0; i < suites.length; i++) { + var els = suites[i].getElementsByClassName(classname); + if (0 == els.length) suites[i].className += ' hidden'; + } +} + +/** + * Unhide .hidden suites. + */ + +function unhide() { + var els = document.getElementsByClassName('suite hidden'); + for (var i = 0; i < els.length; ++i) { + els[i].className = els[i].className.replace('suite hidden', 'suite'); + } +} + +/** + * Set `el` text to `str`. + */ + +function text(el, str) { + if (el.textContent) { + el.textContent = str; + } else { + el.innerText = str; + } +} + +/** + * Listen on `event` with callback `fn`. + */ + +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +}); // module: reporters/html.js + +require.register("reporters/index.js", function(module, exports, require){ + +exports.Base = require('./base'); +exports.Dot = require('./dot'); +exports.Doc = require('./doc'); +exports.TAP = require('./tap'); +exports.JSON = require('./json'); +exports.HTML = require('./html'); +exports.List = require('./list'); +exports.Min = require('./min'); +exports.Spec = require('./spec'); +exports.Nyan = require('./nyan'); +exports.XUnit = require('./xunit'); +exports.Markdown = require('./markdown'); +exports.Progress = require('./progress'); +exports.Landing = require('./landing'); +exports.JSONCov = require('./json-cov'); +exports.HTMLCov = require('./html-cov'); +exports.JSONStream = require('./json-stream'); +exports.Teamcity = require('./teamcity'); + +}); // module: reporters/index.js + +require.register("reporters/json-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @param {Boolean} output + * @api public + */ + +function JSONCov(runner, output) { + var self = this + , output = 1 == arguments.length ? true : output; + + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) return; + process.stdout.write(JSON.stringify(result, null, 2 )); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @param {Object} cov + * @return {Object} + * @api private + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage' + , sloc: 0 + , hits: 0 + , misses: 0 + , coverage: 0 + , files: [] + }; + + for (var filename in cov) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + + ret.files.sort(function(a, b) { + return a.filename.localeCompare(b.filename); + }); + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +}; + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @param {String} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + * @api private + */ + +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num){ + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line + , coverage: data[num] === undefined + ? '' + : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} + +}); // module: reporters/json-cov.js + +require.register("reporters/json-stream.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total; + + runner.on('start', function(){ + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test){ + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err){ + console.log(JSON.stringify(['fail', clean(test)])); + }); + + runner.on('end', function(){ + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json-stream.js + +require.register("reporters/json.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @param {Runner} runner + * @api public + */ + +function JSONReporter(runner) { + var self = this; + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var obj = { + stats: self.stats + , tests: tests.map(clean) + , failures: failures.map(clean) + , passes: passes.map(clean) + }; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json.js + +require.register("reporters/landing.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Landing(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , total = runner.total + , stream = process.stdout + , plane = color('plane', '✈') + , crashed = -1 + , n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function(){ + stream.write('\n '); + cursor.hide(); + }); + + runner.on('test end', function(test){ + // check if the plane crashed + var col = -1 == crashed + ? width * ++n / total | 0 + : crashed; + + // show the crash + if ('failed' == test.state) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\u001b[4F\n\n'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane) + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\u001b[0m'); + }); + + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Landing.prototype = new F; +Landing.prototype.constructor = Landing; + +}); // module: reporters/landing.js + +require.register("reporters/list.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 0; + + runner.on('start', function(){ + console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test){ + var fmt = color('checkmark', ' '+Base.symbols.dot) + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +List.prototype = new F; +List.prototype.constructor = List; + + +}); // module: reporters/list.js + +require.register("reporters/markdown.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Markdown(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , level = 0 + , buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function indent() { + return Array(level).join(' '); + } + + function mapTOC(suite, obj) { + var ret = obj; + obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + suite.suites.forEach(function(suite){ + mapTOC(suite, obj); + }); + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if ('suite' == key) continue; + if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + if (key) buf += Array(level).join(' ') + link; + buf += stringifyTOC(obj[key], level); + } + --level; + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite){ + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function(suite){ + --level; + }); + + runner.on('pass', function(test){ + var code = utils.clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js\n'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function(){ + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} +}); // module: reporters/markdown.js + +require.register("reporters/min.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @param {Runner} runner + * @api public + */ + +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function(){ + // clear screen + process.stdout.write('\u001b[2J'); + // set cursor position + process.stdout.write('\u001b[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Min.prototype = new F; +Min.prototype.constructor = Min; + + +}); // module: reporters/min.js + +require.register("reporters/nyan.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = NyanCat; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function NyanCat(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , rainbowColors = this.rainbowColors = self.generateColors() + , colorIndex = this.colorIndex = 0 + , numerOfLines = this.numberOfLines = 4 + , trajectories = this.trajectories = [[], [], [], []] + , nyanCatWidth = this.nyanCatWidth = 11 + , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) + , scoreboardWidth = this.scoreboardWidth = 5 + , tick = this.tick = 0 + , n = 0; + + runner.on('start', function(){ + Base.cursor.hide(); + self.draw('start'); + }); + + runner.on('pending', function(test){ + self.draw('pending'); + }); + + runner.on('pass', function(test){ + self.draw('pass'); + }); + + runner.on('fail', function(test, err){ + self.draw('fail'); + }); + + runner.on('end', function(){ + Base.cursor.show(); + for (var i = 0; i < self.numberOfLines; i++) write('\n'); + self.epilogue(); + }); +} + +/** + * Draw the nyan cat with runner `status`. + * + * @param {String} status + * @api private + */ + +NyanCat.prototype.draw = function(status){ + this.appendRainbow(); + this.drawScoreboard(); + this.drawRainbow(); + this.drawNyanCat(status); + this.tick = !this.tick; +}; + +/** + * Draw the "scoreboard" showing the number + * of passes, failures and pending tests. + * + * @api private + */ + +NyanCat.prototype.drawScoreboard = function(){ + var stats = this.stats; + var colors = Base.colors; + + function draw(color, n) { + write(' '); + write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write('\n'); + } + + draw(colors.green, stats.passes); + draw(colors.fail, stats.failures); + draw(colors.pending, stats.pending); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Append the rainbow. + * + * @api private + */ + +NyanCat.prototype.appendRainbow = function(){ + var segment = this.tick ? '_' : '-'; + var rainbowified = this.rainbowify(segment); + + for (var index = 0; index < this.numberOfLines; index++) { + var trajectory = this.trajectories[index]; + if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); + trajectory.push(rainbowified); + } +}; + +/** + * Draw the rainbow. + * + * @api private + */ + +NyanCat.prototype.drawRainbow = function(){ + var self = this; + + this.trajectories.forEach(function(line, index) { + write('\u001b[' + self.scoreboardWidth + 'C'); + write(line.join('')); + write('\n'); + }); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw the nyan cat with `status`. + * + * @param {String} status + * @api private + */ + +NyanCat.prototype.drawNyanCat = function(status) { + var self = this; + var startWidth = this.scoreboardWidth + this.trajectories[0].length; + var color = '\u001b[' + startWidth + 'C'; + var padding = ''; + + write(color); + write('_,------,'); + write('\n'); + + write(color); + padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + + write(color); + padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + var face; + switch (status) { + case 'pass': + face = '( ^ .^)'; + break; + case 'fail': + face = '( o .o)'; + break; + default: + face = '( - .-)'; + } + write(tail + '|' + padding + face + ' '); + write('\n'); + + write(color); + padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Move cursor up `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorUp = function(n) { + write('\u001b[' + n + 'A'); +}; + +/** + * Move cursor down `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorDown = function(n) { + write('\u001b[' + n + 'B'); +}; + +/** + * Generate rainbow colors. + * + * @return {Array} + * @api private + */ + +NyanCat.prototype.generateColors = function(){ + var colors = []; + + for (var i = 0; i < (6 * 7); i++) { + var pi3 = Math.floor(Math.PI / 3); + var n = (i * (1.0 / 6)); + var r = Math.floor(3 * Math.sin(n) + 3); + var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); + var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); + colors.push(36 * r + 6 * g + b + 16); + } + + return colors; +}; + +/** + * Apply rainbow to the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +NyanCat.prototype.rainbowify = function(str){ + var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; + this.colorIndex += 1; + return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; +}; + +/** + * Stdout helper. + */ + +function write(string) { + process.stdout.write(string); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +NyanCat.prototype = new F; +NyanCat.prototype.constructor = NyanCat; + + +}); // module: reporters/nyan.js + +require.register("reporters/progress.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @param {Runner} runner + * @param {Object} options + * @api public + */ + +function Progress(runner, options) { + Base.call(this, runner); + + var self = this + , options = options || {} + , stats = this.stats + , width = Base.window.width * .50 | 0 + , total = runner.total + , complete = 0 + , max = Math.max; + + // default chars + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || Base.symbols.dot; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function(){ + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function(){ + complete++; + var incomplete = total - complete + , percent = complete / total + , n = width * percent | 0 + , i = width - n; + + cursor.CR(); + process.stdout.write('\u001b[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Progress.prototype = new F; +Progress.prototype.constructor = Progress; + + +}); // module: reporters/progress.js + +require.register("reporters/spec.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Spec(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , indents = 0 + , n = 0; + + function indent() { + return Array(indents).join(' ') + } + + runner.on('start', function(){ + console.log(); + }); + + runner.on('suite', function(suite){ + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function(suite){ + --indents; + if (1 == indents) console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test){ + if ('fast' == test.speed) { + var fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s '); + cursor.CR(); + console.log(fmt, test.title); + } else { + var fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s ') + + color(test.speed, '(%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Spec.prototype = new F; +Spec.prototype.constructor = Spec; + + +}); // module: reporters/spec.js + +require.register("reporters/tap.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @param {Runner} runner + * @api public + */ + +function TAP(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 1 + , passes = 0 + , failures = 0; + + runner.on('start', function(){ + var total = runner.grepTotal(runner.suite); + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function(){ + ++n; + }); + + runner.on('pending', function(test){ + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test){ + passes++; + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err){ + failures++; + console.log('not ok %d %s', n, title(test)); + if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); + }); + + runner.on('end', function(){ + console.log('# tests ' + (passes + failures)); + console.log('# pass ' + passes); + console.log('# fail ' + failures); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @param {Object} test + * @return {String} + * @api private + */ + +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +}); // module: reporters/tap.js + +require.register("reporters/teamcity.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Teamcity(runner) { + Base.call(this, runner); + var stats = this.stats; + + runner.on('start', function() { + console.log("##teamcity[testSuiteStarted name='mocha.suite']"); + }); + + runner.on('test', function(test) { + console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); + }); + + runner.on('fail', function(test, err) { + console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); + }); + + runner.on('pending', function(test) { + console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); + }); + + runner.on('test end', function(test) { + console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); + }); + + runner.on('end', function() { + console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); + }); +} + +/** + * Escape the given `str`. + */ + +function escape(str) { + return str + .replace(/\|/g, "||") + .replace(/\n/g, "|n") + .replace(/\r/g, "|r") + .replace(/\[/g, "|[") + .replace(/\]/g, "|]") + .replace(/\u0085/g, "|x") + .replace(/\u2028/g, "|l") + .replace(/\u2029/g, "|p") + .replace(/'/g, "|'"); +} + +}); // module: reporters/teamcity.js + +require.register("reporters/xunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @param {Runner} runner + * @api public + */ + +function XUnit(runner) { + Base.call(this, runner); + var stats = this.stats + , tests = [] + , self = this; + + runner.on('pass', function(test){ + tests.push(test); + }); + + runner.on('fail', function(test){ + tests.push(test); + }); + + runner.on('end', function(){ + console.log(tag('testsuite', { + name: 'Mocha Tests' + , tests: stats.tests + , failures: stats.failures + , errors: stats.failures + , skip: stats.tests - stats.failures - stats.passes + , timestamp: (new Date).toUTCString() + , time: stats.duration / 1000 + }, false)); + + tests.forEach(test); + console.log(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +XUnit.prototype = new F; +XUnit.prototype.constructor = XUnit; + + +/** + * Output tag for the given `test.` + */ + +function test(test) { + var attrs = { + classname: test.parent.fullTitle() + , name: test.title + , time: test.duration / 1000 + }; + + if ('failed' == test.state) { + var err = test.err; + attrs.message = escape(err.message); + console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + } else if (test.pending) { + console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + console.log(tag('testcase', attrs, true) ); + } +} + +/** + * HTML tag helper. + */ + +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>' + , pairs = [] + , tag; + + for (var key in attrs) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) tag += content + ''; +} + +}); // module: reporters/xunit.js + +require.register("runnable.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runnable') + , milliseconds = require('./ms'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Object#toString(). + */ + +var toString = Object.prototype.toString; + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = ! this.async; + this._timeout = 2000; + this._slow = 75; + this.timedOut = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Runnable.prototype = new F; +Runnable.prototype.constructor = Runnable; + + +/** + * Set & get timeout `ms`. + * + * @param {Number|String} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) this.resetTimeout(); + return this; +}; + +/** + * Set & get slow `ms`. + * + * @param {Number|String} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._slow = ms; + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Runnable.prototype.fullTitle = function(){ + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ + +Runnable.prototype.clearTimeout = function(){ + clearTimeout(this.timer); +}; + +/** + * Inspect the runnable void of private properties. + * + * @return {String} + * @api private + */ + +Runnable.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_' == key[0]) return; + if ('parent' == key) return '#'; + if ('ctx' == key) return '#'; + return val; + }, 2); +}; + +/** + * Reset the timeout. + * + * @api private + */ + +Runnable.prototype.resetTimeout = function(){ + var self = this + , ms = this.timeout(); + + this.clearTimeout(); + if (ms) { + this.timer = setTimeout(function(){ + self.callback(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runnable.prototype.run = function(fn){ + var self = this + , ms = this.timeout() + , start = new Date + , ctx = this.ctx + , finished + , emitted; + + if (ctx) ctx.runnable(this); + + // timeout + if (this.async) { + if (ms) { + this.timer = setTimeout(function(){ + done(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } + } + + // called multiple times + function multiple(err) { + if (emitted) return; + emitted = true; + self.emit('error', err || new Error('done() called multiple times')); + } + + // finished + function done(err) { + if (self.timedOut) return; + if (finished) return multiple(err); + self.clearTimeout(); + self.duration = new Date - start; + finished = true; + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // async + if (this.async) { + try { + this.fn.call(ctx, function(err){ + if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); + if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + done(); + }); + } catch (err) { + done(err); + } + return; + } + + if (this.asyncOnly) { + return done(new Error('--async-only option in use without declaring `done()`')); + } + + // sync + try { + if (!this.pending) this.fn.call(ctx); + this.duration = new Date - start; + fn(); + } catch (err) { + fn(err); + } +}; + +}); // module: runnable.js + +require.register("runner.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runner') + , Test = require('./test') + , utils = require('./utils') + , filter = utils.filter + , keys = utils.keys; + +/** + * Non-enumerable globals. + */ + +var globals = [ + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'XMLHttpRequest', + 'Date' +]; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * + * @api public + */ + +function Runner(suite) { + var self = this; + this._globals = []; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test){ self.checkGlobals(test); }); + this.on('hook end', function(hook){ self.checkGlobals(hook); }); + this.grep(/.*/); + this.globals(this.globalProps().concat(['errno'])); +} + +/** + * Wrapper for setImmediate, process.nextTick, or browser polyfill. + * + * @param {Function} fn + * @api private + */ + +Runner.immediately = global.setImmediate || process.nextTick; + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Runner.prototype = new F; +Runner.prototype.constructor = Runner; + + +/** + * Run tests with full titles matching `re`. Updates runner.total + * with number of tests matched. + * + * @param {RegExp} re + * @param {Boolean} invert + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.grep = function(re, invert){ + debug('grep %s', re); + this._grep = re; + this._invert = invert; + this.total = this.grepTotal(this.suite); + return this; +}; + +/** + * Returns the number of tests matching the grep search for the + * given suite. + * + * @param {Suite} suite + * @return {Number} + * @api public + */ + +Runner.prototype.grepTotal = function(suite) { + var self = this; + var total = 0; + + suite.eachTest(function(test){ + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (match) total++; + }); + + return total; +}; + +/** + * Return a list of global properties. + * + * @return {Array} + * @api private + */ + +Runner.prototype.globalProps = function() { + var props = utils.keys(global); + + // non-enumerables + for (var i = 0; i < globals.length; ++i) { + if (~utils.indexOf(props, globals[i])) continue; + props.push(globals[i]); + } + + return props; +}; + +/** + * Allow the given `arr` of globals. + * + * @param {Array} arr + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.globals = function(arr){ + if (0 == arguments.length) return this._globals; + debug('globals %j', arr); + utils.forEach(arr, function(arr){ + this._globals.push(arr); + }, this); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ + +Runner.prototype.checkGlobals = function(test){ + if (this.ignoreLeaks) return; + var ok = this._globals; + var globals = this.globalProps(); + var isNode = process.kill; + var leaks; + + // check length - 2 ('errno' and 'location' globals) + if (isNode && 1 == ok.length - globals.length) return + else if (2 == ok.length - globals.length) return; + + leaks = filterLeaks(ok, globals); + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @param {Test} test + * @param {Error} err + * @api private + */ + +Runner.prototype.fail = function(test, err){ + ++this.failures; + test.state = 'failed'; + + if ('string' == typeof err) { + err = new Error('the string "' + err + '" was thrown, throw an Error :)'); + } + + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures (currently) hard-end due + * to that fact that a failing hook will + * surely cause subsequent tests to fail, + * causing jumbled reporting. + * + * @param {Hook} hook + * @param {Error} err + * @api private + */ + +Runner.prototype.failHook = function(hook, err){ + this.fail(hook, err); + this.emit('end'); +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @param {String} name + * @param {Function} function + * @api private + */ + +Runner.prototype.hook = function(name, fn){ + var suite = this.suite + , hooks = suite['_' + name] + , self = this + , timer; + + function next(i) { + var hook = hooks[i]; + if (!hook) return fn(); + self.currentRunnable = hook; + + self.emit('hook', hook); + + hook.on('error', function(err){ + self.failHook(hook, err); + }); + + hook.run(function(err){ + hook.removeAllListeners('error'); + var testError = hook.error(); + if (testError) self.fail(self.test, testError); + if (err) return self.failHook(hook, err); + self.emit('hook end', hook); + next(++i); + }); + } + + Runner.immediately(function(){ + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err)`. + * + * @param {String} name + * @param {Array} suites + * @param {Function} fn + * @api private + */ + +Runner.prototype.hooks = function(name, suites, fn){ + var self = this + , orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err){ + if (err) { + self.suite = orig; + return fn(err); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookUp = function(name, fn){ + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookDown = function(name, fn){ + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @api private + */ + +Runner.prototype.parents = function(){ + var suite = this.suite + , suites = []; + while (suite = suite.parent) suites.push(suite); + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTest = function(fn){ + var test = this.test + , self = this; + + if (this.asyncOnly) test.asyncOnly = true; + + try { + test.on('error', function(err){ + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke + * the callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTests = function(suite, fn){ + var self = this + , tests = suite.tests.slice() + , test; + + function next(err) { + // if we bail after first err + if (self.failures && suite._bail) return fn(); + + // next test + test = tests.shift(); + + // all done + if (!test) return fn(); + + // grep + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (!match) return next(); + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(){ + self.currentRunnable = self.test; + self.runTest(function(err){ + test = self.test; + + if (err) { + self.fail(test, err); + self.emit('test end', test); + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the + * callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runSuite = function(suite, fn){ + var total = this.grepTotal(suite) + , self = this + , i = 0; + + debug('run suite %s', suite.fullTitle()); + + if (!total) return fn(); + + this.emit('suite', this.suite = suite); + + function next() { + var curr = suite.suites[i++]; + if (!curr) return done(); + self.runSuite(curr, next); + } + + function done() { + self.suite = suite; + self.hook('afterAll', function(){ + self.emit('suite end', suite); + fn(); + }); + } + + this.hook('beforeAll', function(){ + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @param {Error} err + * @api private + */ + +Runner.prototype.uncaught = function(err){ + debug('uncaught exception %s', err.message); + var runnable = this.currentRunnable; + if (!runnable || 'failed' == runnable.state) return; + runnable.clearTimeout(); + err.uncaught = true; + this.fail(runnable, err); + + // recover from test + if ('test' == runnable.type) { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @param {Function} fn + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.run = function(fn){ + var self = this + , fn = fn || function(){}; + + function uncaught(err){ + self.uncaught(err); + } + + debug('start'); + + // callback + this.on('end', function(){ + debug('end'); + process.removeListener('uncaughtException', uncaught); + fn(self.failures); + }); + + // run suites + this.emit('start'); + this.runSuite(this.suite, function(){ + debug('finished running'); + self.emit('end'); + }); + + // uncaught exception + process.on('uncaughtException', uncaught); + + return this; +}; + +/** + * Filter leaks with the given globals flagged as `ok`. + * + * @param {Array} ok + * @param {Array} globals + * @return {Array} + * @api private + */ + +function filterLeaks(ok, globals) { + return filter(globals, function(key){ + var matched = filter(ok, function(ok){ + if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); + // Opera and IE expose global variables for HTML element IDs (issue #243) + if (/^mocha-/.test(key)) return true; + return key == ok; + }); + return matched.length == 0 && (!global.navigator || 'onerror' !== key); + }); +} + +}); // module: runner.js + +require.register("suite.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:suite') + , milliseconds = require('./ms') + , utils = require('./utils') + , Hook = require('./hook'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` + * and parent `Suite`. When a suite with the + * same title is already present, that suite + * is returned to provide nicer reporter + * and more flexible meta-testing. + * + * @param {Suite} parent + * @param {String} title + * @return {Suite} + * @api public + */ + +exports.create = function(parent, title){ + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + if (parent.pending) suite.pending = true; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given + * `title` and `ctx`. + * + * @param {String} title + * @param {Context} ctx + * @api private + */ + +function Suite(title, ctx) { + this.title = title; + this.ctx = ctx; + this.suites = []; + this.tests = []; + this.pending = false; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._slow = 75; + this._bail = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Suite.prototype = new F; +Suite.prototype.constructor = Suite; + + +/** + * Return a clone of this `Suite`. + * + * @return {Suite} + * @api private + */ + +Suite.prototype.clone = function(){ + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.slow(this.slow()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Set slow `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('slow %d', ms); + this._slow = ms; + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @parma {Boolean} bail + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.bail = function(bail){ + if (0 == arguments.length) return this._bail; + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeAll = function(fn){ + if (this.pending) return this; + var hook = new Hook('"before all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterAll = function(fn){ + if (this.pending) return this; + var hook = new Hook('"after all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeEach = function(fn){ + if (this.pending) return this; + var hook = new Hook('"before each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterEach = function(fn){ + if (this.pending) return this; + var hook = new Hook('"after each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @param {Suite} suite + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addSuite = function(suite){ + suite.parent = this; + suite.timeout(this.timeout()); + suite.slow(this.slow()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @param {Test} test + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addTest = function(test){ + test.parent = this; + test.timeout(this.timeout()); + test.slow(this.slow()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Suite.prototype.fullTitle = function(){ + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) return full + ' ' + this.title; + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @return {Number} + * @api public + */ + +Suite.prototype.total = function(){ + return utils.reduce(this.suites, function(sum, suite){ + return sum + suite.total(); + }, 0) + this.tests.length; +}; + +/** + * Iterates through each suite recursively to find + * all tests. Applies a function in the format + * `fn(test)`. + * + * @param {Function} fn + * @return {Suite} + * @api private + */ + +Suite.prototype.eachTest = function(fn){ + utils.forEach(this.tests, fn); + utils.forEach(this.suites, function(suite){ + suite.eachTest(fn); + }); + return this; +}; + +}); // module: suite.js + +require.register("test.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +function F(){}; +F.prototype = Runnable.prototype; +Test.prototype = new F; +Test.prototype.constructor = Test; + + +}); // module: test.js + +require.register("utils.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var fs = require('browser/fs') + , path = require('browser/path') + , join = path.join + , debug = require('browser/debug')('mocha:watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * Escape special characters in the given string of html. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html){ + return String(html) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope){ + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function(arr, obj, start){ + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @api private + */ + +exports.reduce = function(arr, fn, val){ + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn(rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @api private + */ + +exports.filter = function(arr, fn){ + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn(val, i, arr)) ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ret){ + ret = ret || []; + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ret); + } else if (path.match(/\.(js|coffee)$/)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +exports.clean = function(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return exports.trim(str); +}; + +/** + * Escape regular expression characters in `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.escapeRegexp = function(str){ + return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); +}; + +/** + * Trim the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.trim = function(str){ + return str.replace(/^\s+|\s+$/g, ''); +}; + +/** + * Parse the given `qs`. + * + * @param {String} qs + * @return {Object} + * @api private + */ + +exports.parseQuery = function(qs){ + return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ + var i = pair.indexOf('=') + , key = pair.slice(0, i) + , val = pair.slice(++i); + + obj[key] = decodeURIComponent(val); + return obj; + }, {}); +}; + +/** + * Highlight the given string of `js`. + * + * @param {String} js + * @return {String} + * @api private + */ + +function highlight(js) { + return js + .replace(//g, '>') + .replace(/\/\/(.*)/gm, '//$1') + .replace(/('.*?')/gm, '$1') + .replace(/(\d+\.\d+)/gm, '$1') + .replace(/(\d+)/gm, '$1') + .replace(/\bnew *(\w+)/gm, 'new $1') + .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') +} + +/** + * Highlight the contents of tag `name`. + * + * @param {String} name + * @api private + */ + +exports.highlightTags = function(name) { + var code = document.getElementsByTagName(name); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } +}; + +}); // module: utils.js +/** + * Node shims. + * + * These are meant only to allow + * mocha.js to run untouched, not + * to allow running node code in + * the browser. + */ + +process = {}; +process.exit = function(status){}; +process.stdout = {}; +global = window; + +/** + * Remove uncaughtException listener. + */ + +process.removeListener = function(e){ + if ('uncaughtException' == e) { + window.onerror = null; + } +}; + +/** + * Implements uncaughtException listener. + */ + +process.on = function(e, fn){ + if ('uncaughtException' == e) { + window.onerror = function(err, url, line){ + fn(new Error(err + ' (' + url + ':' + line + ')')); + }; + } +}; + +// boot +;(function(){ + + /** + * Expose mocha. + */ + + var Mocha = window.Mocha = require('mocha'), + mocha = window.mocha = new Mocha({ reporter: 'html' }); + + var immediateQueue = [] + , immediateTimeout; + + function timeslice() { + var immediateStart = new Date().getTime(); + while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { + immediateQueue.shift()(); + } + if (immediateQueue.length) { + immediateTimeout = setTimeout(timeslice, 0); + } else { + immediateTimeout = null; + } + } + + /** + * High-performance override of Runner.immediately. + */ + + Mocha.Runner.immediately = function(callback) { + immediateQueue.push(callback); + if (!immediateTimeout) { + immediateTimeout = setTimeout(timeslice, 0); + } + }; + + /** + * Override ui to ensure that the ui functions are initialized. + * Normally this would happen in Mocha.prototype.loadFiles. + */ + + mocha.ui = function(ui){ + Mocha.prototype.ui.call(this, ui); + this.suite.emit('pre-require', window, null, this); + return this; + }; + + /** + * Setup mocha with the given setting options. + */ + + mocha.setup = function(opts){ + if ('string' == typeof opts) opts = { ui: opts }; + for (var opt in opts) this[opt](opts[opt]); + return this; + }; + + /** + * Run mocha, returning the Runner. + */ + + mocha.run = function(fn){ + var options = mocha.options; + mocha.globals('location'); + + var query = Mocha.utils.parseQuery(window.location.search || ''); + if (query.grep) mocha.grep(query.grep); + if (query.invert) mocha.invert(); + + return Mocha.prototype.run.call(mocha, function(){ + Mocha.utils.highlightTags('code'); + if (fn) fn(); + }); + }; +})(); +})(); diff --git a/tests/libs/sinon-1.7.1.js b/tests/libs/sinon-1.7.1.js new file mode 100644 index 00000000..fe9529f7 --- /dev/null +++ b/tests/libs/sinon-1.7.1.js @@ -0,0 +1,4299 @@ +/** + * Sinon.JS 1.7.1, 2013/05/07 + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS + * + * (The BSD License) + * + * Copyright (c) 2010-2013, Christian Johansen, christian@cjohansen.no + * 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 Christian Johansen nor the names of his 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. + */ + +this.sinon = (function () { +var buster = (function (setTimeout, B) { + var isNode = typeof require == "function" && typeof module == "object"; + var div = typeof document != "undefined" && document.createElement("div"); + var F = function () {}; + + var buster = { + bind: function bind(obj, methOrProp) { + var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp; + var args = Array.prototype.slice.call(arguments, 2); + return function () { + var allArgs = args.concat(Array.prototype.slice.call(arguments)); + return method.apply(obj, allArgs); + }; + }, + + partial: function partial(fn) { + var args = [].slice.call(arguments, 1); + return function () { + return fn.apply(this, args.concat([].slice.call(arguments))); + }; + }, + + create: function create(object) { + F.prototype = object; + return new F(); + }, + + extend: function extend(target) { + if (!target) { return; } + for (var i = 1, l = arguments.length, prop; i < l; ++i) { + for (prop in arguments[i]) { + target[prop] = arguments[i][prop]; + } + } + return target; + }, + + nextTick: function nextTick(callback) { + if (typeof process != "undefined" && process.nextTick) { + return process.nextTick(callback); + } + setTimeout(callback, 0); + }, + + functionName: function functionName(func) { + if (!func) return ""; + if (func.displayName) return func.displayName; + if (func.name) return func.name; + var matches = func.toString().match(/function\s+([^\(]+)/m); + return matches && matches[1] || ""; + }, + + isNode: function isNode(obj) { + if (!div) return false; + try { + obj.appendChild(div); + obj.removeChild(div); + } catch (e) { + return false; + } + return true; + }, + + isElement: function isElement(obj) { + return obj && obj.nodeType === 1 && buster.isNode(obj); + }, + + isArray: function isArray(arr) { + return Object.prototype.toString.call(arr) == "[object Array]"; + }, + + flatten: function flatten(arr) { + var result = [], arr = arr || []; + for (var i = 0, l = arr.length; i < l; ++i) { + result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]); + } + return result; + }, + + each: function each(arr, callback) { + for (var i = 0, l = arr.length; i < l; ++i) { + callback(arr[i]); + } + }, + + map: function map(arr, callback) { + var results = []; + for (var i = 0, l = arr.length; i < l; ++i) { + results.push(callback(arr[i])); + } + return results; + }, + + parallel: function parallel(fns, callback) { + function cb(err, res) { + if (typeof callback == "function") { + callback(err, res); + callback = null; + } + } + if (fns.length == 0) { return cb(null, []); } + var remaining = fns.length, results = []; + function makeDone(num) { + return function done(err, result) { + if (err) { return cb(err); } + results[num] = result; + if (--remaining == 0) { cb(null, results); } + }; + } + for (var i = 0, l = fns.length; i < l; ++i) { + fns[i](makeDone(i)); + } + }, + + series: function series(fns, callback) { + function cb(err, res) { + if (typeof callback == "function") { + callback(err, res); + } + } + var remaining = fns.slice(); + var results = []; + function callNext() { + if (remaining.length == 0) return cb(null, results); + var promise = remaining.shift()(next); + if (promise && typeof promise.then == "function") { + promise.then(buster.partial(next, null), next); + } + } + function next(err, result) { + if (err) return cb(err); + results.push(result); + callNext(); + } + callNext(); + }, + + countdown: function countdown(num, done) { + return function () { + if (--num == 0) done(); + }; + } + }; + + if (typeof process === "object" && + typeof require === "function" && typeof module === "object") { + var crypto = require("crypto"); + var path = require("path"); + + buster.tmpFile = function (fileName) { + var hashed = crypto.createHash("sha1"); + hashed.update(fileName); + var tmpfileName = hashed.digest("hex"); + + if (process.platform == "win32") { + return path.join(process.env["TEMP"], tmpfileName); + } else { + return path.join("/tmp", tmpfileName); + } + }; + } + + if (Array.prototype.some) { + buster.some = function (arr, fn, thisp) { + return arr.some(fn, thisp); + }; + } else { + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some + buster.some = function (arr, fun, thisp) { + if (arr == null) { throw new TypeError(); } + arr = Object(arr); + var len = arr.length >>> 0; + if (typeof fun !== "function") { throw new TypeError(); } + + for (var i = 0; i < len; i++) { + if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) { + return true; + } + } + + return false; + }; + } + + if (Array.prototype.filter) { + buster.filter = function (arr, fn, thisp) { + return arr.filter(fn, thisp); + }; + } else { + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter + buster.filter = function (fn, thisp) { + if (this == null) { throw new TypeError(); } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fn != "function") { throw new TypeError(); } + + var res = []; + for (var i = 0; i < len; i++) { + if (i in t) { + var val = t[i]; // in case fun mutates this + if (fn.call(thisp, val, i, t)) { res.push(val); } + } + } + + return res; + }; + } + + if (isNode) { + module.exports = buster; + buster.eventEmitter = require("./buster-event-emitter"); + Object.defineProperty(buster, "defineVersionGetter", { + get: function () { + return require("./define-version-getter"); + } + }); + } + + return buster.extend(B || {}, buster); +}(setTimeout, buster)); +if (typeof buster === "undefined") { + var buster = {}; +} + +if (typeof module === "object" && typeof require === "function") { + buster = require("buster-core"); +} + +buster.format = buster.format || {}; +buster.format.excludeConstructors = ["Object", /^.$/]; +buster.format.quoteStrings = true; + +buster.format.ascii = (function () { + + var hasOwn = Object.prototype.hasOwnProperty; + + var specialObjects = []; + if (typeof global != "undefined") { + specialObjects.push({ obj: global, value: "[object global]" }); + } + if (typeof document != "undefined") { + specialObjects.push({ obj: document, value: "[object HTMLDocument]" }); + } + if (typeof window != "undefined") { + specialObjects.push({ obj: window, value: "[object Window]" }); + } + + function keys(object) { + var k = Object.keys && Object.keys(object) || []; + + if (k.length == 0) { + for (var prop in object) { + if (hasOwn.call(object, prop)) { + k.push(prop); + } + } + } + + return k.sort(); + } + + function isCircular(object, objects) { + if (typeof object != "object") { + return false; + } + + for (var i = 0, l = objects.length; i < l; ++i) { + if (objects[i] === object) { + return true; + } + } + + return false; + } + + function ascii(object, processed, indent) { + if (typeof object == "string") { + var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings; + return processed || quote ? '"' + object + '"' : object; + } + + if (typeof object == "function" && !(object instanceof RegExp)) { + return ascii.func(object); + } + + processed = processed || []; + + if (isCircular(object, processed)) { + return "[Circular]"; + } + + if (Object.prototype.toString.call(object) == "[object Array]") { + return ascii.array.call(this, object, processed); + } + + if (!object) { + return "" + object; + } + + if (buster.isElement(object)) { + return ascii.element(object); + } + + if (typeof object.toString == "function" && + object.toString !== Object.prototype.toString) { + return object.toString(); + } + + for (var i = 0, l = specialObjects.length; i < l; i++) { + if (object === specialObjects[i].obj) { + return specialObjects[i].value; + } + } + + return ascii.object.call(this, object, processed, indent); + } + + ascii.func = function (func) { + return "function " + buster.functionName(func) + "() {}"; + }; + + ascii.array = function (array, processed) { + processed = processed || []; + processed.push(array); + var pieces = []; + + for (var i = 0, l = array.length; i < l; ++i) { + pieces.push(ascii.call(this, array[i], processed)); + } + + return "[" + pieces.join(", ") + "]"; + }; + + ascii.object = function (object, processed, indent) { + processed = processed || []; + processed.push(object); + indent = indent || 0; + var pieces = [], properties = keys(object), prop, str, obj; + var is = ""; + var length = 3; + + for (var i = 0, l = indent; i < l; ++i) { + is += " "; + } + + for (i = 0, l = properties.length; i < l; ++i) { + prop = properties[i]; + obj = object[prop]; + + if (isCircular(obj, processed)) { + str = "[Circular]"; + } else { + str = ascii.call(this, obj, processed, indent + 2); + } + + str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; + length += str.length; + pieces.push(str); + } + + var cons = ascii.constructorName.call(this, object); + var prefix = cons ? "[" + cons + "] " : "" + + return (length + indent) > 80 ? + prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" : + prefix + "{ " + pieces.join(", ") + " }"; + }; + + ascii.element = function (element) { + var tagName = element.tagName.toLowerCase(); + var attrs = element.attributes, attribute, pairs = [], attrName; + + for (var i = 0, l = attrs.length; i < l; ++i) { + attribute = attrs.item(i); + attrName = attribute.nodeName.toLowerCase().replace("html:", ""); + + if (attrName == "contenteditable" && attribute.nodeValue == "inherit") { + continue; + } + + if (!!attribute.nodeValue) { + pairs.push(attrName + "=\"" + attribute.nodeValue + "\""); + } + } + + var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); + var content = element.innerHTML; + + if (content.length > 20) { + content = content.substr(0, 20) + "[...]"; + } + + var res = formatted + pairs.join(" ") + ">" + content + ""; + + return res.replace(/ contentEditable="inherit"/, ""); + }; + + ascii.constructorName = function (object) { + var name = buster.functionName(object && object.constructor); + var excludes = this.excludeConstructors || buster.format.excludeConstructors || []; + + for (var i = 0, l = excludes.length; i < l; ++i) { + if (typeof excludes[i] == "string" && excludes[i] == name) { + return ""; + } else if (excludes[i].test && excludes[i].test(name)) { + return ""; + } + } + + return name; + }; + + return ascii; +}()); + +if (typeof module != "undefined") { + module.exports = buster.format; +} +/*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/ +/*global module, require, __dirname, document*/ +/** + * Sinon core utilities. For internal use only. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +var sinon = (function (buster) { + var div = typeof document != "undefined" && document.createElement("div"); + var hasOwn = Object.prototype.hasOwnProperty; + + function isDOMNode(obj) { + var success = false; + + try { + obj.appendChild(div); + success = div.parentNode == obj; + } catch (e) { + return false; + } finally { + try { + obj.removeChild(div); + } catch (e) { + // Remove failed, not much we can do about that + } + } + + return success; + } + + function isElement(obj) { + return div && obj && obj.nodeType === 1 && isDOMNode(obj); + } + + function isFunction(obj) { + return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply); + } + + function mirrorProperties(target, source) { + for (var prop in source) { + if (!hasOwn.call(target, prop)) { + target[prop] = source[prop]; + } + } + } + + function isRestorable (obj) { + return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon; + } + + var sinon = { + wrapMethod: function wrapMethod(object, property, method) { + if (!object) { + throw new TypeError("Should wrap property of object"); + } + + if (typeof method != "function") { + throw new TypeError("Method wrapper should be function"); + } + + var wrappedMethod = object[property]; + + if (!isFunction(wrappedMethod)) { + throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + + property + " as function"); + } + + if (wrappedMethod.restore && wrappedMethod.restore.sinon) { + throw new TypeError("Attempted to wrap " + property + " which is already wrapped"); + } + + if (wrappedMethod.calledBefore) { + var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; + throw new TypeError("Attempted to wrap " + property + " which is already " + verb); + } + + // IE 8 does not support hasOwnProperty on the window object. + var owned = hasOwn.call(object, property); + object[property] = method; + method.displayName = property; + + method.restore = function () { + // For prototype properties try to reset by delete first. + // If this fails (ex: localStorage on mobile safari) then force a reset + // via direct assignment. + if (!owned) { + delete object[property]; + } + if (object[property] === method) { + object[property] = wrappedMethod; + } + }; + + method.restore.sinon = true; + mirrorProperties(method, wrappedMethod); + + return method; + }, + + extend: function extend(target) { + for (var i = 1, l = arguments.length; i < l; i += 1) { + for (var prop in arguments[i]) { + if (arguments[i].hasOwnProperty(prop)) { + target[prop] = arguments[i][prop]; + } + + // DONT ENUM bug, only care about toString + if (arguments[i].hasOwnProperty("toString") && + arguments[i].toString != target.toString) { + target.toString = arguments[i].toString; + } + } + } + + return target; + }, + + create: function create(proto) { + var F = function () {}; + F.prototype = proto; + return new F(); + }, + + deepEqual: function deepEqual(a, b) { + if (sinon.match && sinon.match.isMatcher(a)) { + return a.test(b); + } + if (typeof a != "object" || typeof b != "object") { + return a === b; + } + + if (isElement(a) || isElement(b)) { + return a === b; + } + + if (a === b) { + return true; + } + + if ((a === null && b !== null) || (a !== null && b === null)) { + return false; + } + + var aString = Object.prototype.toString.call(a); + if (aString != Object.prototype.toString.call(b)) { + return false; + } + + if (aString == "[object Array]") { + if (a.length !== b.length) { + return false; + } + + for (var i = 0, l = a.length; i < l; i += 1) { + if (!deepEqual(a[i], b[i])) { + return false; + } + } + + return true; + } + + var prop, aLength = 0, bLength = 0; + + for (prop in a) { + aLength += 1; + + if (!deepEqual(a[prop], b[prop])) { + return false; + } + } + + for (prop in b) { + bLength += 1; + } + + return aLength == bLength; + }, + + functionName: function functionName(func) { + var name = func.displayName || func.name; + + // Use function decomposition as a last resort to get function + // name. Does not rely on function decomposition to work - if it + // doesn't debugging will be slightly less informative + // (i.e. toString will say 'spy' rather than 'myFunc'). + if (!name) { + var matches = func.toString().match(/function ([^\s\(]+)/); + name = matches && matches[1]; + } + + return name; + }, + + functionToString: function toString() { + if (this.getCall && this.callCount) { + var thisValue, prop, i = this.callCount; + + while (i--) { + thisValue = this.getCall(i).thisValue; + + for (prop in thisValue) { + if (thisValue[prop] === this) { + return prop; + } + } + } + } + + return this.displayName || "sinon fake"; + }, + + getConfig: function (custom) { + var config = {}; + custom = custom || {}; + var defaults = sinon.defaultConfig; + + for (var prop in defaults) { + if (defaults.hasOwnProperty(prop)) { + config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop]; + } + } + + return config; + }, + + format: function (val) { + return "" + val; + }, + + defaultConfig: { + injectIntoThis: true, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "server", "requests"], + useFakeTimers: true, + useFakeServer: true + }, + + timesInWords: function timesInWords(count) { + return count == 1 && "once" || + count == 2 && "twice" || + count == 3 && "thrice" || + (count || 0) + " times"; + }, + + calledInOrder: function (spies) { + for (var i = 1, l = spies.length; i < l; i++) { + if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) { + return false; + } + } + + return true; + }, + + orderByFirstCall: function (spies) { + return spies.sort(function (a, b) { + // uuid, won't ever be equal + var aCall = a.getCall(0); + var bCall = b.getCall(0); + var aId = aCall && aCall.callId || -1; + var bId = bCall && bCall.callId || -1; + + return aId < bId ? -1 : 1; + }); + }, + + log: function () {}, + + logError: function (label, err) { + var msg = label + " threw exception: " + sinon.log(msg + "[" + err.name + "] " + err.message); + if (err.stack) { sinon.log(err.stack); } + + setTimeout(function () { + err.message = msg + err.message; + throw err; + }, 0); + }, + + typeOf: function (value) { + if (value === null) { + return "null"; + } + else if (value === undefined) { + return "undefined"; + } + var string = Object.prototype.toString.call(value); + return string.substring(8, string.length - 1).toLowerCase(); + }, + + createStubInstance: function (constructor) { + if (typeof constructor !== "function") { + throw new TypeError("The constructor should be a function."); + } + return sinon.stub(sinon.create(constructor.prototype)); + }, + + restore: function (object) { + if (object !== null && typeof object === "object") { + for (var prop in object) { + if (isRestorable(object[prop])) { + object[prop].restore(); + } + } + } + else if (isRestorable(object)) { + object.restore(); + } + } + }; + + var isNode = typeof module == "object" && typeof require == "function"; + + if (isNode) { + try { + buster = { format: require("buster-format") }; + } catch (e) {} + module.exports = sinon; + module.exports.spy = require("./sinon/spy"); + module.exports.spyCall = require("./sinon/call"); + module.exports.stub = require("./sinon/stub"); + module.exports.mock = require("./sinon/mock"); + module.exports.collection = require("./sinon/collection"); + module.exports.assert = require("./sinon/assert"); + module.exports.sandbox = require("./sinon/sandbox"); + module.exports.test = require("./sinon/test"); + module.exports.testCase = require("./sinon/test_case"); + module.exports.assert = require("./sinon/assert"); + module.exports.match = require("./sinon/match"); + } + + if (buster) { + var formatter = sinon.create(buster.format); + formatter.quoteStrings = false; + sinon.format = function () { + return formatter.ascii.apply(formatter, arguments); + }; + } else if (isNode) { + try { + var util = require("util"); + sinon.format = function (value) { + return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value; + }; + } catch (e) { + /* Node, but no util module - would be very old, but better safe than + sorry */ + } + } + + return sinon; +}(typeof buster == "object" && buster)); + +/* @depend ../sinon.js */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Match functions + * + * @author Maximilian Antoni (mail@maxantoni.de) + * @license BSD + * + * Copyright (c) 2012 Maximilian Antoni + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function assertType(value, type, name) { + var actual = sinon.typeOf(value); + if (actual !== type) { + throw new TypeError("Expected type of " + name + " to be " + + type + ", but was " + actual); + } + } + + var matcher = { + toString: function () { + return this.message; + } + }; + + function isMatcher(object) { + return matcher.isPrototypeOf(object); + } + + function matchObject(expectation, actual) { + if (actual === null || actual === undefined) { + return false; + } + for (var key in expectation) { + if (expectation.hasOwnProperty(key)) { + var exp = expectation[key]; + var act = actual[key]; + if (match.isMatcher(exp)) { + if (!exp.test(act)) { + return false; + } + } else if (sinon.typeOf(exp) === "object") { + if (!matchObject(exp, act)) { + return false; + } + } else if (!sinon.deepEqual(exp, act)) { + return false; + } + } + } + return true; + } + + matcher.or = function (m2) { + if (!isMatcher(m2)) { + throw new TypeError("Matcher expected"); + } + var m1 = this; + var or = sinon.create(matcher); + or.test = function (actual) { + return m1.test(actual) || m2.test(actual); + }; + or.message = m1.message + ".or(" + m2.message + ")"; + return or; + }; + + matcher.and = function (m2) { + if (!isMatcher(m2)) { + throw new TypeError("Matcher expected"); + } + var m1 = this; + var and = sinon.create(matcher); + and.test = function (actual) { + return m1.test(actual) && m2.test(actual); + }; + and.message = m1.message + ".and(" + m2.message + ")"; + return and; + }; + + var match = function (expectation, message) { + var m = sinon.create(matcher); + var type = sinon.typeOf(expectation); + switch (type) { + case "object": + if (typeof expectation.test === "function") { + m.test = function (actual) { + return expectation.test(actual) === true; + }; + m.message = "match(" + sinon.functionName(expectation.test) + ")"; + return m; + } + var str = []; + for (var key in expectation) { + if (expectation.hasOwnProperty(key)) { + str.push(key + ": " + expectation[key]); + } + } + m.test = function (actual) { + return matchObject(expectation, actual); + }; + m.message = "match(" + str.join(", ") + ")"; + break; + case "number": + m.test = function (actual) { + return expectation == actual; + }; + break; + case "string": + m.test = function (actual) { + if (typeof actual !== "string") { + return false; + } + return actual.indexOf(expectation) !== -1; + }; + m.message = "match(\"" + expectation + "\")"; + break; + case "regexp": + m.test = function (actual) { + if (typeof actual !== "string") { + return false; + } + return expectation.test(actual); + }; + break; + case "function": + m.test = expectation; + if (message) { + m.message = message; + } else { + m.message = "match(" + sinon.functionName(expectation) + ")"; + } + break; + default: + m.test = function (actual) { + return sinon.deepEqual(expectation, actual); + }; + } + if (!m.message) { + m.message = "match(" + expectation + ")"; + } + return m; + }; + + match.isMatcher = isMatcher; + + match.any = match(function () { + return true; + }, "any"); + + match.defined = match(function (actual) { + return actual !== null && actual !== undefined; + }, "defined"); + + match.truthy = match(function (actual) { + return !!actual; + }, "truthy"); + + match.falsy = match(function (actual) { + return !actual; + }, "falsy"); + + match.same = function (expectation) { + return match(function (actual) { + return expectation === actual; + }, "same(" + expectation + ")"); + }; + + match.typeOf = function (type) { + assertType(type, "string", "type"); + return match(function (actual) { + return sinon.typeOf(actual) === type; + }, "typeOf(\"" + type + "\")"); + }; + + match.instanceOf = function (type) { + assertType(type, "function", "type"); + return match(function (actual) { + return actual instanceof type; + }, "instanceOf(" + sinon.functionName(type) + ")"); + }; + + function createPropertyMatcher(propertyTest, messagePrefix) { + return function (property, value) { + assertType(property, "string", "property"); + var onlyProperty = arguments.length === 1; + var message = messagePrefix + "(\"" + property + "\""; + if (!onlyProperty) { + message += ", " + value; + } + message += ")"; + return match(function (actual) { + if (actual === undefined || actual === null || + !propertyTest(actual, property)) { + return false; + } + return onlyProperty || sinon.deepEqual(value, actual[property]); + }, message); + }; + } + + match.has = createPropertyMatcher(function (actual, property) { + if (typeof actual === "object") { + return property in actual; + } + return actual[property] !== undefined; + }, "has"); + + match.hasOwn = createPropertyMatcher(function (actual, property) { + return actual.hasOwnProperty(property); + }, "hasOwn"); + + match.bool = match.typeOf("boolean"); + match.number = match.typeOf("number"); + match.string = match.typeOf("string"); + match.object = match.typeOf("object"); + match.func = match.typeOf("function"); + match.array = match.typeOf("array"); + match.regexp = match.typeOf("regexp"); + match.date = match.typeOf("date"); + + if (commonJSModule) { + module.exports = match; + } else { + sinon.match = match; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend match.js + */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Spy calls + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Maximilian Antoni (mail@maxantoni.de) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + * Copyright (c) 2013 Maximilian Antoni + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function throwYieldError(proxy, text, args) { + var msg = sinon.functionName(proxy) + text; + if (args.length) { + msg += " Received [" + slice.call(args).join(", ") + "]"; + } + throw new Error(msg); + } + + var slice = Array.prototype.slice; + + var callProto = { + calledOn: function calledOn(thisValue) { + if (sinon.match && sinon.match.isMatcher(thisValue)) { + return thisValue.test(this.thisValue); + } + return this.thisValue === thisValue; + }, + + calledWith: function calledWith() { + for (var i = 0, l = arguments.length; i < l; i += 1) { + if (!sinon.deepEqual(arguments[i], this.args[i])) { + return false; + } + } + + return true; + }, + + calledWithMatch: function calledWithMatch() { + for (var i = 0, l = arguments.length; i < l; i += 1) { + var actual = this.args[i]; + var expectation = arguments[i]; + if (!sinon.match || !sinon.match(expectation).test(actual)) { + return false; + } + } + return true; + }, + + calledWithExactly: function calledWithExactly() { + return arguments.length == this.args.length && + this.calledWith.apply(this, arguments); + }, + + notCalledWith: function notCalledWith() { + return !this.calledWith.apply(this, arguments); + }, + + notCalledWithMatch: function notCalledWithMatch() { + return !this.calledWithMatch.apply(this, arguments); + }, + + returned: function returned(value) { + return sinon.deepEqual(value, this.returnValue); + }, + + threw: function threw(error) { + if (typeof error === "undefined" || !this.exception) { + return !!this.exception; + } + + return this.exception === error || this.exception.name === error; + }, + + calledWithNew: function calledWithNew(thisValue) { + return this.thisValue instanceof this.proxy; + }, + + calledBefore: function (other) { + return this.callId < other.callId; + }, + + calledAfter: function (other) { + return this.callId > other.callId; + }, + + callArg: function (pos) { + this.args[pos](); + }, + + callArgOn: function (pos, thisValue) { + this.args[pos].apply(thisValue); + }, + + callArgWith: function (pos) { + this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1))); + }, + + callArgOnWith: function (pos, thisValue) { + var args = slice.call(arguments, 2); + this.args[pos].apply(thisValue, args); + }, + + "yield": function () { + this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0))); + }, + + yieldOn: function (thisValue) { + var args = this.args; + for (var i = 0, l = args.length; i < l; ++i) { + if (typeof args[i] === "function") { + args[i].apply(thisValue, slice.call(arguments, 1)); + return; + } + } + throwYieldError(this.proxy, " cannot yield since no callback was passed.", args); + }, + + yieldTo: function (prop) { + this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1))); + }, + + yieldToOn: function (prop, thisValue) { + var args = this.args; + for (var i = 0, l = args.length; i < l; ++i) { + if (args[i] && typeof args[i][prop] === "function") { + args[i][prop].apply(thisValue, slice.call(arguments, 2)); + return; + } + } + throwYieldError(this.proxy, " cannot yield to '" + prop + + "' since no callback was passed.", args); + }, + + toString: function () { + var callStr = this.proxy.toString() + "("; + var args = []; + + for (var i = 0, l = this.args.length; i < l; ++i) { + args.push(sinon.format(this.args[i])); + } + + callStr = callStr + args.join(", ") + ")"; + + if (typeof this.returnValue != "undefined") { + callStr += " => " + sinon.format(this.returnValue); + } + + if (this.exception) { + callStr += " !" + this.exception.name; + + if (this.exception.message) { + callStr += "(" + this.exception.message + ")"; + } + } + + return callStr; + } + }; + + callProto.invokeCallback = callProto.yield; + + function createSpyCall(spy, thisValue, args, returnValue, exception, id) { + if (typeof id !== "number") { + throw new TypeError("Call id is not a number"); + } + var proxyCall = sinon.create(callProto); + proxyCall.proxy = spy; + proxyCall.thisValue = thisValue; + proxyCall.args = args; + proxyCall.returnValue = returnValue; + proxyCall.exception = exception; + proxyCall.callId = id; + + return proxyCall; + }; + createSpyCall.toString = callProto.toString; // used by mocks + + if (commonJSModule) { + module.exports = createSpyCall; + } else { + sinon.spyCall = createSpyCall; + } +}(typeof sinon == "object" && sinon || null)); + + +/** + * @depend ../sinon.js + * @depend call.js + */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Spy functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = Array.prototype.push; + var slice = Array.prototype.slice; + var callId = 0; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function spy(object, property) { + if (!property && typeof object == "function") { + return spy.create(object); + } + + if (!object && !property) { + return spy.create(function () { }); + } + + var method = object[property]; + return sinon.wrapMethod(object, property, spy.create(method)); + } + + function matchingFake(fakes, args, strict) { + if (!fakes) { + return; + } + + var alen = args.length; + + for (var i = 0, l = fakes.length; i < l; i++) { + if (fakes[i].matches(args, strict)) { + return fakes[i]; + } + } + } + + function incrementCallCount() { + this.called = true; + this.callCount += 1; + this.notCalled = false; + this.calledOnce = this.callCount == 1; + this.calledTwice = this.callCount == 2; + this.calledThrice = this.callCount == 3; + } + + function createCallProperties() { + this.firstCall = this.getCall(0); + this.secondCall = this.getCall(1); + this.thirdCall = this.getCall(2); + this.lastCall = this.getCall(this.callCount - 1); + } + + var vars = "a,b,c,d,e,f,g,h,i,j,k,l"; + function createProxy(func) { + // Retain the function length: + var p; + if (func.length) { + eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) + + ") { return p.invoke(func, this, slice.call(arguments)); });"); + } + else { + p = function proxy() { + return p.invoke(func, this, slice.call(arguments)); + }; + } + return p; + } + + var uuid = 0; + + // Public API + var spyApi = { + reset: function () { + this.called = false; + this.notCalled = true; + this.calledOnce = false; + this.calledTwice = false; + this.calledThrice = false; + this.callCount = 0; + this.firstCall = null; + this.secondCall = null; + this.thirdCall = null; + this.lastCall = null; + this.args = []; + this.returnValues = []; + this.thisValues = []; + this.exceptions = []; + this.callIds = []; + if (this.fakes) { + for (var i = 0; i < this.fakes.length; i++) { + this.fakes[i].reset(); + } + } + }, + + create: function create(func) { + var name; + + if (typeof func != "function") { + func = function () { }; + } else { + name = sinon.functionName(func); + } + + var proxy = createProxy(func); + + sinon.extend(proxy, spy); + delete proxy.create; + sinon.extend(proxy, func); + + proxy.reset(); + proxy.prototype = func.prototype; + proxy.displayName = name || "spy"; + proxy.toString = sinon.functionToString; + proxy._create = sinon.spy.create; + proxy.id = "spy#" + uuid++; + + return proxy; + }, + + invoke: function invoke(func, thisValue, args) { + var matching = matchingFake(this.fakes, args); + var exception, returnValue; + + incrementCallCount.call(this); + push.call(this.thisValues, thisValue); + push.call(this.args, args); + push.call(this.callIds, callId++); + + try { + if (matching) { + returnValue = matching.invoke(func, thisValue, args); + } else { + returnValue = (this.func || func).apply(thisValue, args); + } + } catch (e) { + push.call(this.returnValues, undefined); + exception = e; + throw e; + } finally { + push.call(this.exceptions, exception); + } + + push.call(this.returnValues, returnValue); + + createCallProperties.call(this); + + return returnValue; + }, + + getCall: function getCall(i) { + if (i < 0 || i >= this.callCount) { + return null; + } + + return sinon.spyCall(this, this.thisValues[i], this.args[i], + this.returnValues[i], this.exceptions[i], + this.callIds[i]); + }, + + calledBefore: function calledBefore(spyFn) { + if (!this.called) { + return false; + } + + if (!spyFn.called) { + return true; + } + + return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1]; + }, + + calledAfter: function calledAfter(spyFn) { + if (!this.called || !spyFn.called) { + return false; + } + + return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1]; + }, + + withArgs: function () { + var args = slice.call(arguments); + + if (this.fakes) { + var match = matchingFake(this.fakes, args, true); + + if (match) { + return match; + } + } else { + this.fakes = []; + } + + var original = this; + var fake = this._create(); + fake.matchingAguments = args; + push.call(this.fakes, fake); + + fake.withArgs = function () { + return original.withArgs.apply(original, arguments); + }; + + for (var i = 0; i < this.args.length; i++) { + if (fake.matches(this.args[i])) { + incrementCallCount.call(fake); + push.call(fake.thisValues, this.thisValues[i]); + push.call(fake.args, this.args[i]); + push.call(fake.returnValues, this.returnValues[i]); + push.call(fake.exceptions, this.exceptions[i]); + push.call(fake.callIds, this.callIds[i]); + } + } + createCallProperties.call(fake); + + return fake; + }, + + matches: function (args, strict) { + var margs = this.matchingAguments; + + if (margs.length <= args.length && + sinon.deepEqual(margs, args.slice(0, margs.length))) { + return !strict || margs.length == args.length; + } + }, + + printf: function (format) { + var spy = this; + var args = slice.call(arguments, 1); + var formatter; + + return (format || "").replace(/%(.)/g, function (match, specifyer) { + formatter = spyApi.formatters[specifyer]; + + if (typeof formatter == "function") { + return formatter.call(null, spy, args); + } else if (!isNaN(parseInt(specifyer), 10)) { + return sinon.format(args[specifyer - 1]); + } + + return "%" + specifyer; + }); + } + }; + + function delegateToCalls(method, matchAny, actual, notCalled) { + spyApi[method] = function () { + if (!this.called) { + if (notCalled) { + return notCalled.apply(this, arguments); + } + return false; + } + + var currentCall; + var matches = 0; + + for (var i = 0, l = this.callCount; i < l; i += 1) { + currentCall = this.getCall(i); + + if (currentCall[actual || method].apply(currentCall, arguments)) { + matches += 1; + + if (matchAny) { + return true; + } + } + } + + return matches === this.callCount; + }; + } + + delegateToCalls("calledOn", true); + delegateToCalls("alwaysCalledOn", false, "calledOn"); + delegateToCalls("calledWith", true); + delegateToCalls("calledWithMatch", true); + delegateToCalls("alwaysCalledWith", false, "calledWith"); + delegateToCalls("alwaysCalledWithMatch", false, "calledWithMatch"); + delegateToCalls("calledWithExactly", true); + delegateToCalls("alwaysCalledWithExactly", false, "calledWithExactly"); + delegateToCalls("neverCalledWith", false, "notCalledWith", + function () { return true; }); + delegateToCalls("neverCalledWithMatch", false, "notCalledWithMatch", + function () { return true; }); + delegateToCalls("threw", true); + delegateToCalls("alwaysThrew", false, "threw"); + delegateToCalls("returned", true); + delegateToCalls("alwaysReturned", false, "returned"); + delegateToCalls("calledWithNew", true); + delegateToCalls("alwaysCalledWithNew", false, "calledWithNew"); + delegateToCalls("callArg", false, "callArgWith", function () { + throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); + }); + spyApi.callArgWith = spyApi.callArg; + delegateToCalls("callArgOn", false, "callArgOnWith", function () { + throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); + }); + spyApi.callArgOnWith = spyApi.callArgOn; + delegateToCalls("yield", false, "yield", function () { + throw new Error(this.toString() + " cannot yield since it was not yet invoked."); + }); + // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode. + spyApi.invokeCallback = spyApi.yield; + delegateToCalls("yieldOn", false, "yieldOn", function () { + throw new Error(this.toString() + " cannot yield since it was not yet invoked."); + }); + delegateToCalls("yieldTo", false, "yieldTo", function (property) { + throw new Error(this.toString() + " cannot yield to '" + property + + "' since it was not yet invoked."); + }); + delegateToCalls("yieldToOn", false, "yieldToOn", function (property) { + throw new Error(this.toString() + " cannot yield to '" + property + + "' since it was not yet invoked."); + }); + + spyApi.formatters = { + "c": function (spy) { + return sinon.timesInWords(spy.callCount); + }, + + "n": function (spy) { + return spy.toString(); + }, + + "C": function (spy) { + var calls = []; + + for (var i = 0, l = spy.callCount; i < l; ++i) { + var stringifiedCall = " " + spy.getCall(i).toString(); + if (/\n/.test(calls[i - 1])) { + stringifiedCall = "\n" + stringifiedCall; + } + push.call(calls, stringifiedCall); + } + + return calls.length > 0 ? "\n" + calls.join("\n") : ""; + }, + + "t": function (spy) { + var objects = []; + + for (var i = 0, l = spy.callCount; i < l; ++i) { + push.call(objects, sinon.format(spy.thisValues[i])); + } + + return objects.join(", "); + }, + + "*": function (spy, args) { + var formatted = []; + + for (var i = 0, l = args.length; i < l; ++i) { + push.call(formatted, sinon.format(args[i])); + } + + return formatted.join(", "); + } + }; + + sinon.extend(spy, spyApi); + + spy.spyCall = sinon.spyCall; + + if (commonJSModule) { + module.exports = spy; + } else { + sinon.spy = spy; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend spy.js + */ +/*jslint eqeqeq: false, onevar: false*/ +/*global module, require, sinon*/ +/** + * Stub functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function stub(object, property, func) { + if (!!func && typeof func != "function") { + throw new TypeError("Custom stub should be function"); + } + + var wrapper; + + if (func) { + wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; + } else { + wrapper = stub.create(); + } + + if (!object && !property) { + return sinon.stub.create(); + } + + if (!property && !!object && typeof object == "object") { + for (var prop in object) { + if (typeof object[prop] === "function") { + stub(object, prop); + } + } + + return object; + } + + return sinon.wrapMethod(object, property, wrapper); + } + + function getChangingValue(stub, property) { + var index = stub.callCount - 1; + var values = stub[property]; + var prop = index in values ? values[index] : values[values.length - 1]; + stub[property + "Last"] = prop; + + return prop; + } + + function getCallback(stub, args) { + var callArgAt = getChangingValue(stub, "callArgAts"); + + if (callArgAt < 0) { + var callArgProp = getChangingValue(stub, "callArgProps"); + + for (var i = 0, l = args.length; i < l; ++i) { + if (!callArgProp && typeof args[i] == "function") { + return args[i]; + } + + if (callArgProp && args[i] && + typeof args[i][callArgProp] == "function") { + return args[i][callArgProp]; + } + } + + return null; + } + + return args[callArgAt]; + } + + var join = Array.prototype.join; + + function getCallbackError(stub, func, args) { + if (stub.callArgAtsLast < 0) { + var msg; + + if (stub.callArgPropsLast) { + msg = sinon.functionName(stub) + + " expected to yield to '" + stub.callArgPropsLast + + "', but no object with such a property was passed." + } else { + msg = sinon.functionName(stub) + + " expected to yield, but no callback was passed." + } + + if (args.length > 0) { + msg += " Received [" + join.call(args, ", ") + "]"; + } + + return msg; + } + + return "argument at index " + stub.callArgAtsLast + " is not a function: " + func; + } + + var nextTick = (function () { + if (typeof process === "object" && typeof process.nextTick === "function") { + return process.nextTick; + } else if (typeof setImmediate === "function") { + return setImmediate; + } else { + return function (callback) { + setTimeout(callback, 0); + }; + } + })(); + + function callCallback(stub, args) { + if (stub.callArgAts.length > 0) { + var func = getCallback(stub, args); + + if (typeof func != "function") { + throw new TypeError(getCallbackError(stub, func, args)); + } + + var callbackArguments = getChangingValue(stub, "callbackArguments"); + var callbackContext = getChangingValue(stub, "callbackContexts"); + + if (stub.callbackAsync) { + nextTick(function() { + func.apply(callbackContext, callbackArguments); + }); + } else { + func.apply(callbackContext, callbackArguments); + } + } + } + + var uuid = 0; + + sinon.extend(stub, (function () { + var slice = Array.prototype.slice, proto; + + function throwsException(error, message) { + if (typeof error == "string") { + this.exception = new Error(message || ""); + this.exception.name = error; + } else if (!error) { + this.exception = new Error("Error"); + } else { + this.exception = error; + } + + return this; + } + + proto = { + create: function create() { + var functionStub = function () { + + callCallback(functionStub, arguments); + + if (functionStub.exception) { + throw functionStub.exception; + } else if (typeof functionStub.returnArgAt == 'number') { + return arguments[functionStub.returnArgAt]; + } else if (functionStub.returnThis) { + return this; + } + return functionStub.returnValue; + }; + + functionStub.id = "stub#" + uuid++; + var orig = functionStub; + functionStub = sinon.spy.create(functionStub); + functionStub.func = orig; + + functionStub.callArgAts = []; + functionStub.callbackArguments = []; + functionStub.callbackContexts = []; + functionStub.callArgProps = []; + + sinon.extend(functionStub, stub); + functionStub._create = sinon.stub.create; + functionStub.displayName = "stub"; + functionStub.toString = sinon.functionToString; + + return functionStub; + }, + + resetBehavior: function () { + var i; + + this.callArgAts = []; + this.callbackArguments = []; + this.callbackContexts = []; + this.callArgProps = []; + + delete this.returnValue; + delete this.returnArgAt; + this.returnThis = false; + + if (this.fakes) { + for (i = 0; i < this.fakes.length; i++) { + this.fakes[i].resetBehavior(); + } + } + }, + + returns: function returns(value) { + this.returnValue = value; + + return this; + }, + + returnsArg: function returnsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.returnArgAt = pos; + + return this; + }, + + returnsThis: function returnsThis() { + this.returnThis = true; + + return this; + }, + + "throws": throwsException, + throwsException: throwsException, + + callsArg: function callsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push([]); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgOn: function callsArgOn(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push([]); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgWith: function callsArgWith(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgOnWith: function callsArgWith(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push(slice.call(arguments, 2)); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + yields: function () { + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 0)); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + yieldsOn: function (context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + yieldsTo: function (prop) { + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(undefined); + this.callArgProps.push(prop); + + return this; + }, + + yieldsToOn: function (prop, context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 2)); + this.callbackContexts.push(context); + this.callArgProps.push(prop); + + return this; + } + }; + + // create asynchronous versions of callsArg* and yields* methods + for (var method in proto) { + // need to avoid creating anotherasync versions of the newly added async methods + if (proto.hasOwnProperty(method) && + method.match(/^(callsArg|yields|thenYields$)/) && + !method.match(/Async/)) { + proto[method + 'Async'] = (function (syncFnName) { + return function () { + this.callbackAsync = true; + return this[syncFnName].apply(this, arguments); + }; + })(method); + } + } + + return proto; + + }())); + + if (commonJSModule) { + module.exports = stub; + } else { + sinon.stub = stub; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend stub.js + */ +/*jslint eqeqeq: false, onevar: false, nomen: false*/ +/*global module, require, sinon*/ +/** + * Mock functions. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = [].push; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function mock(object) { + if (!object) { + return sinon.expectation.create("Anonymous mock"); + } + + return mock.create(object); + } + + sinon.mock = mock; + + sinon.extend(mock, (function () { + function each(collection, callback) { + if (!collection) { + return; + } + + for (var i = 0, l = collection.length; i < l; i += 1) { + callback(collection[i]); + } + } + + return { + create: function create(object) { + if (!object) { + throw new TypeError("object is null"); + } + + var mockObject = sinon.extend({}, mock); + mockObject.object = object; + delete mockObject.create; + + return mockObject; + }, + + expects: function expects(method) { + if (!method) { + throw new TypeError("method is falsy"); + } + + if (!this.expectations) { + this.expectations = {}; + this.proxies = []; + } + + if (!this.expectations[method]) { + this.expectations[method] = []; + var mockObject = this; + + sinon.wrapMethod(this.object, method, function () { + return mockObject.invokeMethod(method, this, arguments); + }); + + push.call(this.proxies, method); + } + + var expectation = sinon.expectation.create(method); + push.call(this.expectations[method], expectation); + + return expectation; + }, + + restore: function restore() { + var object = this.object; + + each(this.proxies, function (proxy) { + if (typeof object[proxy].restore == "function") { + object[proxy].restore(); + } + }); + }, + + verify: function verify() { + var expectations = this.expectations || {}; + var messages = [], met = []; + + each(this.proxies, function (proxy) { + each(expectations[proxy], function (expectation) { + if (!expectation.met()) { + push.call(messages, expectation.toString()); + } else { + push.call(met, expectation.toString()); + } + }); + }); + + this.restore(); + + if (messages.length > 0) { + sinon.expectation.fail(messages.concat(met).join("\n")); + } else { + sinon.expectation.pass(messages.concat(met).join("\n")); + } + + return true; + }, + + invokeMethod: function invokeMethod(method, thisValue, args) { + var expectations = this.expectations && this.expectations[method]; + var length = expectations && expectations.length || 0, i; + + for (i = 0; i < length; i += 1) { + if (!expectations[i].met() && + expectations[i].allowsCall(thisValue, args)) { + return expectations[i].apply(thisValue, args); + } + } + + var messages = [], available, exhausted = 0; + + for (i = 0; i < length; i += 1) { + if (expectations[i].allowsCall(thisValue, args)) { + available = available || expectations[i]; + } else { + exhausted += 1; + } + push.call(messages, " " + expectations[i].toString()); + } + + if (exhausted === 0) { + return available.apply(thisValue, args); + } + + messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({ + proxy: method, + args: args + })); + + sinon.expectation.fail(messages.join("\n")); + } + }; + }())); + + var times = sinon.timesInWords; + + sinon.expectation = (function () { + var slice = Array.prototype.slice; + var _invoke = sinon.spy.invoke; + + function callCountInWords(callCount) { + if (callCount == 0) { + return "never called"; + } else { + return "called " + times(callCount); + } + } + + function expectedCallCountInWords(expectation) { + var min = expectation.minCalls; + var max = expectation.maxCalls; + + if (typeof min == "number" && typeof max == "number") { + var str = times(min); + + if (min != max) { + str = "at least " + str + " and at most " + times(max); + } + + return str; + } + + if (typeof min == "number") { + return "at least " + times(min); + } + + return "at most " + times(max); + } + + function receivedMinCalls(expectation) { + var hasMinLimit = typeof expectation.minCalls == "number"; + return !hasMinLimit || expectation.callCount >= expectation.minCalls; + } + + function receivedMaxCalls(expectation) { + if (typeof expectation.maxCalls != "number") { + return false; + } + + return expectation.callCount == expectation.maxCalls; + } + + return { + minCalls: 1, + maxCalls: 1, + + create: function create(methodName) { + var expectation = sinon.extend(sinon.stub.create(), sinon.expectation); + delete expectation.create; + expectation.method = methodName; + + return expectation; + }, + + invoke: function invoke(func, thisValue, args) { + this.verifyCallAllowed(thisValue, args); + + return _invoke.apply(this, arguments); + }, + + atLeast: function atLeast(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not number"); + } + + if (!this.limitsSet) { + this.maxCalls = null; + this.limitsSet = true; + } + + this.minCalls = num; + + return this; + }, + + atMost: function atMost(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not number"); + } + + if (!this.limitsSet) { + this.minCalls = null; + this.limitsSet = true; + } + + this.maxCalls = num; + + return this; + }, + + never: function never() { + return this.exactly(0); + }, + + once: function once() { + return this.exactly(1); + }, + + twice: function twice() { + return this.exactly(2); + }, + + thrice: function thrice() { + return this.exactly(3); + }, + + exactly: function exactly(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not a number"); + } + + this.atLeast(num); + return this.atMost(num); + }, + + met: function met() { + return !this.failed && receivedMinCalls(this); + }, + + verifyCallAllowed: function verifyCallAllowed(thisValue, args) { + if (receivedMaxCalls(this)) { + this.failed = true; + sinon.expectation.fail(this.method + " already called " + times(this.maxCalls)); + } + + if ("expectedThis" in this && this.expectedThis !== thisValue) { + sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " + + this.expectedThis); + } + + if (!("expectedArguments" in this)) { + return; + } + + if (!args) { + sinon.expectation.fail(this.method + " received no arguments, expected " + + sinon.format(this.expectedArguments)); + } + + if (args.length < this.expectedArguments.length) { + sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) + + "), expected " + sinon.format(this.expectedArguments)); + } + + if (this.expectsExactArgCount && + args.length != this.expectedArguments.length) { + sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) + + "), expected " + sinon.format(this.expectedArguments)); + } + + for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { + sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + + ", expected " + sinon.format(this.expectedArguments)); + } + } + }, + + allowsCall: function allowsCall(thisValue, args) { + if (this.met() && receivedMaxCalls(this)) { + return false; + } + + if ("expectedThis" in this && this.expectedThis !== thisValue) { + return false; + } + + if (!("expectedArguments" in this)) { + return true; + } + + args = args || []; + + if (args.length < this.expectedArguments.length) { + return false; + } + + if (this.expectsExactArgCount && + args.length != this.expectedArguments.length) { + return false; + } + + for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { + return false; + } + } + + return true; + }, + + withArgs: function withArgs() { + this.expectedArguments = slice.call(arguments); + return this; + }, + + withExactArgs: function withExactArgs() { + this.withArgs.apply(this, arguments); + this.expectsExactArgCount = true; + return this; + }, + + on: function on(thisValue) { + this.expectedThis = thisValue; + return this; + }, + + toString: function () { + var args = (this.expectedArguments || []).slice(); + + if (!this.expectsExactArgCount) { + push.call(args, "[...]"); + } + + var callStr = sinon.spyCall.toString.call({ + proxy: this.method || "anonymous mock expectation", + args: args + }); + + var message = callStr.replace(", [...", "[, ...") + " " + + expectedCallCountInWords(this); + + if (this.met()) { + return "Expectation met: " + message; + } + + return "Expected " + message + " (" + + callCountInWords(this.callCount) + ")"; + }, + + verify: function verify() { + if (!this.met()) { + sinon.expectation.fail(this.toString()); + } else { + sinon.expectation.pass(this.toString()); + } + + return true; + }, + + pass: function(message) { + sinon.assert.pass(message); + }, + fail: function (message) { + var exception = new Error(message); + exception.name = "ExpectationError"; + + throw exception; + } + }; + }()); + + if (commonJSModule) { + module.exports = mock; + } else { + sinon.mock = mock; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend stub.js + * @depend mock.js + */ +/*jslint eqeqeq: false, onevar: false, forin: true*/ +/*global module, require, sinon*/ +/** + * Collections of stubs, spies and mocks. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = [].push; + var hasOwnProperty = Object.prototype.hasOwnProperty; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function getFakes(fakeCollection) { + if (!fakeCollection.fakes) { + fakeCollection.fakes = []; + } + + return fakeCollection.fakes; + } + + function each(fakeCollection, method) { + var fakes = getFakes(fakeCollection); + + for (var i = 0, l = fakes.length; i < l; i += 1) { + if (typeof fakes[i][method] == "function") { + fakes[i][method](); + } + } + } + + function compact(fakeCollection) { + var fakes = getFakes(fakeCollection); + var i = 0; + while (i < fakes.length) { + fakes.splice(i, 1); + } + } + + var collection = { + verify: function resolve() { + each(this, "verify"); + }, + + restore: function restore() { + each(this, "restore"); + compact(this); + }, + + verifyAndRestore: function verifyAndRestore() { + var exception; + + try { + this.verify(); + } catch (e) { + exception = e; + } + + this.restore(); + + if (exception) { + throw exception; + } + }, + + add: function add(fake) { + push.call(getFakes(this), fake); + return fake; + }, + + spy: function spy() { + return this.add(sinon.spy.apply(sinon, arguments)); + }, + + stub: function stub(object, property, value) { + if (property) { + var original = object[property]; + + if (typeof original != "function") { + if (!hasOwnProperty.call(object, property)) { + throw new TypeError("Cannot stub non-existent own property " + property); + } + + object[property] = value; + + return this.add({ + restore: function () { + object[property] = original; + } + }); + } + } + if (!property && !!object && typeof object == "object") { + var stubbedObj = sinon.stub.apply(sinon, arguments); + + for (var prop in stubbedObj) { + if (typeof stubbedObj[prop] === "function") { + this.add(stubbedObj[prop]); + } + } + + return stubbedObj; + } + + return this.add(sinon.stub.apply(sinon, arguments)); + }, + + mock: function mock() { + return this.add(sinon.mock.apply(sinon, arguments)); + }, + + inject: function inject(obj) { + var col = this; + + obj.spy = function () { + return col.spy.apply(col, arguments); + }; + + obj.stub = function () { + return col.stub.apply(col, arguments); + }; + + obj.mock = function () { + return col.mock.apply(col, arguments); + }; + + return obj; + } + }; + + if (commonJSModule) { + module.exports = collection; + } else { + sinon.collection = collection; + } +}(typeof sinon == "object" && sinon || null)); + +/*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/ +/*global module, require, window*/ +/** + * Fake timer API + * setTimeout + * setInterval + * clearTimeout + * clearInterval + * tick + * reset + * Date + * + * Inspired by jsUnitMockTimeOut from JsUnit + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +if (typeof sinon == "undefined") { + var sinon = {}; +} + +(function (global) { + var id = 1; + + function addTimer(args, recurring) { + if (args.length === 0) { + throw new Error("Function requires at least 1 parameter"); + } + + var toId = id++; + var delay = args[1] || 0; + + if (!this.timeouts) { + this.timeouts = {}; + } + + this.timeouts[toId] = { + id: toId, + func: args[0], + callAt: this.now + delay, + invokeArgs: Array.prototype.slice.call(args, 2) + }; + + if (recurring === true) { + this.timeouts[toId].interval = delay; + } + + return toId; + } + + function parseTime(str) { + if (!str) { + return 0; + } + + var strings = str.split(":"); + var l = strings.length, i = l; + var ms = 0, parsed; + + if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { + throw new Error("tick only understands numbers and 'h:m:s'"); + } + + while (i--) { + parsed = parseInt(strings[i], 10); + + if (parsed >= 60) { + throw new Error("Invalid time " + str); + } + + ms += parsed * Math.pow(60, (l - i - 1)); + } + + return ms * 1000; + } + + function createObject(object) { + var newObject; + + if (Object.create) { + newObject = Object.create(object); + } else { + var F = function () {}; + F.prototype = object; + newObject = new F(); + } + + newObject.Date.clock = newObject; + return newObject; + } + + sinon.clock = { + now: 0, + + create: function create(now) { + var clock = createObject(this); + + if (typeof now == "number") { + clock.now = now; + } + + if (!!now && typeof now == "object") { + throw new TypeError("now should be milliseconds since UNIX epoch"); + } + + return clock; + }, + + setTimeout: function setTimeout(callback, timeout) { + return addTimer.call(this, arguments, false); + }, + + clearTimeout: function clearTimeout(timerId) { + if (!this.timeouts) { + this.timeouts = []; + } + + if (timerId in this.timeouts) { + delete this.timeouts[timerId]; + } + }, + + setInterval: function setInterval(callback, timeout) { + return addTimer.call(this, arguments, true); + }, + + clearInterval: function clearInterval(timerId) { + this.clearTimeout(timerId); + }, + + tick: function tick(ms) { + ms = typeof ms == "number" ? ms : parseTime(ms); + var tickFrom = this.now, tickTo = this.now + ms, previous = this.now; + var timer = this.firstTimerInRange(tickFrom, tickTo); + + var firstException; + while (timer && tickFrom <= tickTo) { + if (this.timeouts[timer.id]) { + tickFrom = this.now = timer.callAt; + try { + this.callTimer(timer); + } catch (e) { + firstException = firstException || e; + } + } + + timer = this.firstTimerInRange(previous, tickTo); + previous = tickFrom; + } + + this.now = tickTo; + + if (firstException) { + throw firstException; + } + + return this.now; + }, + + firstTimerInRange: function (from, to) { + var timer, smallest, originalTimer; + + for (var id in this.timeouts) { + if (this.timeouts.hasOwnProperty(id)) { + if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) { + continue; + } + + if (!smallest || this.timeouts[id].callAt < smallest) { + originalTimer = this.timeouts[id]; + smallest = this.timeouts[id].callAt; + + timer = { + func: this.timeouts[id].func, + callAt: this.timeouts[id].callAt, + interval: this.timeouts[id].interval, + id: this.timeouts[id].id, + invokeArgs: this.timeouts[id].invokeArgs + }; + } + } + } + + return timer || null; + }, + + callTimer: function (timer) { + if (typeof timer.interval == "number") { + this.timeouts[timer.id].callAt += timer.interval; + } else { + delete this.timeouts[timer.id]; + } + + try { + if (typeof timer.func == "function") { + timer.func.apply(null, timer.invokeArgs); + } else { + eval(timer.func); + } + } catch (e) { + var exception = e; + } + + if (!this.timeouts[timer.id]) { + if (exception) { + throw exception; + } + return; + } + + if (exception) { + throw exception; + } + }, + + reset: function reset() { + this.timeouts = {}; + }, + + Date: (function () { + var NativeDate = Date; + + function ClockDate(year, month, date, hour, minute, second, ms) { + // Defensive and verbose to avoid potential harm in passing + // explicit undefined when user does not pass argument + switch (arguments.length) { + case 0: + return new NativeDate(ClockDate.clock.now); + case 1: + return new NativeDate(year); + case 2: + return new NativeDate(year, month); + case 3: + return new NativeDate(year, month, date); + case 4: + return new NativeDate(year, month, date, hour); + case 5: + return new NativeDate(year, month, date, hour, minute); + case 6: + return new NativeDate(year, month, date, hour, minute, second); + default: + return new NativeDate(year, month, date, hour, minute, second, ms); + } + } + + return mirrorDateProperties(ClockDate, NativeDate); + }()) + }; + + function mirrorDateProperties(target, source) { + if (source.now) { + target.now = function now() { + return target.clock.now; + }; + } else { + delete target.now; + } + + if (source.toSource) { + target.toSource = function toSource() { + return source.toSource(); + }; + } else { + delete target.toSource; + } + + target.toString = function toString() { + return source.toString(); + }; + + target.prototype = source.prototype; + target.parse = source.parse; + target.UTC = source.UTC; + target.prototype.toUTCString = source.prototype.toUTCString; + return target; + } + + var methods = ["Date", "setTimeout", "setInterval", + "clearTimeout", "clearInterval"]; + + function restore() { + var method; + + for (var i = 0, l = this.methods.length; i < l; i++) { + method = this.methods[i]; + if (global[method].hadOwnProperty) { + global[method] = this["_" + method]; + } else { + delete global[method]; + } + } + + // Prevent multiple executions which will completely remove these props + this.methods = []; + } + + function stubGlobal(method, clock) { + clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method); + clock["_" + method] = global[method]; + + if (method == "Date") { + var date = mirrorDateProperties(clock[method], global[method]); + global[method] = date; + } else { + global[method] = function () { + return clock[method].apply(clock, arguments); + }; + + for (var prop in clock[method]) { + if (clock[method].hasOwnProperty(prop)) { + global[method][prop] = clock[method][prop]; + } + } + } + + global[method].clock = clock; + } + + sinon.useFakeTimers = function useFakeTimers(now) { + var clock = sinon.clock.create(now); + clock.restore = restore; + clock.methods = Array.prototype.slice.call(arguments, + typeof now == "number" ? 1 : 0); + + if (clock.methods.length === 0) { + clock.methods = methods; + } + + for (var i = 0, l = clock.methods.length; i < l; i++) { + stubGlobal(clock.methods[i], clock); + } + + return clock; + }; +}(typeof global != "undefined" && typeof global !== "function" ? global : this)); + +sinon.timers = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval, + Date: Date +}; + +if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; +} + +/*jslint eqeqeq: false, onevar: false*/ +/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ +/** + * Minimal Event interface implementation + * + * Original implementation by Sven Fuchs: https://gist.github.com/995028 + * Modifications and tests by Christian Johansen. + * + * @author Sven Fuchs (svenfuchs@artweb-design.de) + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2011 Sven Fuchs, Christian Johansen + */ + +if (typeof sinon == "undefined") { + this.sinon = {}; +} + +(function () { + var push = [].push; + + sinon.Event = function Event(type, bubbles, cancelable, target) { + this.initEvent(type, bubbles, cancelable, target); + }; + + sinon.Event.prototype = { + initEvent: function(type, bubbles, cancelable, target) { + this.type = type; + this.bubbles = bubbles; + this.cancelable = cancelable; + this.target = target; + }, + + stopPropagation: function () {}, + + preventDefault: function () { + this.defaultPrevented = true; + } + }; + + sinon.EventTarget = { + addEventListener: function addEventListener(event, listener, useCapture) { + this.eventListeners = this.eventListeners || {}; + this.eventListeners[event] = this.eventListeners[event] || []; + push.call(this.eventListeners[event], listener); + }, + + removeEventListener: function removeEventListener(event, listener, useCapture) { + var listeners = this.eventListeners && this.eventListeners[event] || []; + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i] == listener) { + return listeners.splice(i, 1); + } + } + }, + + dispatchEvent: function dispatchEvent(event) { + var type = event.type; + var listeners = this.eventListeners && this.eventListeners[type] || []; + + for (var i = 0; i < listeners.length; i++) { + if (typeof listeners[i] == "function") { + listeners[i].call(this, event); + } else { + listeners[i].handleEvent(event); + } + } + + return !!event.defaultPrevented; + } + }; +}()); + +/** + * @depend ../../sinon.js + * @depend event.js + */ +/*jslint eqeqeq: false, onevar: false*/ +/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ +/** + * Fake XMLHttpRequest object + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +if (typeof sinon == "undefined") { + this.sinon = {}; +} +sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; + +// wrapper for global +(function(global) { + var xhr = sinon.xhr; + xhr.GlobalXMLHttpRequest = global.XMLHttpRequest; + xhr.GlobalActiveXObject = global.ActiveXObject; + xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined"; + xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined"; + xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX + ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false; + + /*jsl:ignore*/ + var unsafeHeaders = { + "Accept-Charset": true, + "Accept-Encoding": true, + "Connection": true, + "Content-Length": true, + "Cookie": true, + "Cookie2": true, + "Content-Transfer-Encoding": true, + "Date": true, + "Expect": true, + "Host": true, + "Keep-Alive": true, + "Referer": true, + "TE": true, + "Trailer": true, + "Transfer-Encoding": true, + "Upgrade": true, + "User-Agent": true, + "Via": true + }; + /*jsl:end*/ + + function FakeXMLHttpRequest() { + this.readyState = FakeXMLHttpRequest.UNSENT; + this.requestHeaders = {}; + this.requestBody = null; + this.status = 0; + this.statusText = ""; + + var xhr = this; + + ["loadstart", "load", "abort", "loadend"].forEach(function (eventName) { + xhr.addEventListener(eventName, function (event) { + var listener = xhr["on" + eventName]; + + if (listener && typeof listener == "function") { + listener(event); + } + }); + }); + + if (typeof FakeXMLHttpRequest.onCreate == "function") { + FakeXMLHttpRequest.onCreate(this); + } + } + + function verifyState(xhr) { + if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { + throw new Error("INVALID_STATE_ERR"); + } + + if (xhr.sendFlag) { + throw new Error("INVALID_STATE_ERR"); + } + } + + // filtering to enable a white-list version of Sinon FakeXhr, + // where whitelisted requests are passed through to real XHR + function each(collection, callback) { + if (!collection) return; + for (var i = 0, l = collection.length; i < l; i += 1) { + callback(collection[i]); + } + } + function some(collection, callback) { + for (var index = 0; index < collection.length; index++) { + if(callback(collection[index]) === true) return true; + }; + return false; + } + // largest arity in XHR is 5 - XHR#open + var apply = function(obj,method,args) { + switch(args.length) { + case 0: return obj[method](); + case 1: return obj[method](args[0]); + case 2: return obj[method](args[0],args[1]); + case 3: return obj[method](args[0],args[1],args[2]); + case 4: return obj[method](args[0],args[1],args[2],args[3]); + case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]); + }; + }; + + FakeXMLHttpRequest.filters = []; + FakeXMLHttpRequest.addFilter = function(fn) { + this.filters.push(fn) + }; + var IE6Re = /MSIE 6/; + FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) { + var xhr = new sinon.xhr.workingXHR(); + each(["open","setRequestHeader","send","abort","getResponseHeader", + "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"], + function(method) { + fakeXhr[method] = function() { + return apply(xhr,method,arguments); + }; + }); + + var copyAttrs = function(args) { + each(args, function(attr) { + try { + fakeXhr[attr] = xhr[attr] + } catch(e) { + if(!IE6Re.test(navigator.userAgent)) throw e; + } + }); + }; + + var stateChange = function() { + fakeXhr.readyState = xhr.readyState; + if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) { + copyAttrs(["status","statusText"]); + } + if(xhr.readyState >= FakeXMLHttpRequest.LOADING) { + copyAttrs(["responseText"]); + } + if(xhr.readyState === FakeXMLHttpRequest.DONE) { + copyAttrs(["responseXML"]); + } + if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr); + }; + if(xhr.addEventListener) { + for(var event in fakeXhr.eventListeners) { + if(fakeXhr.eventListeners.hasOwnProperty(event)) { + each(fakeXhr.eventListeners[event],function(handler) { + xhr.addEventListener(event, handler); + }); + } + } + xhr.addEventListener("readystatechange",stateChange); + } else { + xhr.onreadystatechange = stateChange; + } + apply(xhr,"open",xhrArgs); + }; + FakeXMLHttpRequest.useFilters = false; + + function verifyRequestSent(xhr) { + if (xhr.readyState == FakeXMLHttpRequest.DONE) { + throw new Error("Request done"); + } + } + + function verifyHeadersReceived(xhr) { + if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { + throw new Error("No headers received"); + } + } + + function verifyResponseBodyType(body) { + if (typeof body != "string") { + var error = new Error("Attempted to respond to fake XMLHttpRequest with " + + body + ", which is not a string."); + error.name = "InvalidBodyException"; + throw error; + } + } + + sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, { + async: true, + + open: function open(method, url, async, username, password) { + this.method = method; + this.url = url; + this.async = typeof async == "boolean" ? async : true; + this.username = username; + this.password = password; + this.responseText = null; + this.responseXML = null; + this.requestHeaders = {}; + this.sendFlag = false; + if(sinon.FakeXMLHttpRequest.useFilters === true) { + var xhrArgs = arguments; + var defake = some(FakeXMLHttpRequest.filters,function(filter) { + return filter.apply(this,xhrArgs) + }); + if (defake) { + return sinon.FakeXMLHttpRequest.defake(this,arguments); + } + } + this.readyStateChange(FakeXMLHttpRequest.OPENED); + }, + + readyStateChange: function readyStateChange(state) { + this.readyState = state; + + if (typeof this.onreadystatechange == "function") { + try { + this.onreadystatechange(); + } catch (e) { + sinon.logError("Fake XHR onreadystatechange handler", e); + } + } + + this.dispatchEvent(new sinon.Event("readystatechange")); + + switch (this.readyState) { + case FakeXMLHttpRequest.DONE: + this.dispatchEvent(new sinon.Event("load", false, false, this)); + this.dispatchEvent(new sinon.Event("loadend", false, false, this)); + break; + } + }, + + setRequestHeader: function setRequestHeader(header, value) { + verifyState(this); + + if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { + throw new Error("Refused to set unsafe header \"" + header + "\""); + } + + if (this.requestHeaders[header]) { + this.requestHeaders[header] += "," + value; + } else { + this.requestHeaders[header] = value; + } + }, + + // Helps testing + setResponseHeaders: function setResponseHeaders(headers) { + this.responseHeaders = {}; + + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + this.responseHeaders[header] = headers[header]; + } + } + + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); + } else { + this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; + } + }, + + // Currently treats ALL data as a DOMString (i.e. no Document) + send: function send(data) { + verifyState(this); + + if (!/^(get|head)$/i.test(this.method)) { + if (this.requestHeaders["Content-Type"]) { + var value = this.requestHeaders["Content-Type"].split(";"); + this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8"; + } else { + this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; + } + + this.requestBody = data; + } + + this.errorFlag = false; + this.sendFlag = this.async; + this.readyStateChange(FakeXMLHttpRequest.OPENED); + + if (typeof this.onSend == "function") { + this.onSend(this); + } + + this.dispatchEvent(new sinon.Event("loadstart", false, false, this)); + }, + + abort: function abort() { + this.aborted = true; + this.responseText = null; + this.errorFlag = true; + this.requestHeaders = {}; + + if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) { + this.readyStateChange(sinon.FakeXMLHttpRequest.DONE); + this.sendFlag = false; + } + + this.readyState = sinon.FakeXMLHttpRequest.UNSENT; + + this.dispatchEvent(new sinon.Event("abort", false, false, this)); + if (typeof this.onerror === "function") { + this.onerror(); + } + }, + + getResponseHeader: function getResponseHeader(header) { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return null; + } + + if (/^Set-Cookie2?$/i.test(header)) { + return null; + } + + header = header.toLowerCase(); + + for (var h in this.responseHeaders) { + if (h.toLowerCase() == header) { + return this.responseHeaders[h]; + } + } + + return null; + }, + + getAllResponseHeaders: function getAllResponseHeaders() { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return ""; + } + + var headers = ""; + + for (var header in this.responseHeaders) { + if (this.responseHeaders.hasOwnProperty(header) && + !/^Set-Cookie2?$/i.test(header)) { + headers += header + ": " + this.responseHeaders[header] + "\r\n"; + } + } + + return headers; + }, + + setResponseBody: function setResponseBody(body) { + verifyRequestSent(this); + verifyHeadersReceived(this); + verifyResponseBodyType(body); + + var chunkSize = this.chunkSize || 10; + var index = 0; + this.responseText = ""; + + do { + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.LOADING); + } + + this.responseText += body.substring(index, index + chunkSize); + index += chunkSize; + } while (index < body.length); + + var type = this.getResponseHeader("Content-Type"); + + if (this.responseText && + (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { + try { + this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); + } catch (e) { + // Unable to parse XML - no biggie + } + } + + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.DONE); + } else { + this.readyState = FakeXMLHttpRequest.DONE; + } + }, + + respond: function respond(status, headers, body) { + this.setResponseHeaders(headers || {}); + this.status = typeof status == "number" ? status : 200; + this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; + this.setResponseBody(body || ""); + if (typeof this.onload === "function"){ + this.onload(); + } + + } + }); + + sinon.extend(FakeXMLHttpRequest, { + UNSENT: 0, + OPENED: 1, + HEADERS_RECEIVED: 2, + LOADING: 3, + DONE: 4 + }); + + // Borrowed from JSpec + FakeXMLHttpRequest.parseXML = function parseXML(text) { + var xmlDoc; + + if (typeof DOMParser != "undefined") { + var parser = new DOMParser(); + xmlDoc = parser.parseFromString(text, "text/xml"); + } else { + xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); + xmlDoc.async = "false"; + xmlDoc.loadXML(text); + } + + return xmlDoc; + }; + + FakeXMLHttpRequest.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" + }; + + sinon.useFakeXMLHttpRequest = function () { + sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) { + if (xhr.supportsXHR) { + global.XMLHttpRequest = xhr.GlobalXMLHttpRequest; + } + + if (xhr.supportsActiveX) { + global.ActiveXObject = xhr.GlobalActiveXObject; + } + + delete sinon.FakeXMLHttpRequest.restore; + + if (keepOnCreate !== true) { + delete sinon.FakeXMLHttpRequest.onCreate; + } + }; + if (xhr.supportsXHR) { + global.XMLHttpRequest = sinon.FakeXMLHttpRequest; + } + + if (xhr.supportsActiveX) { + global.ActiveXObject = function ActiveXObject(objId) { + if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) { + + return new sinon.FakeXMLHttpRequest(); + } + + return new xhr.GlobalActiveXObject(objId); + }; + } + + return sinon.FakeXMLHttpRequest; + }; + + sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; +})(this); + +if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; +} + +/** + * @depend fake_xml_http_request.js + */ +/*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/ +/*global module, require, window*/ +/** + * The Sinon "server" mimics a web server that receives requests from + * sinon.FakeXMLHttpRequest and provides an API to respond to those requests, + * both synchronously and asynchronously. To respond synchronuously, canned + * answers have to be provided upfront. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +if (typeof sinon == "undefined") { + var sinon = {}; +} + +sinon.fakeServer = (function () { + var push = [].push; + function F() {} + + function create(proto) { + F.prototype = proto; + return new F(); + } + + function responseArray(handler) { + var response = handler; + + if (Object.prototype.toString.call(handler) != "[object Array]") { + response = [200, {}, handler]; + } + + if (typeof response[2] != "string") { + throw new TypeError("Fake server response body should be string, but was " + + typeof response[2]); + } + + return response; + } + + var wloc = typeof window !== "undefined" ? window.location : {}; + var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host); + + function matchOne(response, reqMethod, reqUrl) { + var rmeth = response.method; + var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase(); + var url = response.url; + var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl)); + + return matchMethod && matchUrl; + } + + function match(response, request) { + var requestMethod = this.getHTTPMethod(request); + var requestUrl = request.url; + + if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { + requestUrl = requestUrl.replace(rCurrLoc, ""); + } + + if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { + if (typeof response.response == "function") { + var ru = response.url; + var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1)); + return response.response.apply(response, args); + } + + return true; + } + + return false; + } + + function log(response, request) { + var str; + + str = "Request:\n" + sinon.format(request) + "\n\n"; + str += "Response:\n" + sinon.format(response) + "\n\n"; + + sinon.log(str); + } + + return { + create: function () { + var server = create(this); + this.xhr = sinon.useFakeXMLHttpRequest(); + server.requests = []; + + this.xhr.onCreate = function (xhrObj) { + server.addRequest(xhrObj); + }; + + return server; + }, + + addRequest: function addRequest(xhrObj) { + var server = this; + push.call(this.requests, xhrObj); + + xhrObj.onSend = function () { + server.handleRequest(this); + }; + + if (this.autoRespond && !this.responding) { + setTimeout(function () { + server.responding = false; + server.respond(); + }, this.autoRespondAfter || 10); + + this.responding = true; + } + }, + + getHTTPMethod: function getHTTPMethod(request) { + if (this.fakeHTTPMethods && /post/i.test(request.method)) { + var matches = (request.requestBody || "").match(/_method=([^\b;]+)/); + return !!matches ? matches[1] : request.method; + } + + return request.method; + }, + + handleRequest: function handleRequest(xhr) { + if (xhr.async) { + if (!this.queue) { + this.queue = []; + } + + push.call(this.queue, xhr); + } else { + this.processRequest(xhr); + } + }, + + respondWith: function respondWith(method, url, body) { + if (arguments.length == 1 && typeof method != "function") { + this.response = responseArray(method); + return; + } + + if (!this.responses) { this.responses = []; } + + if (arguments.length == 1) { + body = method; + url = method = null; + } + + if (arguments.length == 2) { + body = url; + url = method; + method = null; + } + + push.call(this.responses, { + method: method, + url: url, + response: typeof body == "function" ? body : responseArray(body) + }); + }, + + respond: function respond() { + if (arguments.length > 0) this.respondWith.apply(this, arguments); + var queue = this.queue || []; + var request; + + while(request = queue.shift()) { + this.processRequest(request); + } + }, + + processRequest: function processRequest(request) { + try { + if (request.aborted) { + return; + } + + var response = this.response || [404, {}, ""]; + + if (this.responses) { + for (var i = 0, l = this.responses.length; i < l; i++) { + if (match.call(this, this.responses[i], request)) { + response = this.responses[i].response; + break; + } + } + } + + if (request.readyState != 4) { + log(response, request); + + request.respond(response[0], response[1], response[2]); + } + } catch (e) { + sinon.logError("Fake server request processing", e); + } + }, + + restore: function restore() { + return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); + } + }; +}()); + +if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; +} + +/** + * @depend fake_server.js + * @depend fake_timers.js + */ +/*jslint browser: true, eqeqeq: false, onevar: false*/ +/*global sinon*/ +/** + * Add-on for sinon.fakeServer that automatically handles a fake timer along with + * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery + * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead, + * it polls the object for completion with setInterval. Dispite the direct + * motivation, there is nothing jQuery-specific in this file, so it can be used + * in any environment where the ajax implementation depends on setInterval or + * setTimeout. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function () { + function Server() {} + Server.prototype = sinon.fakeServer; + + sinon.fakeServerWithClock = new Server(); + + sinon.fakeServerWithClock.addRequest = function addRequest(xhr) { + if (xhr.async) { + if (typeof setTimeout.clock == "object") { + this.clock = setTimeout.clock; + } else { + this.clock = sinon.useFakeTimers(); + this.resetClock = true; + } + + if (!this.longestTimeout) { + var clockSetTimeout = this.clock.setTimeout; + var clockSetInterval = this.clock.setInterval; + var server = this; + + this.clock.setTimeout = function (fn, timeout) { + server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); + + return clockSetTimeout.apply(this, arguments); + }; + + this.clock.setInterval = function (fn, timeout) { + server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); + + return clockSetInterval.apply(this, arguments); + }; + } + } + + return sinon.fakeServer.addRequest.call(this, xhr); + }; + + sinon.fakeServerWithClock.respond = function respond() { + var returnVal = sinon.fakeServer.respond.apply(this, arguments); + + if (this.clock) { + this.clock.tick(this.longestTimeout || 0); + this.longestTimeout = 0; + + if (this.resetClock) { + this.clock.restore(); + this.resetClock = false; + } + } + + return returnVal; + }; + + sinon.fakeServerWithClock.restore = function restore() { + if (this.clock) { + this.clock.restore(); + } + + return sinon.fakeServer.restore.apply(this, arguments); + }; +}()); + +/** + * @depend ../sinon.js + * @depend collection.js + * @depend util/fake_timers.js + * @depend util/fake_server_with_clock.js + */ +/*jslint eqeqeq: false, onevar: false, plusplus: false*/ +/*global require, module*/ +/** + * Manages fake collections as well as fake utilities such as Sinon's + * timers and fake XHR implementation in one convenient object. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +if (typeof module == "object" && typeof require == "function") { + var sinon = require("../sinon"); + sinon.extend(sinon, require("./util/fake_timers")); +} + +(function () { + var push = [].push; + + function exposeValue(sandbox, config, key, value) { + if (!value) { + return; + } + + if (config.injectInto) { + config.injectInto[key] = value; + } else { + push.call(sandbox.args, value); + } + } + + function prepareSandboxFromConfig(config) { + var sandbox = sinon.create(sinon.sandbox); + + if (config.useFakeServer) { + if (typeof config.useFakeServer == "object") { + sandbox.serverPrototype = config.useFakeServer; + } + + sandbox.useFakeServer(); + } + + if (config.useFakeTimers) { + if (typeof config.useFakeTimers == "object") { + sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers); + } else { + sandbox.useFakeTimers(); + } + } + + return sandbox; + } + + sinon.sandbox = sinon.extend(sinon.create(sinon.collection), { + useFakeTimers: function useFakeTimers() { + this.clock = sinon.useFakeTimers.apply(sinon, arguments); + + return this.add(this.clock); + }, + + serverPrototype: sinon.fakeServer, + + useFakeServer: function useFakeServer() { + var proto = this.serverPrototype || sinon.fakeServer; + + if (!proto || !proto.create) { + return null; + } + + this.server = proto.create(); + return this.add(this.server); + }, + + inject: function (obj) { + sinon.collection.inject.call(this, obj); + + if (this.clock) { + obj.clock = this.clock; + } + + if (this.server) { + obj.server = this.server; + obj.requests = this.server.requests; + } + + return obj; + }, + + create: function (config) { + if (!config) { + return sinon.create(sinon.sandbox); + } + + var sandbox = prepareSandboxFromConfig(config); + sandbox.args = sandbox.args || []; + var prop, value, exposed = sandbox.inject({}); + + if (config.properties) { + for (var i = 0, l = config.properties.length; i < l; i++) { + prop = config.properties[i]; + value = exposed[prop] || prop == "sandbox" && sandbox; + exposeValue(sandbox, config, prop, value); + } + } else { + exposeValue(sandbox, config, "sandbox", value); + } + + return sandbox; + } + }); + + sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; + + if (typeof module == "object" && typeof require == "function") { + module.exports = sinon.sandbox; + } +}()); + +/** + * @depend ../sinon.js + * @depend stub.js + * @depend mock.js + * @depend sandbox.js + */ +/*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Test function, sandboxes fakes + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function test(callback) { + var type = typeof callback; + + if (type != "function") { + throw new TypeError("sinon.test needs to wrap a test function, got " + type); + } + + return function () { + var config = sinon.getConfig(sinon.config); + config.injectInto = config.injectIntoThis && this || config.injectInto; + var sandbox = sinon.sandbox.create(config); + var exception, result; + var args = Array.prototype.slice.call(arguments).concat(sandbox.args); + + try { + result = callback.apply(this, args); + } catch (e) { + exception = e; + } + + if (typeof exception !== "undefined") { + sandbox.restore(); + throw exception; + } + else { + sandbox.verifyAndRestore(); + } + + return result; + }; + } + + test.config = { + injectIntoThis: true, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "server", "requests"], + useFakeTimers: true, + useFakeServer: true + }; + + if (commonJSModule) { + module.exports = test; + } else { + sinon.test = test; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend test.js + */ +/*jslint eqeqeq: false, onevar: false, eqeqeq: false*/ +/*global module, require, sinon*/ +/** + * Test case, sandboxes all test functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon || !Object.prototype.hasOwnProperty) { + return; + } + + function createTest(property, setUp, tearDown) { + return function () { + if (setUp) { + setUp.apply(this, arguments); + } + + var exception, result; + + try { + result = property.apply(this, arguments); + } catch (e) { + exception = e; + } + + if (tearDown) { + tearDown.apply(this, arguments); + } + + if (exception) { + throw exception; + } + + return result; + }; + } + + function testCase(tests, prefix) { + /*jsl:ignore*/ + if (!tests || typeof tests != "object") { + throw new TypeError("sinon.testCase needs an object with test functions"); + } + /*jsl:end*/ + + prefix = prefix || "test"; + var rPrefix = new RegExp("^" + prefix); + var methods = {}, testName, property, method; + var setUp = tests.setUp; + var tearDown = tests.tearDown; + + for (testName in tests) { + if (tests.hasOwnProperty(testName)) { + property = tests[testName]; + + if (/^(setUp|tearDown)$/.test(testName)) { + continue; + } + + if (typeof property == "function" && rPrefix.test(testName)) { + method = property; + + if (setUp || tearDown) { + method = createTest(property, setUp, tearDown); + } + + methods[testName] = sinon.test(method); + } else { + methods[testName] = tests[testName]; + } + } + } + + return methods; + } + + if (commonJSModule) { + module.exports = testCase; + } else { + sinon.testCase = testCase; + } +}(typeof sinon == "object" && sinon || null)); + +/** + * @depend ../sinon.js + * @depend stub.js + */ +/*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/ +/*global module, require, sinon*/ +/** + * Assertions matching the test spy retrieval interface. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + +(function (sinon, global) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var slice = Array.prototype.slice; + var assert; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function verifyIsStub() { + var method; + + for (var i = 0, l = arguments.length; i < l; ++i) { + method = arguments[i]; + + if (!method) { + assert.fail("fake is not a spy"); + } + + if (typeof method != "function") { + assert.fail(method + " is not a function"); + } + + if (typeof method.getCall != "function") { + assert.fail(method + " is not stubbed"); + } + } + } + + function failAssertion(object, msg) { + object = object || global; + var failMethod = object.fail || assert.fail; + failMethod.call(object, msg); + } + + function mirrorPropAsAssertion(name, method, message) { + if (arguments.length == 2) { + message = method; + method = name; + } + + assert[name] = function (fake) { + verifyIsStub(fake); + + var args = slice.call(arguments, 1); + var failed = false; + + if (typeof method == "function") { + failed = !method(fake); + } else { + failed = typeof fake[method] == "function" ? + !fake[method].apply(fake, args) : !fake[method]; + } + + if (failed) { + failAssertion(this, fake.printf.apply(fake, [message].concat(args))); + } else { + assert.pass(name); + } + }; + } + + function exposedName(prefix, prop) { + return !prefix || /^fail/.test(prop) ? prop : + prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1); + }; + + assert = { + failException: "AssertError", + + fail: function fail(message) { + var error = new Error(message); + error.name = this.failException || assert.failException; + + throw error; + }, + + pass: function pass(assertion) {}, + + callOrder: function assertCallOrder() { + verifyIsStub.apply(null, arguments); + var expected = "", actual = ""; + + if (!sinon.calledInOrder(arguments)) { + try { + expected = [].join.call(arguments, ", "); + var calls = slice.call(arguments); + var i = calls.length; + while (i) { + if (!calls[--i].called) { + calls.splice(i, 1); + } + } + actual = sinon.orderByFirstCall(calls).join(", "); + } catch (e) { + // If this fails, we'll just fall back to the blank string + } + + failAssertion(this, "expected " + expected + " to be " + + "called in order but were called as " + actual); + } else { + assert.pass("callOrder"); + } + }, + + callCount: function assertCallCount(method, count) { + verifyIsStub(method); + + if (method.callCount != count) { + var msg = "expected %n to be called " + sinon.timesInWords(count) + + " but was called %c%C"; + failAssertion(this, method.printf(msg)); + } else { + assert.pass("callCount"); + } + }, + + expose: function expose(target, options) { + if (!target) { + throw new TypeError("target is null or undefined"); + } + + var o = options || {}; + var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix; + var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail; + + for (var method in this) { + if (method != "export" && (includeFail || !/^(fail)/.test(method))) { + target[exposedName(prefix, method)] = this[method]; + } + } + + return target; + } + }; + + mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called"); + mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; }, + "expected %n to not have been called but was called %c%C"); + mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C"); + mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C"); + mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C"); + mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t"); + mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t"); + mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new"); + mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new"); + mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C"); + mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C"); + mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C"); + mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C"); + mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C"); + mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C"); + mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C"); + mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C"); + mirrorPropAsAssertion("threw", "%n did not throw exception%C"); + mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C"); + + if (commonJSModule) { + module.exports = assert; + } else { + sinon.assert = assert; + } +}(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global)); + +return sinon;}.call(typeof window != 'undefined' && window || {})); diff --git a/tests/libs/sinon-chai-2.4.0.js b/tests/libs/sinon-chai-2.4.0.js new file mode 100644 index 00000000..26cee36a --- /dev/null +++ b/tests/libs/sinon-chai-2.4.0.js @@ -0,0 +1,109 @@ +(function (sinonChai) { + "use strict"; + + // Module systems magic dance. + + if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { + // NodeJS + module.exports = sinonChai; + } else if (typeof define === "function" && define.amd) { + // AMD + define(function () { + return sinonChai; + }); + } else { + // Other environment (usually + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/themes/all-hallows-eve.css b/themes/all-hallows-eve.css new file mode 100644 index 00000000..f06a33f3 --- /dev/null +++ b/themes/all-hallows-eve.css @@ -0,0 +1,49 @@ +/** + * All Hallows Eve theme + * + * Adapted from Ultraviolet RubyGem + * + * @author Flinn Mueller + * @version 1.0.1 + */ +pre { + background: #000; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #fff; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #9933CC; +} + +pre .constant { + color: #3387CC; +} + +pre .storage { + color: #CC7833; +} + +pre .string { + color: #66CC33; +} + +pre .keyword, pre .selector { + color: #CC7833; +} + +pre .inherited-class { + font-style: italic; +} + +pre .support { + color: #C83730; +} diff --git a/themes/blackboard.css b/themes/blackboard.css new file mode 100644 index 00000000..68fe5158 --- /dev/null +++ b/themes/blackboard.css @@ -0,0 +1,62 @@ +/** + * Blackboard theme + * + * Adapted from Domenico Carbotta's TextMate theme of the same name + * + * @author Domenico Carbotta + * @author Craig Campbell + * @version 1.0.2 + */ +pre { + background: #0B1022; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #fff; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #727272; +} + +pre .constant { + color: #D8FA3C; +} + +pre .storage { + color: #FBDE2D; +} + +pre .string, pre .comment.docstring { + color: #61CE3C; +} + +pre .string.regexp, pre .support.tag.script, pre .support.tag.style { + color: #fff; +} + +pre .keyword, pre .selector { + color: #FBDE2D; +} + +pre .inherited-class { + font-style: italic; +} + +pre .entity { + color: #FF6400; +} + +pre .support, *[data-language="c"] .function.call { + color: #8DA6CE; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #FF6400; +} diff --git a/themes/dreamweaver.css b/themes/dreamweaver.css new file mode 100644 index 00000000..16081639 --- /dev/null +++ b/themes/dreamweaver.css @@ -0,0 +1,109 @@ +/** + * Dreamweaver theme + * + * @author Sean Coker + * @url http://seancoker.com + * @version 1.0 + */ + +pre { + /* original is white background with no border */ + background-color: #fff; + word-wrap: break-word; + margin: 0; + padding: 10px; + color: #000; + font-size: 13px; + line-height: 16px; + margin-bottom: 20px +} + +pre, code { + font-family: monospace; +} + +pre .comment { + color: #888; +} + +pre .support { + color: #cd57d5; +} + +pre .constant.numeric, pre .php.embedded { + color: #fa0002; + font-weight: bold; +} + +pre .keyword, pre .constant.language { + color: #000789; + font-weight: bold; +} + +pre .selector, pre .support.property, pre .entity.name.function { + color: #000; +} + +pre .storage.function, pre .variable.self, pre .support.function, pre .constant.language { + color: #000; + font-weight: bold; +} + +pre .string { + color: #0d43fa; + font-weight: normal; +} + +pre .css-property + span, pre .keyword.unit, pre .support.css-value { + color: #0d43fa !important; + font-weight: normal !important; +} + +pre .entity.tag.style + .string, pre .php.embedded .constant.language, pre .php.embedded .keyword { + color: #37a348 !important; +} + +pre .support.method { + color: #2bd5bb; +} + +pre .entity.name { + color: #fd74e0; +} + +pre .support.css-property, pre .support.tag-name, pre .support.tag, pre .support.attribute, pre .support.attribute + .operator { + color: #000789; +} + +pre .storage.module, pre .storage.class { + color: #122573; + font-weight: bold; +} + +pre .css.embedded .support.tag, pre .css.embedded .style.tag { + color: #cd57d5; +} + +pre .keyword.operator { + color: #2852eb; + font-weight: normal; +} + +pre .php.embedded .variable, pre .php.embedded .storage.function { + color: #0d43fa; + font-weight: normal; +} + +pre .php.embedded .string, pre .js.embedded .tag.script { + color: #c4001e; +} + +pre .php.embedded .comment { + color: #f4b441; + font-weight: normal; +} + +pre .php.embedded .function.name { + color: #000; + font-weight: normal; +} diff --git a/themes/espresso-libre.css b/themes/espresso-libre.css new file mode 100644 index 00000000..a9ee2d9a --- /dev/null +++ b/themes/espresso-libre.css @@ -0,0 +1,67 @@ +/** + * Espresso Libre theme + * + * Adapted from Ultraviolet RubyGem + * + * @author Flinn Mueller + * @version 1.0 + */ +pre { + background-color: #2a211c; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #bdae9d; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .support.class, pre .support.type { + color: #6d79de; +} + +pre .support.constant { + color: #00af0e; +} + +pre .constant.language { + color: #585cf6; +} + +pre .support.function { + font-weight: bold; + color: #7290d9; +} + +pre .string { + color: #049b0a; +} + +pre .constant.numeric { + color: #44aa43; +} + +pre .keyword, pre .storage { + color: #43a8ed; +} + +pre .entity.function { + color: #ff9358; +} + +pre .comment { + color: #0066ff; + font-style: italic; +} + +pre .constant.symbol { + color:#c5656b; +} + +pre .variable.instance, pre .variable.language, pre .constant { + color: #318495; +} diff --git a/themes/github.css b/themes/github.css new file mode 100644 index 00000000..088f0657 --- /dev/null +++ b/themes/github.css @@ -0,0 +1,88 @@ +/** + * GitHub theme + * + * @author Craig Campbell + * @version 1.0.4 + */ +pre { + border: 1px solid #ccc; + word-wrap: break-word; + padding: 6px 10px; + line-height: 19px; + margin-bottom: 20px; +} + +code { + border: 1px solid #eaeaea; + margin: 0px 2px; + padding: 0px 5px; + font-size: 12px; +} + +pre code { + border: 0px; + padding: 0px; + margin: 0px; + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + border-radius: 0px; +} + +pre, code { + font-family: Consolas, 'Liberation Mono', Courier, monospace; + color: #333; + background: #f8f8f8; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +pre, pre code { + font-size: 13px; +} + +pre .comment { + color: #998; +} + +pre .support { + color: #0086B3; +} + +pre .tag, pre .tag-name { + color: navy; +} + +pre .keyword, pre .css-property, pre .vendor-prefix, pre .sass, pre .class, pre .id, pre .css-value, pre .entity.function, pre .storage.function { + font-weight: bold; +} + +pre .css-property, pre .css-value, pre .vendor-prefix, pre .support.namespace { + color: #333; +} + +pre .constant.numeric, pre .keyword.unit, pre .hex-color { + font-weight: normal; + color: #099; +} + +pre .entity.class { + color: #458; +} + +pre .entity.id, pre .entity.function { + color: #900; +} + +pre .attribute, pre .variable { + color: teal; +} + +pre .string, pre .support.value { + font-weight: normal; + color: #d14; +} + +pre .regexp { + color: #009926; +} diff --git a/themes/kimbie-dark.css b/themes/kimbie-dark.css new file mode 100644 index 00000000..f3d7140f --- /dev/null +++ b/themes/kimbie-dark.css @@ -0,0 +1,48 @@ +/** + * Kimbie (Dark) + * + * @author Jan T. Sott + * @version 1.0.0 + * @url http://github.com/idleberg/Kimbie-Rainbow + */ +pre { + background-color: #221a0f; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #fbebd4; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #a57a4c; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #dc3958; /* red */ +} + +pre .constant.numeric, pre .constant.language, pre .constant.hex-color, pre .keyword.unit { + color: #f79a32; /* orange */ +} + +pre .constant, pre .entity, pre .entity.class, pre .support { + color: #f06431; /* yellow */ +} + +pre .constant.symbol, pre .string { + color: #889b4a; /* green */ +} + +pre .entity.function, pre .support.css-property, pre .selector { + color: #8ab1b0; /* blue */ +} + +pre .keyword, pre .storage { + color: #98676a; /* purple */ +} diff --git a/themes/kimbie-light.css b/themes/kimbie-light.css new file mode 100644 index 00000000..77df45cb --- /dev/null +++ b/themes/kimbie-light.css @@ -0,0 +1,48 @@ +/** + * Kimbie (Light) + * + * @author Jan T. Sott + * @version 1.0.0 + * @url http://github.com/idleberg/Kimbie-Rainbow + */ +pre { + background-color: #fbebd4; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #221a0f; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #d6baad; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #dc3958; /* red */ +} + +pre .constant.numeric, pre .constant.language, pre .constant.hex-color, pre .keyword.unit { + color: #f79a32; /* orange */ +} + +pre .constant, pre .entity, pre .entity.class, pre .support { + color: #f06431; /* yellow */ +} + +pre .constant.symbol, pre .string { + color: #889b4a; /* green */ +} + +pre .entity.function, pre .support.css-property, pre .selector { + color: #8ab1b0; /* blue */ +} + +pre .keyword, pre .storage { + color: #98676a; /* purple */ +} diff --git a/themes/monokai.css b/themes/monokai.css new file mode 100644 index 00000000..1205c780 --- /dev/null +++ b/themes/monokai.css @@ -0,0 +1,81 @@ +/** + * Monokai theme + * + * Adapted from Wimer Hazenberg's TextMate theme of the same name + * + * @author Wimer Hazenberg + * @author Michael Fasani + * @author Craig Campbell + * @version 1.0.0 + */ +pre { + background: #272822; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #fff; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #75715E; +} + +pre .constant { + color: #AE81FF; +} + +pre .storage { + color: #66D9EF; +} + +pre .string, pre .comment.docstring { + color: #E6DB74; +} + +pre .support.tag { + color: #fff; +} + +pre .keyword { + /* @todo some keywords use this light blue, most use the pink */ + /*color: #66D9EF;*/ + color: #F92672; +} + +pre .selector { + color: #F92672; +} + +pre .inherited-class { + font-style: italic; +} + +pre .operator, pre .support.tag-name , pre .entity.tag { + color: #F92672; +} + +pre .entity, pre .support.attribute, pre .entity.attribute { + color: #A6E22E; +} + +pre .support, *[data-language="c"] .function.call { + color: #66D9EF; +} + +*[data-language="html"] .support.operator { + color: #fff; +} + +pre .css-property, pre .storage.function { + font-style: italic; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #A6E22E; +} diff --git a/themes/obsidian.css b/themes/obsidian.css new file mode 100644 index 00000000..7b79b81d --- /dev/null +++ b/themes/obsidian.css @@ -0,0 +1,72 @@ +/** + * Obsidian theme + * + * Adapted from a theme based on: + * http://studiostyl.es/schemes/son-of-obsidian + * + * @author Dan Stewart + * @version 1.0 + */ +pre { + background: #22282A; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #F1F2F3; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #66747B; +} + +pre .constant { + color: #EC7600; +} + +pre .storage { + color: #EC7600; +} + +pre .string, pre .comment.docstring { + color: #EC7600; +} + +pre .string.regexp, pre .support.tag.script, pre .support.tag.style { + color: #fff; +} + + +pre .keyword, pre .selector { + color: #93C763; +} + +pre .inherited-class { + font-style: italic; +} + +pre .entity { + color: #93C763; +} + +pre .integer { + color: #FFCD22; +} + +pre .support, *[data-language="csharp"] .function.call { + color: #FACD22; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #CCC; +} + +/* C# specific rule */ +pre .preprocessor { + color: #66747B; +} diff --git a/themes/paraiso-dark.css b/themes/paraiso-dark.css new file mode 100644 index 00000000..9b9a0e9a --- /dev/null +++ b/themes/paraiso-dark.css @@ -0,0 +1,48 @@ +/** + * Paraíso (Dark) + * + * @author Jan T. Sott + * @version 1.0.0 + * @url http://github.com/idleberg/Paraiso-Rainbow + */ +pre { + background-color: #2f1e2e; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #e7e9db; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #776e71; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #ef6155; /* red */ +} + +pre .constant.numeric, pre .constant.language, pre .constant.hex-color, pre .keyword.unit { + color: #f99b15; /* orange */ +} + +pre .constant, pre .entity, pre .entity.class, pre .support { + color: #fec418; /* yellow */ +} + +pre .constant.symbol, pre .string { + color: #48b685; /* green */ +} + +pre .entity.function, pre .support.css-property, pre .selector { + color: #06b6ef; /* blue */ +} + +pre .keyword, pre .storage { + color: #815ba4; /* purple */ +} diff --git a/themes/paraiso-light.css b/themes/paraiso-light.css new file mode 100644 index 00000000..ba48055b --- /dev/null +++ b/themes/paraiso-light.css @@ -0,0 +1,48 @@ +/** + * Paraíso (Light) + * + * @author Jan T. Sott + * @version 1.0.0 + * @url http://github.com/idleberg/Paraiso-Rainbow + */ +pre { + background-color: #e7e9db; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #2f1e2e; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #8d8687; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #ef6155; /* red */ +} + +pre .constant.numeric, pre .constant.language, pre .constant.hex-color, pre .keyword.unit { + color: #f99b15; /* orange */ +} + +pre .constant, pre .entity, pre .entity.class, pre .support { + color: #fec418; /* yellow */ +} + +pre .constant.symbol, pre .string { + color: #48b685; /* green */ +} + +pre .entity.function, pre .support.css-property, pre .selector { + color: #06b6ef; /* blue */ +} + +pre .keyword, pre .storage { + color: #815ba4; /* purple */ +} diff --git a/themes/pastie.css b/themes/pastie.css new file mode 100644 index 00000000..bc3032f1 --- /dev/null +++ b/themes/pastie.css @@ -0,0 +1,102 @@ +/** + * Pastie theme + * + * @author pygments.org + * @author pastie.org + * @author Simon Potter + * @version 1.0 + */ + +pre { + /* original is white background with no border */ + background-color: #F8F8FF; + border: 1px solid #DEDEDE; + word-wrap: break-word; + margin: 0; + padding: 10px; + color: #000; + font-size: 13px; + line-height: 16px; + margin-bottom: 20px +} + +pre, code { + font-family: monospace; +} + +pre .comment { + color: #888; +} + +pre .keyword, pre .selector, pre .storage.module, pre .storage.class, pre .storage.function { + color: #080; + font-weight: bold; +} + +pre .keyword.operator { + color: #000; + font-weight: normal; +} + +pre .constant.language { + color: #038; + font-weight:bold; +} + +pre .constant.symbol, pre .class, pre .constant { + color: #036; + font-weight: bold; +} + +pre .keyword.namespace, pre .entity.name.class { + color: #B06; + font-weight: bold; +} + +pre .constant.numeric { + color: #00D; + font-weight: bold; +} + +pre .string, pre .comment.docstring { + color: #D20; + background-color: #FFF0F0; +} + +pre .string.regexp { + background-color: #FFF0FF; + color: #808; +} + +pre .variable.instance { + color: #33B; +} + +pre .entity.name.function { + color: #06B; + font-weight: bold; +} + +pre .support.tag-name, pre .entity.tag.script, pre .entity.tag.style { + color: #070; +} + +pre .support.attribute { + color: #007; + font-style: italic; +} + +pre .entity.name.tag, pre .storage.type { + color: #070; + font-weight: bold; +} + +pre .variable.self, pre .support.function { + color: #038; + font-weight: bold; +} + +pre .entity.function, pre .support.magic, pre.support.method { + color: #C00; + font-weight: bold; +} diff --git a/themes/solarized-dark.css b/themes/solarized-dark.css new file mode 100644 index 00000000..7f77841e --- /dev/null +++ b/themes/solarized-dark.css @@ -0,0 +1,86 @@ +/** + * Solarized Dark theme + * + * Adaptation of Solarized Dark from ethanschoonover.com/solarized + * + * @author Ethan Schoonover + * @author David Litmark + * @version 1.0.0 + */ +pre { + background: #002b36; /* base03 */ + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #839496; /* base0 */ + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #586e75; /* base01 */ +} + +pre .constant { + color: #839496; /* base0 */ +} + +pre .constant.language { + color: #268bd2; /* blue */ +} + +pre .constant.regexp { + color: #2aa198; /* cyan */ +} + +pre .storage { + color: #268bd2; /* blue */ +} + +pre .string, pre .comment.docstring { + color: #2aa198; /* cyan */ +} + +pre .support.tag.script, pre .support.tag.style { + color: #2aa198; /* cyan */ +} + +pre .string.regexp { + color: #2aa198; /* cyan */ +} + +pre .string.regexp.open, pre .string.regexp.close { + color: #2aa198; /* cyan */ +} + +pre .keyword, pre .selector { + color: #859900; /* green */ +} + +pre .inherited-class { + font-style: italic; +} + +pre .entity { + color: #b58900; /* yellow */ +} + +pre .support, *[data-language="c"] .function.call { + color: #859900; /* green */ +} + +pre .support.method { + color: #839496; /* base0 */ +} + +pre .support.property { + color: #839496; /* base0 */ +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #839496; /* base0 */ +} diff --git a/themes/solarized-light.css b/themes/solarized-light.css new file mode 100644 index 00000000..01e39e85 --- /dev/null +++ b/themes/solarized-light.css @@ -0,0 +1,86 @@ +/** + * Solarized Light theme + * + * Adaptation of Solarized Light from ethanschoonover.com/solarized + * + * @author Ethan Schoonover + * @author David Litmark + * @version 1.0.0 + */ +pre { + background: #fdf6e3; /* base3 */ + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #657b83; /* base00 */ + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #93a1a1; /* base1 */ +} + +pre .constant { + color: #657b83; /* base00 */ +} + +pre .constant.language { + color: #268bd2; /* blue */ +} + +pre .constant.regexp { + color: #2aa198; /* cyan */ +} + +pre .storage { + color: #268bd2; /* blue */ +} + +pre .string, pre .comment.docstring { + color: #2aa198; /* cyan */ +} + +pre .support.tag.script, pre .support.tag.style { + color: #2aa198; /* cyan */ +} + +pre .string.regexp { + color: #2aa198; /* cyan */ +} + +pre .string.regexp.open, pre .string.regexp.close { + color: #2aa198; /* cyan */ +} + +pre .keyword, pre .selector { + color: #859900; /* green */ +} + +pre .inherited-class { + font-style: italic; +} + +pre .entity { + color: #b58900; /* yellow */ +} + +pre .support, *[data-language="c"] .function.call { + color: #859900; /* green */ +} + +pre .support.method { + color: #657b83; /* base00 */ +} + +pre .support.property { + color: #657b83; /* base00 */ +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #657b83; /* base00 */ +} diff --git a/themes/sunburst.css b/themes/sunburst.css new file mode 100644 index 00000000..9ff5eae6 --- /dev/null +++ b/themes/sunburst.css @@ -0,0 +1,94 @@ +/** + * Sunburst theme + * + * Adapted from the Textmate Sunburst theme by Stanley Rost + * + * @author Stanley Rost + * @author Rachel Baker + * @version 1.0.0 + */ +pre { + background-color: #000; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #fff; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #AEAEAE; font-style: italic; +} + +pre .constant { + color: #3387CC; +} + +pre .storage { + color: #99CF50; +} + +pre .string, pre .entity.name.class, pre .comment.docstring { +color: #65B042; +/* green */ +} + +pre .string.regexp { + color: #E9C062; + } + +pre .string.constant { + color: #DDF2A4; +} + +pre .constant.hex-color { + color: #DD7B3B; + /* orange */ +} +pre .support, pre .tag.script, pre .function.call { + color: #dad085; +} +pre .support.css-property { + color: #C5AF75; +} +pre .support.tag.style { + color: #fff; +} +pre .keyword, pre .selector { + color: #E28964; + /* dark pink */ +} + +pre .keyword.namespace { + color: #3387CC; + font-weight: bold; +} + +pre .inherited-class { + font-style: italic; +} + +pre .entity, pre .variable.instance, pre .support.namespace, pre .support.tag, pre .support.tag-name { + color: #89BDFF; +} + +pre .entity.name.id { + color: #8693A5; +} + +*[data-language="c"] .function.call { + color: #8DA6CE; +} + +pre .variable, pre .variable.global { + color: #3E87E3; + } +pre .variable.class { + + } + diff --git a/themes/tomorrow-night.css b/themes/tomorrow-night.css new file mode 100644 index 00000000..8832b04e --- /dev/null +++ b/themes/tomorrow-night.css @@ -0,0 +1,48 @@ +/** + * Tomorrow Night theme + * + * @author Chris Kempson + * @author skim + * @version 1.0.0 + */ +pre { + background-color: #1d1f21; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #c5c8c6; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #969896; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #cc6666; /* red */ +} + +pre .constant.numeric, pre .constant.language, pre .constant.hex-color, pre .keyword.unit { + color: #de935f; /* orange */ +} + +pre .constant, pre .entity, pre .entity.class, pre .support { + color: #f0c674; /* yellow */ +} + +pre .constant.symbol, pre .string { + color: #b5bd68; /* green */ +} + +pre .entity.function, pre .support.css-property, pre .selector { + color: #81a2be; /* blue */ +} + +pre .keyword, pre .storage { + color: #b294bb; /* purple */ +} diff --git a/themes/tricolore.css b/themes/tricolore.css new file mode 100644 index 00000000..bc16ea91 --- /dev/null +++ b/themes/tricolore.css @@ -0,0 +1,59 @@ +/** + * Tricolore theme + * + * @author Jean Nicolas + * @version 1.0.1 + */ +pre { + background: #FFF; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #000; + font-size: 12px; + margin-bottom: 20px; + line-height: 16px; +} + +pre, code { + font-family: 'Monaco', 'Consolas', monospace; +} + +pre .comment { + color: #7E7E7E; + font-style: italic; +} + +pre .constant { + color: #18838A; + font-weight: bold; +} + +pre .storage { + color: #0000A1; +} + +pre .string { + color: #8E0022; +} + +pre .keyword, pre .selector { + color: #0000A1; + font-weight: bold; +} + +pre .inherited-class { + font-style: italic; +} + +pre .entity { + color: #3E853F; +} + +pre .support { + color: #192140; +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #3E853F; +} diff --git a/themes/twilight.css b/themes/twilight.css new file mode 100644 index 00000000..315bc47d --- /dev/null +++ b/themes/twilight.css @@ -0,0 +1,82 @@ +/** + * Twilight theme + * + * Adapted from Michael Sheets' TextMate theme of the same name + * + * @author Michael Sheets + * @author Jesse Farmer + * @version 1.0.1 + */ +pre { + background: #141414; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #F8F8F8; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #5F5A60; +} + +pre .constant.numeric { + color: #D87D50; +} + +pre .constant { + color: #889AB4; +} + +pre .constant.symbol, pre .constant.language { + color: #D87D50; +} + +pre .storage { + color: #F9EE98; +} + +pre .string { + color: #8F9D6A; +} + +pre .string.regexp { + color: #E9C062; +} + +pre .keyword, pre .selector, pre .storage { + color: #CDA869; +} + +pre .inherited-class { + color: #9B5C2E; +} + +pre .entity { + color: #FF6400; +} + +pre .support { + color: #9B859D; +} + +pre .support.magic { + color: #DAD69A; +} + +pre .variable { + color: #7587A6; +} + +pre .function, pre .entity.class { + color: #9B703F; +} + +pre .support.class-name, pre .support.type { + color: #AB99AC; +} diff --git a/themes/zenburnesque.css b/themes/zenburnesque.css new file mode 100644 index 00000000..fdbe9650 --- /dev/null +++ b/themes/zenburnesque.css @@ -0,0 +1,59 @@ +/** + * Zenburnesque theme + * + * Adapted from Ultraviolet RubyGem + * + * @author Flinn Mueller + * @version 1.0 + */ +pre { + background: #404040; + word-wrap: break-word; + margin: 0px; + padding: 10px; + color: #dedede; + font-size: 14px; + margin-bottom: 20px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #709070; + font-style: italic; +} + +pre .integer { + color: #22C0FF; +} + +pre .storage { + color: #6080FF; +} + +/* This includes regexes */ +pre .string { + color: #FF2020; +} + +pre .keyword, pre .selector { + color: #ffffa0; +} + +pre .inherited-class { + font-style: italic; +} + +pre .entity { + color: #F09040; +} + +pre .support { + color: #C83730; +} + +pre .variable.class { + color: #FF8000; +} diff --git a/util/builder.py b/util/builder.py new file mode 100644 index 00000000..9be89ebb --- /dev/null +++ b/util/builder.py @@ -0,0 +1,152 @@ +import os +import subprocess +import zipfile +import hashlib +import re +import glob +from zipfile import ZipFile +from StringIO import StringIO + + +class RainbowBuilder(object): + + def __init__(self, js_path, closure_path, theme_path=None): + + self.versions = { + 'c': '1.0.7', + 'csharp': '1.0.1', + 'coffeescript': '1.0', + 'css': '1.0.9', + 'd': '1.0', + 'generic': '1.0.13', + 'go': '1.0', + 'haskell': '1.0.1', + 'html': '1.0.9', + 'java': '1.0', + 'javascript': '1.0.9', + 'lua': '1.0.1', + 'php': '1.0.8', + 'python': '1.0.9', + 'r': '1.0', + 'ruby': '1.0.6', + 'scheme': '1.0', + 'shell': '1.0.3', + 'smalltalk': '1.0' + } + + self.js_path = js_path + self.closure_path = closure_path + self.js_files_to_include = [] + self.file_name = "" + self.theme_path = theme_path + + def getPathForLanguage(self, language): + return os.path.join(self.js_path, 'language/' + language + '.js') + + def getRainbowPath(self): + return os.path.join(self.js_path, 'rainbow.js') + + def verifyPaths(self): + if not os.path.exists(self.js_path): + raise Exception('directory does not exist at: %s' % self.js_path) + + if not os.path.isfile(self.closure_path): + raise Exception('closure compiler does not exist at: %s' % self.closure_path) + + def getZipForLanguages(self, languages, path=None): + self.verifyPaths() + + # strip out any duplicates + languages = list(set(languages)) + + write_to = StringIO() if path is None else path + zip_file = ZipFile(write_to, 'w') + zip_file.write(self.getRainbowPath(), 'rainbow.js', zipfile.ZIP_DEFLATED) + + # include minimized version even when downloading the dev version + zip_file.write(self.getRainbowPath().replace('.js', '.min.js'), 'rainbow.min.js', zipfile.ZIP_DEFLATED) + + # include themes as well + if self.theme_path: + files = glob.glob(self.theme_path + '/*.css') + for file_name in files: + zip_file.write(file_name, os.path.join('themes', os.path.basename(file_name)), zipfile.ZIP_DEFLATED) + + for language in languages: + zip_file.write(self.getPathForLanguage(language), os.path.join('language', language + '.js'), zipfile.ZIP_DEFLATED) + + zip_file.close() + + return write_to + + def openFile(self, path): + file = open(path, "r") + content = file.read() + file.close() + return content + + def writeFile(self, path, content): + file = open(path, "w") + file.write(content) + file.close() + + def getVersion(self): + contents = self.openFile(self.getRainbowPath()) + match = re.search(r'@version\s(.*)\s+?', contents) + return match.group(1) + + def getLanguageVersions(self, languages): + groups = [] + for language in languages: + if language in self.versions: + groups.append(language + ' v' + self.versions[language]) + + return ', '.join(groups) + + def getFileForLanguages(self, languages, cache=None): + self.verifyPaths() + + # strip out any duplicates + languages = list(set(languages)) + + self.js_files_to_include = [self.getRainbowPath()] + for language in languages: + path = self.getPathForLanguage(language) + if not os.path.exists(path): + continue + + self.js_files_to_include.append(path) + + self.file_name = 'rainbow' + ('-custom' if len(languages) else '') + '.min.js' + + if cache is not None: + version = self.getVersion() + cache_key = 'rainbow_' + hashlib.md5(self.getLanguageVersions(languages)).hexdigest() + '_' + version + cached_version = cache.get(cache_key) + if cached_version: + return cached_version + + command = ['java', '-jar', self.closure_path, '--compilation_level', 'ADVANCED_OPTIMIZATIONS'] + self.js_files_to_include + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, err = proc.communicate() + + if err: + print err + + lines = output.splitlines() + comments = lines[0:4] + version = comments[1].replace(' @version ', '') + url = comments[2].replace(' @url ', '') + new_comment = '/* Rainbow v' + version + ' ' + url + + if len(languages): + new_comment += ' | included languages: ' + ', '.join(languages) + + new_comment += ' */' + + output = new_comment + '\n' + '\n'.join(lines[4:]) + + if cache is not None: + cache.set(cache_key, output, 14400) # 4 hours + + return output diff --git a/util/compile.py b/util/compile.py new file mode 100755 index 00000000..e0540845 --- /dev/null +++ b/util/compile.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +import sys +import os +from builder import RainbowBuilder + +sys.argv.pop(0) +languages = sys.argv +languages.sort() + +no_language_args = ['--alone', '--forever-alone', '--core', '--no-languages', '--without-languages', '--none'] +rainbow_only = len(set(no_language_args) - set(sys.argv)) < len(no_language_args) + +if not rainbow_only: + languages.insert(0, 'generic') + +js_path = os.path.dirname(__file__) + '/../js/' + +for language in languages[:]: + if language.startswith('--'): + languages.remove(language) + +builder = RainbowBuilder(js_path, os.environ.get('CLOSURE_COMPILER', '/usr/local/compiler-latest/compiler.jar')) + +print 'waiting for closure compiler...' +contents = builder.getFileForLanguages(languages) + +print "\nincluded:" +for file in builder.js_files_to_include: + print " ", os.path.basename(file) +print "" + +print 'writing to file:', builder.file_name + +new_file = os.path.join(js_path, builder.file_name) + +file = open(new_file, "w") +file.write(contents) +file.close() From 8b85196be0bf0eabc97b12bb0d6aa1fbf1247677 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Fri, 22 Jan 2016 18:04:09 +0100 Subject: [PATCH 02/11] added scss code highlight --- js/language/scss.js | 830 +++++++++++++++++++++++++++++++++ tests/language/test.scss.js | 405 ++++++++++++++++ tests/rainbow.html | 2 + themes/solarized-dark-scss.css | 175 +++++++ 4 files changed, 1412 insertions(+) create mode 100644 js/language/scss.js create mode 100644 tests/language/test.scss.js create mode 100644 themes/solarized-dark-scss.css diff --git a/js/language/scss.js b/js/language/scss.js new file mode 100644 index 00000000..ef987a81 --- /dev/null +++ b/js/language/scss.js @@ -0,0 +1,830 @@ +/** + * SCSS patterns + * + * @author Fabio Bucci Di Monte + * @version 1.0.1 + */ +!function(){ + + 'use strict'; + + if(!Rainbow){ + return; + } + + var regexes = { + + /* common */ + anyChars : /(.+)/g,// <- added for SCSS + commonName : /(?=-?[\w#]+)((?:[\w-]+)*(?:#\{.+?})?(?:[\w-]*\w+)*)/g,// <- enhanced for SCSS (interpolations) + parameters : /\((.*)\)/g,// <- enhanced for SCSS (accepts void parameters) + parametersEnhanced : /\((.*)\)/g,// <- added for SCSS (same regex as above) + + /* reserved words / symbols */ + atDirective : /(?:@[a-z]+)/g, + exception : /!(?:important|default|optional)(?= *;)/g,// <- enhanced for SCSS + reserved : /(?:and|or|not|in|from|through|to(?! (?:top|bottom|right|left)))/g,// <- enhanced for SCSS + + /* comments */ + commentMulti : /\/\*([^]*?)\*\//gm, + commentSingle : /\/\/(.*?)$/gm,// <- added for SCSS + + /* units */ + unitAbsolute : /(?:p[xtc]|[cm]m|in)/g, + unitRelative : /(?:e[mx]|rem|ch)/g, + unitViewport : /(?:v(?:h|w|m(?:in|ax)))/g, + unitAngle : /(?:deg|g?rad|turn)/g, + unitTime : /(?:m?s)/g, + unitFrequency : /(?:k?Hz)/g, + unitResolution : /(?:dp(?:i|cm|px))/g, + unitPercentage : /%/g, + + /* values */ + valueText : /\b\w+(?:-\w+)*\b/g, + valueString : /('|")(.*?)\1/g,// <- enhanced for SCSS (grouped string content for interpolations recognition) + valuePath : /[\w/-]+\.[a-z0-9]{3,4}\b(?!["'])/gi, + valueHex : /#(?:[a-f0-9]{6}|[a-f0-9]{3})\b/gi, + valueNumber : /(-?(?:\d*\.)?\d+)((?:p[xtc]|[cm]m|in)|(?:e[mx]|rem|ch)|(?:v(?:h|w|m(?:in|ax)))|(?:deg|g?rad|turn)|(?:m?s)|(?:k?Hz)|(?:dp(?:i|cm|px))|%)?/g, + + /* css rules */ + prefix : /-(?:webkit|moz|ms|o)-/g, + cssProperty : /([a-z-]+)/g, + cssMethod : /(-?\w+(?:-\w+)*)(\(.+\))/g, + cssMethodEnhanced : /(?=(?:-?[\w#\{$} "'!=*/+])+\(.*\))((?:[\w-]+)*(?:#\{.+?})?(?:[\w-]*\w+)*)(\(.*\))/g,// <- added for SCSS + cssSelector : /((?:[\w.#:()%+>~\[="\] -]|\{.+}|,\s?|&(?:gt|amp);)+)(?=\{)/g,// <- enhanced for SCSS (interpolations, placeholders, parent selectors) + + /* ie hacks */ + cssPropertyWithHack : /([\*\+#_])?([a-z-]+)( *\/\*(?:\\\*)?\*\/)?(?= *:)/g, + hackValue : /((?:\\0|\\9|\\0\\9|\\9\\0)|!\w+?|\\0\/)(?= *[;}])/g, + ieHacks : /(_)|(\*|#|!.+)|(\*?\+|\+\*?)|(\\9)|(\\0\\9|\\9\\0)|(\/\*(?:\\\*)?\*\/)|(\\0\/)/g, + + // TODO: improve entity regexes - eg. change [\w-]+ into \w+(?:-\w+)* + /* entities */ + entityClass : /\.(?!\.)[\w-]*(#\{.+?})?[\w-]*/g,// <- enhanced for SCSS (interpolations) + entityId : /#(?!#[^\{])[\w-]*(#\{.+?})?[\w-]*/g,// <- enhanced for SCSS (interpolations) + entityPseudo : /::?(?!::?)[a-z-]*(#\{.+?})?[a-z-]*/g,// <- enhanced for SCSS (interpolations) + entityTag : /[a-z]+\d?(#\{.+?})?(?! *:.*;)/gi,// <- enhanced for SCSS (interpolations) + entityAttribute : /\[([\w#\{$}-]+)=(("|')?[\w#\{$}-]+\3)]/g,// <- enhanced for SCSS (interpolations) + entityParent : /(?:&|&)(?!(?:&|&))/g,// <- added for SCSS + entityPlaceholder : /%(?!%)\w+(?:-\w+)*/g,// <- added for SCSS + + /* siblings */ + directChild : / ?(?:>|>) ?/g, + siblingGeneral : / ?~ ?/g, + siblingAdjacent : / ?\+ ?/g, + + /* media queries */ + mediaReserved : /\b(?:not|only|and)\b/g, + mediaType : /\b(?:all|aural|braille|handheld|print|projection|screen|tty|tv|embossed)\b/g, + mediaFeature : /\b(?:(?:(?:min|max)-)?(?:(?:device-)?(?:width|height|aspect-ratio)|color(?:-index)?|monochrome|resolution)|scan|grid|orientation)\b/g, + mediaExpression : /\((.+?)(?: *: *(.+?))?\)/g, + mediaQuery : /(?:(not|only) +)?(.+)/g, + mediaQueryList : / *([^,\n\r]+) */g, + mediaQueryRule : /(@media) +(.+)(?=\{)/g, + + /* scss only */ + constant : /\b(?:true|false|null)\b/g, + variable : /\$\w+(?:-\w+)*/g, + operator : /(&[lg]t;|<|>|==|!=|\/|\*|\+|\-)(?!\1)/g, + interpolation : /#\{(.+?)}/g, + + /* scss directives */ + scssList : /\(((?:[^,\s]+ ?, ?)+)([^,\s]+)\)/g, + scssExtend : /(@extend) +([.#%]?\w+(?:-\w+)*)(?= *;)/g, + scssImport : /(@import) +(('|").+\3)(?= *;)/g, + scssInclude : /(@include) +(\w+(?:-\w+)*)(\(.*\))?(?= *(?:;|\{))/g, + scssMethod : /(@function) +(\w+(?:-\w+)*)(\(.*\))/g, + scssMixin : /(@mixin) +(\w+(?:-\w+)*)(?: *(\(.*\)))?(?= *\{)?/g, + scssLoopFor : /(@for) +(.+)(?= *\{)/g, + scssLoopEach : /(@each) +(.+)(?= *\{)/g, + scssLoopWhile : /(@while)(?: |\((?=.+\)))+(.+?)\)?(?= *\{)/g, + scssCondition : /(@(?:else )?if)(?: |\((?=.+\)))+(.+?)\)?(?= *\{)/g, + scssTernary : /\b(if) *(\(.+,.+,.+\))/g, + scssMaps : /\(((?:\s*(?:("|')?\w+(?:-\w+)*\2 *: *.+|\/\*[^]*?\*\/|\/\/.*?$))+\s*\))(?=;)/gm, + + /* scss directives syntax */ + scssLoopForSyntax : /(.+) +(from) +(.+) +(to|through) +(.+)/g, + scssLoopEachSyntax : /(.+) +(in) +(.+)/g, + scssLoopWhileSyntax : /(.+) +(.+) +(.+)/g, + scssTernarySyntax : /\((.+),(.+),(.+)\)/g, + scssMapsSyntax : /(("|')?\w+(?:-\w+)*\2) *: *((?:[\w"' $\(\),\*\+-]|\/(?!\/))+)(?=,.*\n?|\n?\s*\)| \/\/)/g + + }; + + /******************** + * @ DIRECTIVES + ********************/ + + var atDirective = { + name: 'at-directive', + pattern: regexes.atDirective + }; + + /******************** + * RESERVED WORDS / SYMBOLS + ********************/ + + var reserved = { + name: 'reserved', + pattern: regexes.reserved + }; + + var exception = { + name: 'keyword.exception', + pattern: regexes.exception + }; + + var operator = { + name: 'constant.operator', + pattern: regexes.operator + }; + + var constant = { + name: 'scss.constant', + pattern: regexes.constant + }; + + var variable = { + name: 'scss.variable', + pattern: regexes.variable + }; + + /******************** + * TYPES + ********************/ + + var unit = { + absolute: { + name: 'keyword.unit.absolute', + pattern: regexes.unitAbsolute + }, + relative: { + name: 'keyword.unit.relative', + pattern: regexes.unitRelative + }, + viewport: { + name: 'keyword.unit.viewport', + pattern: regexes.unitViewport + }, + angle: { + name: 'keyword.unit.angle', + pattern: regexes.unitAngle + }, + time: { + name: 'keyword.unit.time', + pattern: regexes.unitTime + }, + frequency: { + name: 'keyword.unit.frequency', + pattern: regexes.unitFrequency + }, + resolution: { + name: 'keyword.unit.resolution', + pattern: regexes.unitResolution + }, + percentage: { + name: 'keyword.unit.percentage', + pattern: regexes.unitPercentage + } + }; + + var type = { + attribute: { + name: 'support.attribute-name', + pattern: regexes.valueText + }, + text: { + name: 'support.text-value', + pattern: regexes.valueText + }, + string: { + name: 'string', + pattern: regexes.valueString + }, + path: { + name: 'constant.path', + pattern: regexes.valuePath + }, + hex: { + name: 'constant.hex-color', + pattern: regexes.valueHex + }, + number: { + name: 'constant.numeric', + matches: { + 2: [ + unit.absolute, + unit.relative, + unit.viewport, + unit.angle, + unit.time, + unit.frequency, + unit.resolution, + unit.percentage + ] + }, + pattern: regexes.valueNumber + } + }; + + var arr_parameters = [ + operator, + variable, + constant, + type.number, + type.path, + type.text, + type.string, + type.hex + ]; + + var scssList = { + name: 'scss.list', + matches: { + 1: arr_parameters, + 2: arr_parameters + }, + pattern: regexes.scssList + }; + + arr_parameters.unshift(scssList); + + /******************** + * HACKS (css attributes) + ********************/ + + var ieHacks = { + name: 'hack', + matches: { + 1: 'ie-6', + 2: 'ie-lte7', + 3: 'ie-7', + 4: 'ie-lte9', + 5: 'ie-9', + 6: 'ie-gt6', + 7: 'ie-8-9' + }, + pattern: regexes.ieHacks + }; + + var cssHacks = { + matches: { + 1: [ieHacks] + }, + pattern: regexes.hackValue + }; + + /******************** + * CSS PROPERTIES / METHODS + ********************/ + + var prefix = { + name: 'vendor-prefix', + pattern: regexes.prefix + }; + + var cssProperties = { + name: 'css-property', + matches: { + 1: [prefix] + }, + pattern: regexes.cssProperty + }; + + var cssPropertiesHacked = { + matches: { + 1: [ieHacks], + 2: [cssProperties], + 3: [ieHacks] + }, + pattern: regexes.cssPropertyWithHack + }; + + var cssMethods = { + name: 'css-method', + matches: { + 1: [ + { + name: 'method-name', + matches: { + 1: [prefix] + }, + pattern: regexes.commonName + } + ], + 2: [ + { + name: 'method-params', + matches: { + 1: arr_parameters + }, + pattern: regexes.parameters + } + ] + }, + pattern: regexes.cssMethod + }; + + // method inside a method (as parameter) – note: weirdly there is no easier way to achieve the same result.. + var cssMethodsEnhanced = { + name: cssMethods.name, + matches: { + 1: cssMethods.matches[1], + 2: [ + { + name: cssMethods.matches[2][0].name, + matches: { + 1: [cssMethods].concat(arr_parameters) + }, + pattern: regexes.parametersEnhanced// same as regexps.parameters (won't work if using the same) (??) + } + ] + }, + pattern: regexes.cssMethodEnhanced + }; + + var interpolation = { + name: 'scss.interpolation', + matches: { + 1: [cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.interpolation + }; + + // interpolation inside a string + arr_parameters.unshift({ + name: 'string', + matches: { + 2: [interpolation] + }, + pattern: regexes.valueString + }); + + // interpolation inside method name + // TODO (fix) weirdly will not consider methods inside interpolation that is part of another method's name + // eg: #{unquote($type)}-gradient($params); // unquote() method will not be recognized + cssMethodsEnhanced.matches['1'][0].matches['1'] = [prefix,interpolation]; + + // interpolation inside method parameters + cssMethodsEnhanced.matches['2'][0].matches['1'] = [cssMethods].concat(arr_parameters); + + /******************** + * COMMENTS + ********************/ + + var commentMultiline = { + name: 'comment', + matches: { + 1: [interpolation] + }, + pattern: regexes.commentMulti + }; + + var commentSingleline = { + name: 'scss.comment', + matches: { + 1: [interpolation] + }, + pattern: regexes.commentSingle + }; + + var arr_comments = [ + commentMultiline, + commentSingleline + ]; + + /******************** + * SELECTORS + ********************/ + + var entity = { + scssParent: { + name: 'entity.scss.parent', + pattern: regexes.entityParent + }, + scssPlaceholder: { + name: 'entity.scss.placeholder', + pattern: regexes.entityPlaceholder + }, + cssClass: { + name: 'entity.name.class', + matches: { + 1: [interpolation] + }, + pattern: regexes.entityClass + }, + cssId: { + name: 'entity.name.id', + matches: { + 1: [interpolation] + }, + pattern: regexes.entityId + }, + cssAttribute : { + name: 'entity.name.attribute', + matches: { + 1: [ + interpolation, + type.attribute + ], + 2: [ + interpolation, + type.text, + type.string + ] + }, + pattern: regexes.entityAttribute + }, + cssPseudo: { + name: 'entity.name.pseudo', + matches: { + 1: [interpolation] + }, + pattern: regexes.entityPseudo + }, + cssTag: { + name: 'entity.name.tag', + matches: { + 1: [interpolation] + }, + pattern: regexes.entityTag + } + }; + + var sibling = { + direct: { + name: 'direct-child', + pattern: regexes.directChild + }, + general: { + name: 'general-sibling', + pattern: regexes.siblingGeneral + }, + adjacent: { + name: 'adjacent-sibling', + pattern: regexes.siblingAdjacent + } + }; + + var arr_siblings = [ + sibling.direct, + sibling.general, + sibling.adjacent + ]; + + var arr_commonEntities = [ + entity.scssPlaceholder, + entity.cssClass, + entity.cssId, + entity.cssTag + ]; + + var arr_entities = arr_commonEntities.concat([ + entity.cssPseudo, + entity.cssAttribute, + entity.scssParent + ]); + + var cssSelectors = { + name: 'selector', + matches: { + 1: arr_siblings.concat([reserved]).concat(arr_entities) + }, + pattern: regexes.cssSelector + }; + + /******************** + * MEDIA QUERIES + * ref: https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Pseudo-BNF_%28for_those_of_you_that_like_that_kind_of_thing%29 + ********************/ + + var media = { + reserved: { + name: 'mediaquery.reserved', + pattern: regexes.mediaReserved + }, + types: { + name: 'mediaquery.type', + pattern: regexes.mediaType + }, + features: { + name: 'mediaquery.feature', + pattern: regexes.mediaFeature + } + }; + + media.expression = { + name: 'mediaquery.expression', + matches: { + 1: [media.features,variable,interpolation], + 2: [cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.mediaExpression + }; + + media.query = { + name: 'mediaquery.query', + matches: { + 1: [media.reserved], + 2: [ + interpolation, + media.reserved, + media.types, + media.expression + ] + }, + pattern: regexes.mediaQuery + }; + + var mediaQuery = { + name: 'media-query', + matches: { + 1: 'at-directive', + 2: [ + { + matches: { + 1: [media.query] + }, + pattern: regexes.mediaQueryList + } + ] + }, + pattern: regexes.mediaQueryRule + }; + + /******************** + * SASS EXTRA + ********************/ + + var maps = { + name: 'scss.map', + matches: { + 1: arr_comments.concat([ + { + name: 'scss.map-pair', + matches: { + 1: 'scss.map-name', + 3: arr_comments.concat([ + { + name: 'scss.map-value', + matches: { + 1: arr_parameters + }, + pattern: regexes.anyChars + } + ]) + }, + pattern: regexes.scssMapsSyntax + } + ]) + }, + pattern: regexes.scssMaps + }; + + var mixin = { + name: 'scss.mixin', + matches: { + 1: [atDirective], + 2: 'scss.mixin-name', + 3: [ + { + name: 'scss.mixin-params', + matches: { + 1: [cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.parameters + } + ] + }, + pattern: regexes.scssMixin + }; + + // TODO: consider to let all loops recognize methods inside loop expressions + var loops = { + forLoop: { + name: 'scss.loop.for', + matches: { + 1: [atDirective], + 2: [ + { + name: 'scss.loop-condition', + matches: { + 1: [variable], + 2: [reserved], + 3: [ + variable, + type.number + ], + 4: [reserved], + 5: [ + variable, + type.number + ] + }, + pattern: regexes.scssLoopForSyntax + } + ] + }, + pattern: regexes.scssLoopFor + }, + eachLoop: { + name: 'scss.loop.each', + matches: { + 1: [atDirective], + 2: [ + { + name: 'scss.loop-condition', + matches: { + 1: [variable], + 2: [reserved], + 3: [ + scssList, + variable, + type.text + ] + }, + pattern: regexes.scssLoopEachSyntax + } + ] + }, + pattern: regexes.scssLoopEach + }, + whileLoop: { + name: 'scss.loop.while', + matches: { + 1: [atDirective], + 2: [ + { + name: 'scss.loop-condition', + matches: { + 1: [ + variable, + type.number + ], + 2: [operator], + 3: [ + variable, + type.number + ] + }, + pattern: regexes.scssLoopWhileSyntax + } + ] + }, + pattern: regexes.scssLoopWhile + } + }; + + var ternary = { + name: 'scss.condition.ternary', + matches: { + 1: 'scss.ternary-method', + 2: [ + { + name: 'scss.ternary-params', + matches: { + 1: [ + { + name: 'ternary-condition', + matches: { + 1: [cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.anyChars + } + ], + 2: [ + { + name: 'ternary-true', + matches: { + 1: [cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.anyChars + } + ], + 3: [ + { + name: 'ternary-false', + matches: { + 1: [cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.anyChars + } + ] + }, + pattern: regexes.scssTernarySyntax + } + ] + }, + pattern: regexes.scssTernary + }; + + var condition = { + name: 'scss.condition', + matches: { + 1: 'at-directive', + 2: [ + { + name: 'scss.condition-expression', + matches: { + 1: [reserved,cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.anyChars + } + ] + }, + pattern: regexes.scssCondition + }; + + var method = { + name: 'scss.method', + matches: { + 1: [atDirective], + 2: 'scss.method-name', + 3: [ + { + name: 'method-params', + matches: { + 1: [cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.parameters + } + ] + }, + pattern: regexes.scssMethod + }; + + var extend = { + name: 'scss.extend', + matches: { + 1: [atDirective], + 2: arr_commonEntities + }, + pattern: regexes.scssExtend + }; + + var imports = { + name: 'scss.import', + matches: { + 1: [atDirective], + 2: [type.string] + }, + pattern: regexes.scssImport + }; + + var include = { + name: 'scss.include', + matches: { + 1: [atDirective], + 2: 'scss.mixin-name', + 3: [ + { + name: 'scss.mixin-params', + matches: { + 1: [cssMethodsEnhanced].concat(arr_parameters) + }, + pattern: regexes.parameters + } + ] + }, + pattern: regexes.scssInclude + }; + + /******************** + * RAINBOW EXTENSION + ********************/ + + var cssSyntaxEnhanced = [] + .concat([ + commentMultiline, + exception, + mediaQuery, + atDirective, + cssSelectors, + cssPropertiesHacked, + cssHacks + ]) + .concat(arr_parameters) + .concat([ + cssMethodsEnhanced + ]); + + var scssSyntax = [ + commentSingleline, + interpolation, + maps, + mixin, + loops.forLoop, + loops.eachLoop, + loops.whileLoop, + ternary, + condition, + method, + extend, + imports, + include + ]; + + Rainbow.extend('scss', cssSyntaxEnhanced, true); + Rainbow.extend('scss', scssSyntax, true); + +}(); diff --git a/tests/language/test.scss.js b/tests/language/test.scss.js new file mode 100644 index 00000000..afd50e84 --- /dev/null +++ b/tests/language/test.scss.js @@ -0,0 +1,405 @@ +/* global describe, run */ +var language = 'scss'; + +describe(language, function() { + + /******************** + * FORMER CSS VALIDATION (moved in from test.css.js) + ********************/ + + run( + language, + + 'scss', + + 'article {\n' + + ' &.cool {\n' + + ' p {\n' + + ' margin-top: 20px;\n' + + ' }\n' + + ' }\n' + + '}', + + 'article {\n' + + ' &.cool {\n' + + ' p {\n' + + ' margin-top: 20px;\n' + + ' }\n' + + ' }\n' + + '}' + ); + + /******************** + * CSS ENHANCED + ********************/ + + // attribute selector + run( + language, + + 'attribute selector', + + 'input[type="text"] { cursor: pointer; }\n'+ + 'input[type=text] { cursor: pointer; }', + + 'input[type="text"] { cursor: pointer; }\n'+ + 'input[type=text] { cursor: pointer; }' + ); + + // siblings + run( + language, + + 'child/sibling selectors', + + 'div > p,\n'+ + 'p + p,\n'+ + 'p ~ span {', + + ''+ + 'div > p,\n'+ + 'p + p,\n'+ + 'p ~ span '+ + '{' + ); + + // exception (!important keyword) + run( + language, + + 'keyword exception', + + 'border: none !important;', + + 'border: none !important;' + ); + + // css method + run( + language, + + 'css-method', + + 'rgba(100, 200, 175, .6);', + + ''+ + 'rgba'+ + '('+ + '100, '+ + '200, '+ + '175, '+ + '.6)'+ + ';' + ); + + // asset path + run( + language, + + 'asset path', + + 'url(/path/to/image.jpg);', + + ''+ + 'url'+ + '('+ + '/path/to/image.jpg)'+ + ';' + ); + + // media queries + run( + language, + + 'media query', + + '@media not print and (max-width: 1160px), screen and (orientation: landscape) {', + + ''+ + '@media '+ + ''+ + 'not '+ + 'print '+ + 'and '+ + '('+ + 'max-width: '+ + '1160px)'+ + ', '+ + ''+ + 'screen '+ + 'and '+ + '('+ + 'orientation: '+ + 'landscape)'+ + ' '+ + '{' + ); + + /******************** + * SCSS RESERVED + ********************/ + + // variable + run( + language, + + 'variable', + + '$variable', + + '$variable' + ); + + /******************** + * PLACEHOLDERS + ********************/ + + // placeholder selector + run( + language, + + 'placeholder selector', + + '%placeholder {', + + ''+ + '%placeholder '+ + '{' + ); + + // extend placeholder (@extend directive) + run( + language, + + 'extend placeholder', + + '@extend %placeholder;', + + ''+ + '@extend '+ + '%placeholder'+ + ';' + ); + + /******************** + * MIXINS + ********************/ + + // mixin declaration (@mixin directive) + run( + language, + + 'mixin declaration', + + '@mixin rem($property, $size: 1) {', + + ''+ + '@mixin '+ + 'rem'+ + '('+ + '$property, '+ + '$size: '+ + '1)'+ + ' {' + ); + + // mixin usage (@include directive) + run( + language, + + 'mixin usage', + + '@include rem(\'margin-bottom\', 16);', + + ''+ + '@include '+ + 'rem'+ + '('+ + '\'margin-bottom\', '+ + '16)'+ + ';' + ); + + /******************** + * LOOPS + ********************/ + + // for loop (@for directive) + run( + language, + + 'for loop', + + '@for $i from 1 through 3 {', + + ''+ + '@for '+ + ''+ + '$i '+ + 'from '+ + '1 '+ + 'through '+ + '3 '+ + '{' + ); + + // each loop (@each directive) + run( + language, + + 'each loop', + + '@each $animal in puma, sea-slug, egret, salamander {', + + ''+ + '@each '+ + ''+ + '$animal '+ + 'in '+ + 'puma, '+ + 'sea-slug, '+ + 'egret, '+ + 'salamander '+ + '{' + ); + + // while loop (@while directive) + run( + language, + + 'while loop', + + '@while $i > 0 {', + + ''+ + '@while '+ + ''+ + '$i '+ + '> '+ + '0'+ + ' {' + ); + + /******************** + * FUNCTIONS + ********************/ + + // function declaration (@function directive) + run( + language, + + 'function declaration', + + '@function grid-width($n) {', + + ''+ + '@function '+ + 'grid-width'+ + '('+ + '$n)'+ + ' {' + ); + + /******************** + * CONDITIONS + ********************/ + + // if/else condition (@if/@else directive) + run( + language, + + 'if/else condition', + + '@if $var==true {\n'+ + ' // result if true\n'+ + '} @else if $var==false {\n'+ + ' // result if false\n'+ + '} @else {\n'+ + ' // result if null\n'+ + '}', + + '@if $var==true {\n'+ + ' // result if true\n'+ + '} @else if $var==false {\n'+ + ' // result if false\n'+ + '} @else {\n'+ + ' // result if null\n'+ + '}' + ); + + // ternary condition + run( + language, + + 'ternary condition', + + 'if($bigger==true,28px,24px);', + + ''+ + 'if'+ + '('+ + ''+ + '$bigger'+ + '=='+ + 'true'+ + ','+ + ''+ + '28px'+ + ','+ + ''+ + '24px'+ + ')'+ + ';' + ); + + /******************** + * INTERPOLATIONS + ********************/ + + // simple interpolation + run( + language, + + 'simple interpolation', + + '#{$variable}', + + ''+ + '#{$variable}'+ + '' + ); + + // interpolation as part of selector + run( + language, + + 'interpolation as part of selector', + + '.#{$animal}-icon {', + + ''+ + '.'+ + ''+ + '#{$animal}'+ + ''+ + '-icon '+ + '{' + ); + + // interpolation inside a string + run( + language, + + 'interpolation inside a string', + + '"/images/#{$animal}.png"', + + ''+ + '"/images/'+ + ''+ + '#{$animal}'+ + '.png"'+ + '' + ); + +}); diff --git a/tests/rainbow.html b/tests/rainbow.html index 2ac44d7d..15330280 100644 --- a/tests/rainbow.html +++ b/tests/rainbow.html @@ -18,6 +18,7 @@ + @@ -48,6 +49,7 @@ + diff --git a/themes/solarized-dark-scss.css b/themes/solarized-dark-scss.css new file mode 100644 index 00000000..023639be --- /dev/null +++ b/themes/solarized-dark-scss.css @@ -0,0 +1,175 @@ +/** + * Solarized Dark theme + * + * Adaptation of Solarized Dark from ethanschoonover.com/solarized + * + * @author Ethan Schoonover + * @author David Litmark + * @version 1.0.0 + */ +pre { + background: #002b36; /* base03 */ + border-color: #333333; + color: #839496; /* base0 */ + font-size: 16px; + line-height: 20px; + margin: 0; + padding: 10px; +} + +pre, code { + font-family: 'Monaco', courier, monospace; +} + +pre .comment { + color: #586e75; /* base01 */ +} + +pre .constant { + color: #839496; /* base0 */ +} + +pre .constant.language { + color: #268bd2; /* blue */ +} + +pre .constant.regexp { + color: #2aa198; /* cyan */ +} + +pre .storage { + color: #268bd2; /* blue */ +} + +pre .string, pre .comment.docstring { + color: #2aa198; /* cyan */ +} + +pre .support.tag.script, pre .support.tag.style { + color: #2aa198; /* cyan */ +} + +pre .string.regexp { + color: #2aa198; /* cyan */ +} + +pre .string.regexp.open, pre .string.regexp.close { + color: #2aa198; /* cyan */ +} + +pre .keyword, pre .selector { + color: #859900; /* green */ +} + +pre .inherited-class { + font-style: italic; +} + +pre .entity { + color: #b58900; /* yellow */ +} + +pre .support, *[data-language="c"] .function.call { + color: #859900; /* green */ +} + +pre .support.method { + color: #839496; /* base0 */ +} + +pre .support.property { + color: #839496; /* base0 */ +} + +pre .variable.global, pre .variable.class, pre .variable.instance { + color: #839496; /* base0 */ +} + +/** + * Solarized Dark theme + * Additional styles for SCSS syntax + * + * @author Fabio Bucci Di Monte + * @version 1.0.1 + */ + +pre .mediaquery.type { + color: #b58900; /* yellow */ +} + +pre .mediaquery.feature { + color: #8866dd; /* purple */ +} + +pre .method-name { + color: #c5ab0b; /* light yellow */ +} + +pre .constant.path { + color: #268bd2; /* blue */ +} + +pre .constant.numeric { + color: #2aa198; /* cyan */ +} + +pre .constant.hex-color { + color: #2aa198; /* cyan */ +} + +pre .css-property { + color: #8866dd; /* purple */ +} + +pre .selector { + color: #839496; /* base0 */ +} + +pre .entity.tag { + color: #ad5a18; /* orange */ +} + +pre .pseudo, +pre .vendor-prefix { + font-style: italic; +} + +pre .vendor-prefix { + opacity: 0.7; +} + +pre .attribute-name { + color: #b58900; /* yellow */ +} + +pre .at-directive, +pre .reserved, +pre .exception { + color: #b58900; /* yellow */ + font-weight: bold; +} + +pre .scss.interpolation { + background-color: #17436e; /* dark blue */ + color: #839496; /* base0 */ + font-style: italic; +} + +pre .scss.variable { + color: #268bd2; /* blue */ +} + +pre .scss.constant { + color: #859900; /* green */ + font-weight: bold; +} + +pre .scss.method-name, +pre .scss.mixin-name { + color: #c5ab0b; /* light yellow */ +} + +pre .scss.ternary-method { + color: #b58900; /* yellow */ + font-weight: bold; +} From b3f122bed1ea9f481f904f7d6e508c3370461c26 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Fri, 22 Jan 2016 18:07:01 +0100 Subject: [PATCH 03/11] added gitignore file --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a0c7ff00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +*.sw? +js/rainbow-custom.min.js +.AppleDouble +node_modules From 1cbdd5a4ddda36ba7a8123cce2a7bae066947622 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Mon, 25 Jan 2016 13:08:56 +0100 Subject: [PATCH 04/11] enhanced css styles --- themes/solarized-dark-scss.css | 175 --------------------------------- themes/solarized-dark.css | 73 +++++++++++++- 2 files changed, 72 insertions(+), 176 deletions(-) delete mode 100644 themes/solarized-dark-scss.css diff --git a/themes/solarized-dark-scss.css b/themes/solarized-dark-scss.css deleted file mode 100644 index 023639be..00000000 --- a/themes/solarized-dark-scss.css +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Solarized Dark theme - * - * Adaptation of Solarized Dark from ethanschoonover.com/solarized - * - * @author Ethan Schoonover - * @author David Litmark - * @version 1.0.0 - */ -pre { - background: #002b36; /* base03 */ - border-color: #333333; - color: #839496; /* base0 */ - font-size: 16px; - line-height: 20px; - margin: 0; - padding: 10px; -} - -pre, code { - font-family: 'Monaco', courier, monospace; -} - -pre .comment { - color: #586e75; /* base01 */ -} - -pre .constant { - color: #839496; /* base0 */ -} - -pre .constant.language { - color: #268bd2; /* blue */ -} - -pre .constant.regexp { - color: #2aa198; /* cyan */ -} - -pre .storage { - color: #268bd2; /* blue */ -} - -pre .string, pre .comment.docstring { - color: #2aa198; /* cyan */ -} - -pre .support.tag.script, pre .support.tag.style { - color: #2aa198; /* cyan */ -} - -pre .string.regexp { - color: #2aa198; /* cyan */ -} - -pre .string.regexp.open, pre .string.regexp.close { - color: #2aa198; /* cyan */ -} - -pre .keyword, pre .selector { - color: #859900; /* green */ -} - -pre .inherited-class { - font-style: italic; -} - -pre .entity { - color: #b58900; /* yellow */ -} - -pre .support, *[data-language="c"] .function.call { - color: #859900; /* green */ -} - -pre .support.method { - color: #839496; /* base0 */ -} - -pre .support.property { - color: #839496; /* base0 */ -} - -pre .variable.global, pre .variable.class, pre .variable.instance { - color: #839496; /* base0 */ -} - -/** - * Solarized Dark theme - * Additional styles for SCSS syntax - * - * @author Fabio Bucci Di Monte - * @version 1.0.1 - */ - -pre .mediaquery.type { - color: #b58900; /* yellow */ -} - -pre .mediaquery.feature { - color: #8866dd; /* purple */ -} - -pre .method-name { - color: #c5ab0b; /* light yellow */ -} - -pre .constant.path { - color: #268bd2; /* blue */ -} - -pre .constant.numeric { - color: #2aa198; /* cyan */ -} - -pre .constant.hex-color { - color: #2aa198; /* cyan */ -} - -pre .css-property { - color: #8866dd; /* purple */ -} - -pre .selector { - color: #839496; /* base0 */ -} - -pre .entity.tag { - color: #ad5a18; /* orange */ -} - -pre .pseudo, -pre .vendor-prefix { - font-style: italic; -} - -pre .vendor-prefix { - opacity: 0.7; -} - -pre .attribute-name { - color: #b58900; /* yellow */ -} - -pre .at-directive, -pre .reserved, -pre .exception { - color: #b58900; /* yellow */ - font-weight: bold; -} - -pre .scss.interpolation { - background-color: #17436e; /* dark blue */ - color: #839496; /* base0 */ - font-style: italic; -} - -pre .scss.variable { - color: #268bd2; /* blue */ -} - -pre .scss.constant { - color: #859900; /* green */ - font-weight: bold; -} - -pre .scss.method-name, -pre .scss.mixin-name { - color: #c5ab0b; /* light yellow */ -} - -pre .scss.ternary-method { - color: #b58900; /* yellow */ - font-weight: bold; -} diff --git a/themes/solarized-dark.css b/themes/solarized-dark.css index 7f77841e..3e254446 100644 --- a/themes/solarized-dark.css +++ b/themes/solarized-dark.css @@ -13,7 +13,6 @@ pre { margin: 0px; padding: 10px; color: #839496; /* base0 */ - font-size: 14px; margin-bottom: 20px; } @@ -84,3 +83,75 @@ pre .support.property { pre .variable.global, pre .variable.class, pre .variable.instance { color: #839496; /* base0 */ } + +/** + * Solarized Dark theme + * + * Additional styles for SCSS syntax + */ + +pre { + border-color: #333333; + font-size: 16px; + line-height: 20px; +} + +pre .entity, pre .mediaquery.type { + font: inherit; +} + +pre .scss.interpolation, pre .pseudo, pre .vendor-prefix { + font-style: italic; +} + +pre .scss.constant, pre .at-directive, pre .reserved, pre .exception, pre .scss.ternary-method { + font-weight: bold; +} + +pre .hack { + color: #ff0000; /* red */ +} + +pre .scss.interpolation, pre .scss.map-pair, pre .selector { + color: #839496; /* base0 */ +} + +pre .at-directive, pre .reserved, pre .exception, pre .scss.ternary-method, pre .attribute-name, pre .mediaquery.type { + color: #b58900; /* yellow */ +} + +pre .method-name, pre .scss.method-name, pre .scss.mixin-name { + color: #c5ab0b; /* light yellow */ +} + +pre .constant.numeric, pre .constant.hex-color { + color: #2aa198; /* cyan */ +} + +pre .css-property, pre .mediaquery.feature, pre .scss.map-name, pre .scss.map-value { + color: #8866dd; /* purple */ +} + +pre .entity.tag { + color: #ad5a18; /* orange */ +} + +pre .constant.path, pre .scss.variable { + color: #268bd2; /* blue */ +} + +pre .scss.constant { + color: #859900; /* green */ +} + +pre .scss.interpolation { + background-color: #17436e; /* dark blue */ +} + +pre .scss.map-pair { + background-color: #26254a; /* dark purple */ +} + +pre .vendor-prefix { + opacity: 0.7; +} From 8d5e374c629672d603243976f02bdbe7eff27465 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Mon, 25 Jan 2016 16:38:28 +0100 Subject: [PATCH 05/11] versioning fixes as suggested in PR comments --- themes/solarized-dark.css | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/themes/solarized-dark.css b/themes/solarized-dark.css index 3e254446..8cd43dec 100644 --- a/themes/solarized-dark.css +++ b/themes/solarized-dark.css @@ -5,7 +5,8 @@ * * @author Ethan Schoonover * @author David Litmark - * @version 1.0.0 + * @author Fabio Bucci Di Monte (Additional styles for enhanced CSS syntax and SCSS syntax) + * @version 1.1.0 */ pre { background: #002b36; /* base03 */ @@ -14,6 +15,9 @@ pre { padding: 10px; color: #839496; /* base0 */ margin-bottom: 20px; + border-color: #333333; + font-size: 16px; + line-height: 20px; } pre, code { @@ -85,26 +89,20 @@ pre .variable.global, pre .variable.class, pre .variable.instance { } /** - * Solarized Dark theme - * - * Additional styles for SCSS syntax + * Additional styles for enhanced CSS syntax and SCSS syntax */ -pre { - border-color: #333333; - font-size: 16px; - line-height: 20px; -} - pre .entity, pre .mediaquery.type { font: inherit; } -pre .scss.interpolation, pre .pseudo, pre .vendor-prefix { +pre .pseudo, pre .vendor-prefix, +pre .scss.interpolation { font-style: italic; } -pre .scss.constant, pre .at-directive, pre .reserved, pre .exception, pre .scss.ternary-method { +pre .at-directive, pre .reserved, pre .exception, +pre .scss.constant, pre .scss.ternary-method { font-weight: bold; } @@ -112,15 +110,18 @@ pre .hack { color: #ff0000; /* red */ } -pre .scss.interpolation, pre .scss.map-pair, pre .selector { +pre .selector, +pre .scss.interpolation, pre .scss.map-pair { color: #839496; /* base0 */ } -pre .at-directive, pre .reserved, pre .exception, pre .scss.ternary-method, pre .attribute-name, pre .mediaquery.type { +pre .at-directive, pre .reserved, pre .exception, pre .attribute-name, pre .mediaquery.type, +pre .scss.ternary-method { color: #b58900; /* yellow */ } -pre .method-name, pre .scss.method-name, pre .scss.mixin-name { +pre .method-name, +pre .scss.method-name, pre .scss.mixin-name { color: #c5ab0b; /* light yellow */ } @@ -128,7 +129,8 @@ pre .constant.numeric, pre .constant.hex-color { color: #2aa198; /* cyan */ } -pre .css-property, pre .mediaquery.feature, pre .scss.map-name, pre .scss.map-value { +pre .css-property, pre .mediaquery.feature, +pre .scss.map-name, pre .scss.map-value { color: #8866dd; /* purple */ } @@ -136,7 +138,8 @@ pre .entity.tag { color: #ad5a18; /* orange */ } -pre .constant.path, pre .scss.variable { +pre .constant.path, +pre .scss.variable { color: #268bd2; /* blue */ } From 666664519f900981356aff38a7b17e18b93c9e11 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Tue, 26 Jan 2016 10:17:54 +0100 Subject: [PATCH 06/11] css patch version bumped --- themes/solarized-dark.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/solarized-dark.css b/themes/solarized-dark.css index 8cd43dec..65271462 100644 --- a/themes/solarized-dark.css +++ b/themes/solarized-dark.css @@ -6,7 +6,7 @@ * @author Ethan Schoonover * @author David Litmark * @author Fabio Bucci Di Monte (Additional styles for enhanced CSS syntax and SCSS syntax) - * @version 1.1.0 + * @version 1.1.1 */ pre { background: #002b36; /* base03 */ From 64fdd262fdd8f450f0ed3d17da8e27543109b9ba Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Thu, 28 Jan 2016 11:14:56 +0100 Subject: [PATCH 07/11] css properties alphabetically ordered (as suggested in PR comments) --- themes/solarized-dark.css | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/themes/solarized-dark.css b/themes/solarized-dark.css index 65271462..8511a81f 100644 --- a/themes/solarized-dark.css +++ b/themes/solarized-dark.css @@ -10,14 +10,13 @@ */ pre { background: #002b36; /* base03 */ - word-wrap: break-word; - margin: 0px; - padding: 10px; - color: #839496; /* base0 */ - margin-bottom: 20px; border-color: #333333; + color: #839496; /* base0 */ font-size: 16px; line-height: 20px; + margin: 0 0 20px 0; + padding: 10px; + word-wrap: break-word; } pre, code { From 7fa81fbae9358c64c329b342dffbc0df57f5fbe4 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Fri, 29 Jan 2016 18:13:37 +0100 Subject: [PATCH 08/11] minor code clean (just added a comment) --- js/language/scss.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/language/scss.js b/js/language/scss.js index ef987a81..e86fea9a 100644 --- a/js/language/scss.js +++ b/js/language/scss.js @@ -26,7 +26,7 @@ reserved : /(?:and|or|not|in|from|through|to(?! (?:top|bottom|right|left)))/g,// <- enhanced for SCSS /* comments */ - commentMulti : /\/\*([^]*?)\*\//gm, + commentMulti : /\/\*([^]*?)\*\//gm,// <- enhanced for SCSS (interpolations) commentSingle : /\/\/(.*?)$/gm,// <- added for SCSS /* units */ From b71dfa7a149b695ed5860e7e85f49098d088c918 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Wed, 10 Feb 2016 15:58:57 +0100 Subject: [PATCH 09/11] regexes enhancements according to PR comments --- js/language/scss.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/js/language/scss.js b/js/language/scss.js index e86fea9a..d1b518c2 100644 --- a/js/language/scss.js +++ b/js/language/scss.js @@ -16,7 +16,7 @@ /* common */ anyChars : /(.+)/g,// <- added for SCSS - commonName : /(?=-?[\w#]+)((?:[\w-]+)*(?:#\{.+?})?(?:[\w-]*\w+)*)/g,// <- enhanced for SCSS (interpolations) + commonName : /(?=-?[\w#]+)((?:[\w-]+)*(?:#\{[^}]+})?(?:[\w-]*\w+)*)/g,// <- enhanced for SCSS (interpolations) parameters : /\((.*)\)/g,// <- enhanced for SCSS (accepts void parameters) parametersEnhanced : /\((.*)\)/g,// <- added for SCSS (same regex as above) @@ -50,7 +50,7 @@ prefix : /-(?:webkit|moz|ms|o)-/g, cssProperty : /([a-z-]+)/g, cssMethod : /(-?\w+(?:-\w+)*)(\(.+\))/g, - cssMethodEnhanced : /(?=(?:-?[\w#\{$} "'!=*/+])+\(.*\))((?:[\w-]+)*(?:#\{.+?})?(?:[\w-]*\w+)*)(\(.*\))/g,// <- added for SCSS + cssMethodEnhanced : /(?=(?:-?[\w#{$} "'!=*/+])+\(.*\))((?:[\w-]+)*(?:#\{[^}]+})?(?:[\w-]*\w+)*)(\(.*\))/g,// <- added for SCSS cssSelector : /((?:[\w.#:()%+>~\[="\] -]|\{.+}|,\s?|&(?:gt|amp);)+)(?=\{)/g,// <- enhanced for SCSS (interpolations, placeholders, parent selectors) /* ie hacks */ @@ -60,11 +60,11 @@ // TODO: improve entity regexes - eg. change [\w-]+ into \w+(?:-\w+)* /* entities */ - entityClass : /\.(?!\.)[\w-]*(#\{.+?})?[\w-]*/g,// <- enhanced for SCSS (interpolations) - entityId : /#(?!#[^\{])[\w-]*(#\{.+?})?[\w-]*/g,// <- enhanced for SCSS (interpolations) - entityPseudo : /::?(?!::?)[a-z-]*(#\{.+?})?[a-z-]*/g,// <- enhanced for SCSS (interpolations) - entityTag : /[a-z]+\d?(#\{.+?})?(?! *:.*;)/gi,// <- enhanced for SCSS (interpolations) - entityAttribute : /\[([\w#\{$}-]+)=(("|')?[\w#\{$}-]+\3)]/g,// <- enhanced for SCSS (interpolations) + entityClass : /\.(?!\.)[\w-]*(#\{[^}]+})?[\w-]*/g,// <- enhanced for SCSS (interpolations) + entityId : /#(?!#[^{])[\w-]*(#\{[^}]+})?[\w-]*/g,// <- enhanced for SCSS (interpolations) + entityPseudo : /::?(?!::?)[a-z-]*(#\{[^}]+})?[a-z-]*/g,// <- enhanced for SCSS (interpolations) + entityTag : /[a-z]+\d?(#\{[^}]+})?(?! *:.*;)/gi,// <- enhanced for SCSS (interpolations) + entityAttribute : /\[([\w#{$}-]+)=(("|')?[\w#{$}-]+\3)]/g,// <- enhanced for SCSS (interpolations) entityParent : /(?:&|&)(?!(?:&|&))/g,// <- added for SCSS entityPlaceholder : /%(?!%)\w+(?:-\w+)*/g,// <- added for SCSS @@ -77,7 +77,7 @@ mediaReserved : /\b(?:not|only|and)\b/g, mediaType : /\b(?:all|aural|braille|handheld|print|projection|screen|tty|tv|embossed)\b/g, mediaFeature : /\b(?:(?:(?:min|max)-)?(?:(?:device-)?(?:width|height|aspect-ratio)|color(?:-index)?|monochrome|resolution)|scan|grid|orientation)\b/g, - mediaExpression : /\((.+?)(?: *: *(.+?))?\)/g, + mediaExpression : /\(([^:]+)(?: *: *([^)]+))?\)/g, mediaQuery : /(?:(not|only) +)?(.+)/g, mediaQueryList : / *([^,\n\r]+) */g, mediaQueryRule : /(@media) +(.+)(?=\{)/g, @@ -86,7 +86,7 @@ constant : /\b(?:true|false|null)\b/g, variable : /\$\w+(?:-\w+)*/g, operator : /(&[lg]t;|<|>|==|!=|\/|\*|\+|\-)(?!\1)/g, - interpolation : /#\{(.+?)}/g, + interpolation : /#\{([^}]+)}/g, /* scss directives */ scssList : /\(((?:[^,\s]+ ?, ?)+)([^,\s]+)\)/g, From d8bb80dc3220fada0f403d96d897a15ea49efd48 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Wed, 10 Feb 2016 15:59:43 +0100 Subject: [PATCH 10/11] enhanced text values and media features with vendor prefix matches --- js/language/scss.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/js/language/scss.js b/js/language/scss.js index d1b518c2..1b8fda23 100644 --- a/js/language/scss.js +++ b/js/language/scss.js @@ -40,7 +40,7 @@ unitPercentage : /%/g, /* values */ - valueText : /\b\w+(?:-\w+)*\b/g, + valueText : /(-(?:webkit|moz|ms|o)-)?\b\w+(?:-\w+)*\b/g, valueString : /('|")(.*?)\1/g,// <- enhanced for SCSS (grouped string content for interpolations recognition) valuePath : /[\w/-]+\.[a-z0-9]{3,4}\b(?!["'])/gi, valueHex : /#(?:[a-f0-9]{6}|[a-f0-9]{3})\b/gi, @@ -76,7 +76,7 @@ /* media queries */ mediaReserved : /\b(?:not|only|and)\b/g, mediaType : /\b(?:all|aural|braille|handheld|print|projection|screen|tty|tv|embossed)\b/g, - mediaFeature : /\b(?:(?:(?:min|max)-)?(?:(?:device-)?(?:width|height|aspect-ratio)|color(?:-index)?|monochrome|resolution)|scan|grid|orientation)\b/g, + mediaFeature : /(-(?:webkit|moz|ms|o)-)?\b(?:(?:(?:min|max)-)?(?:(?:device-)?(?:width|height|(?:pixel|aspect)-ratio)|color(?:-index)?|monochrome|resolution)|scan|grid|orientation)\b/g, mediaExpression : /\(([^:]+)(?: *: *([^)]+))?\)/g, mediaQuery : /(?:(not|only) +)?(.+)/g, mediaQueryList : / *([^,\n\r]+) */g, @@ -149,6 +149,11 @@ pattern: regexes.variable }; + var prefix = { + name: 'vendor-prefix', + pattern: regexes.prefix + }; + /******************** * TYPES ********************/ @@ -195,6 +200,9 @@ }, text: { name: 'support.text-value', + matches: { + 1: [prefix] + }, pattern: regexes.valueText }, string: { @@ -278,11 +286,6 @@ * CSS PROPERTIES / METHODS ********************/ - var prefix = { - name: 'vendor-prefix', - pattern: regexes.prefix - }; - var cssProperties = { name: 'css-property', matches: { @@ -509,6 +512,9 @@ }, features: { name: 'mediaquery.feature', + matches: { + 1: [prefix] + }, pattern: regexes.mediaFeature } }; From 21bc1ecfbd77ace9d772491588f1fc674fe6fd57 Mon Sep 17 00:00:00 2001 From: Fabio Di Monte Date: Tue, 26 Apr 2016 10:13:28 +0200 Subject: [PATCH 11/11] removed useless check for Rainbow --- js/language/scss.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/js/language/scss.js b/js/language/scss.js index 1b8fda23..26b4658d 100644 --- a/js/language/scss.js +++ b/js/language/scss.js @@ -8,10 +8,6 @@ 'use strict'; - if(!Rainbow){ - return; - } - var regexes = { /* common */