[Pkg-javascript-commits] [node-cson-parser] 01/02: Imported Upstream version 1.3.4

Ross Gammon ross-guest at moszumanska.debian.org
Tue Dec 20 18:51:57 UTC 2016


This is an automated email from the git hooks/post-receive script.

ross-guest pushed a commit to branch master
in repository node-cson-parser.

commit d33dac59253bc468ed725930d513cb4e8ff7803d
Author: Ross Gammon <rossgammon at mail.dk>
Date:   Sat Nov 19 17:30:43 2016 +0100

    Imported Upstream version 1.3.4
---
 .editorconfig              |  11 +++
 .gitignore                 |   3 +
 .npmrc                     |   1 +
 .travis.yml                |  16 +++
 CHANGELOG.md               | 111 +++++++++++++++++++++
 CONTRIBUTING.md            |  30 ++++++
 LICENSE                    |  29 ++++++
 README.md                  |  71 ++++++++++++++
 lib/cson-parser.js         |  42 ++++++++
 lib/parse.js               | 237 +++++++++++++++++++++++++++++++++++++++++++++
 lib/stringify.js           | 164 +++++++++++++++++++++++++++++++
 package.json               |  53 ++++++++++
 src/cson-parser.coffee     |  36 +++++++
 src/parse.coffee           | 196 +++++++++++++++++++++++++++++++++++++
 src/stringify.coffee       | 134 +++++++++++++++++++++++++
 test/mocha.opts            |   2 +
 test/parse.test.coffee     | 215 ++++++++++++++++++++++++++++++++++++++++
 test/stringify.test.coffee | 154 +++++++++++++++++++++++++++++
 18 files changed, 1505 insertions(+)

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..beffa30
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c78d32f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+npm-debug.log
+/tmp
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..38f11c6
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+registry=https://registry.npmjs.org
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b1a0247
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,16 @@
+language: node_js
+node_js:
+  - '0.10'
+  - '4'
+before_install:
+  - npm install -g npm at latest-2
+before_deploy:
+  - 'git config --global user.email "opensource at groupon.com"'
+  - 'git config --global user.name "Groupon"'
+deploy:
+  provider: script
+  script: ./node_modules/.bin/nlm release
+  skip_cleanup: true
+  'on':
+    branch: master
+    node: '4'
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..39a9b77
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,111 @@
+### 1.3.4
+
+* Compatible with coffee-script 1.11.0 - **[@jkrems](https://github.com/jkrems)** [#57](https://github.com/groupon/cson-parser/pull/57)
+  - [`ed54c9a`](https://github.com/groupon/cson-parser/commit/ed54c9a89b3afb933c2eee1281e17fd6d78e8dba) **fix:** Compatible with coffee-script 1.11.0 - see: [#56](https://github.com/groupon/cson-parser/issues/56)
+
+
+### 1.3.3
+
+* Apply latest nlm generator - **[@i-tier-bot](https://github.com/i-tier-bot)** [#55](https://github.com/groupon/cson-parser/pull/55)
+  - [`9905e06`](https://github.com/groupon/cson-parser/commit/9905e064f85f2cad7c656821195ea4afcd37f11f) **chore:** Apply latest nlm generator
+
+
+### 1.3.2
+
+* Parser no longer requires support for constructor.name - **[@jkrems](https://github.com/jkrems)** [#52](https://github.com/groupon/cson-parser/pull/52)
+  - [`c5591b3`](https://github.com/groupon/cson-parser/commit/c5591b3a8ce0ba88a3e7738f940263ef053e7145) **fix:** Handle missing fn.name
+
+
+### 1.3.1
+
+* chore: Move to nlm for publishing - **[@jkrems](https://github.com/jkrems)** [#50](https://github.com/groupon/cson-parser/pull/50)
+  - [`3c704a4`](https://github.com/groupon/cson-parser/commit/3c704a4e796b6d997a4aea499ac7d85bfe9fffe6) **chore:** Move to nlm for publishing
+
+
+1.3.0
+-----
+* Add support for regular expressions - @jkrems
+  https://github.com/groupon/cson-parser/pull/49
+
+1.2.0
+-----
+* Use flexible coffee-script version - @jkrems
+  https://github.com/groupon/cson-parser/pull/47
+
+1.1.1
+-----
+* Support key with empty object - @charlierudolph
+  https://github.com/groupon/cson-parser/pull/42
+
+1.1.0
+-----
+* Add fs-cson to readme - @charlierudolph
+  https://github.com/groupon/cson-parser/pull/39
+* Proper CSON single-line formatting - @charlierudolph
+  https://github.com/groupon/cson-parser/pull/37
+
+1.0.9
+-----
+* Escape backslash in multi-line string - @jkrems
+  https://github.com/groupon/cson-parser/pull/35
+* Pin coffee-script version to 1.9.0 - @jkrems
+  https://github.com/groupon/cson-parser/pull/33
+
+1.0.8
+-----
+* Run against iojs - @jkrems #27
+
+1.0.7
+-----
+* Added CSON package which now uses cson-parser - @balupton
+  https://github.com/groupon/cson-parser/pull/25
+
+1.0.6
+-----
+* Rename to cson-parser - @jkrems
+  https://github.com/groupon/cson-parser/pull/23
+
+1.0.5
+-----
+* Be explicit about registry for publish - @jkrems
+  https://github.com/groupon/cson-safe/pull/19
+* Unify `''`/`""` handling, str.charAt(0) -> str[0] - @jkrems
+  https://github.com/groupon/cson-safe/pull/22
+
+1.0.4
+-----
+* Use `vm.runInThisContext` instead of eval - @jkrems
+  https://github.com/groupon/cson-safe/pull/18
+
+1.0.3
+-----
+* Add license SPDX ID to package.json - @jkrems
+  https://github.com/groupon/cson-safe/pull/16
+
+1.0.2
+-----
+* Upgrade npub - @abloom
+  https://github.com/groupon/cson-safe/pull/14
+
+v1.0.1
+------
+* Even nicer stringify with less noise - @jkrems
+  https://github.com/groupon/cson-safe/pull/13
+
+v1.0.0
+------
+* Switch to coffee-script for parsing - @jkrems
+  https://github.com/groupon/cson-safe/pull/10
+
+v0.2.0
+------
+* A nicer CSON.stringify - @johan
+  https://github.com/groupon/cson-safe/pull/9
+
+v0.1.1
+------
+* Fix package meta data
+
+v0.1.0
+------
+* Initial public release
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..82c3e4e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,30 @@
+# Contribution Guide
+
+Please follow this guide when
+creating issues or pull requests.
+
+## Reporting a Bug
+
+Before reporting a bug,
+make sure you are using the latest versions of the code.
+
+When reporting a bug with this library,
+please provide a minimal test case.
+This can be a gist,
+inline in the description,
+or in the form of a pull request
+that includes a failing test.
+
+If you are contributing a bug fix,
+make sure it has a passing test
+in your pull request.
+
+## Adding a Feature
+
+Before implementing features,
+try to make sure that
+(1) no one else is currently working on that and
+(2) you have checked with the maintainers
+that this is something they would like to see.
+
+All features should have tests.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e97f18f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2014, Groupon, Inc.
+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 GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c75c15e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,71 @@
+# cson-parser
+
+A minimalistic CSON parser. Offers:
+
+* A strict subset of CSON that allows only data
+* Interface is identical to JSON.{parse,stringify}
+* Does not run the code, free of intermediate string representations
+* Sane parse error messages with line/column
+* Regular Expressions are considered data and will be accepted as well
+
+In addition of pure data it allows for simple arithmetic expressions like
+addition and multiplication.
+This allows more readable configuration of numbers,
+the following is a valid strict CSON file:
+
+```coffee
+cachedData:
+  refreshIntervalMs: 5 * 60 * 1000
+```
+
+## Install
+
+`npm install --save cson-parser`
+
+## Usage
+
+```coffee
+CSON = require 'cson-parser'
+# This will print { a: '123' }
+console.log CSON.parse "a: '123'"
+```
+
+## High-level APIs
+
+`cson-parser` only offers basic parsing and serialization.
+But there are some great tools if you want more than that:
+
+* [`fs-cson`](https://github.com/charlierudolph/fs-cson), read and write CSON files
+* [`CSON`](https://github.com/bevry/cson), provides file, coffeescript, javascript handling and a CLI
+* [`season`](https://www.npmjs.org/package/season),
+  atom.io's CSON package.
+  Includes CLI tool to convert CSON to JSON
+* [`grunt-cson`](https://www.npmjs.org/package/grunt-cson),
+  converts CSON to JSON as a grunt task
+* [`load-grunt-configs`](https://www.npmjs.org/package/load-grunt-configs),
+  loads grunt config from CSON files (among other formats)
+* [`fetcher`](https://www.npmjs.org/package/fetcher),
+  a declarative way to download (frontend) libraries, supports CSON configs
+* [`csonschema`](https://www.npmjs.org/package/csonschema),
+  parses [JSON Schema](http://json-schema.org) files written in CSON
+
+You can find more on the
+[npm website](https://preview.npmjs.com/browse/depended/cson-parser).
+
+## FAQ
+
+### Why not just use YAML?
+
+YAML allows for some pretty complex constructs like anchor and alias,
+which can behave in unexpected ways, especially with nested objects.
+CSON is simpler while still offering most of the niceties of YAML.
+
+### Why not just use JSON?
+
+JSON doesn't offer multi-line strings and is generally a little noisier.
+Also sometimes it can be nice to have comments in config files.
+
+### Why not just use CoffeeScript directly?
+
+You don't want data files being able to run arbitrary code.
+Even when ran in a proper sandbox, `while(true)` is still possible.
diff --git a/lib/cson-parser.js b/lib/cson-parser.js
new file mode 100644
index 0000000..130a80f
--- /dev/null
+++ b/lib/cson-parser.js
@@ -0,0 +1,42 @@
+
+/*
+Copyright (c) 2014, Groupon, Inc.
+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 GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+var CSON, parse, stringify;
+
+stringify = require('./stringify');
+
+parse = require('./parse');
+
+module.exports = CSON = {
+  stringify: stringify,
+  parse: parse
+};
diff --git a/lib/parse.js b/lib/parse.js
new file mode 100644
index 0000000..1ac92c1
--- /dev/null
+++ b/lib/parse.js
@@ -0,0 +1,237 @@
+
+/*
+Copyright (c) 2014, Groupon, Inc.
+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 GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+var defaultReviver, getFunctionNameIE, nodeTypeString, nodes, parse, parseRegExpLiteral, parseStringLiteral, runInThisContext, syntaxErrorMessage;
+
+runInThisContext = require('vm').runInThisContext;
+
+nodes = require('coffee-script').nodes;
+
+defaultReviver = function(key, value) {
+  return value;
+};
+
+getFunctionNameIE = function(fn) {
+  return csNode.constructor.toString().match(/^function\s*([^( ]+)/)[1];
+};
+
+nodeTypeString = function(csNode) {
+  var ref;
+  return (ref = csNode.constructor.name) != null ? ref : getFunctionNameIE(csNode.constructor);
+};
+
+syntaxErrorMessage = function(csNode, msg) {
+  var column, columnIdx, line, lineIdx, ref;
+  ref = csNode.locationData, lineIdx = ref.first_line, columnIdx = ref.first_column;
+  if (lineIdx != null) {
+    line = lineIdx + 1;
+  }
+  if (columnIdx != null) {
+    column = columnIdx + 1;
+  }
+  return "Syntax error on line " + line + ", column " + column + ": " + msg;
+};
+
+parseStringLiteral = function(literal) {
+  return runInThisContext(literal);
+};
+
+parseRegExpLiteral = function(literal) {
+  return runInThisContext(literal);
+};
+
+parse = function(source, reviver) {
+  var coffeeAst, contextObj, isLiteral, nodeTransforms, parsed, transformKey, transformNode;
+  if (reviver == null) {
+    reviver = defaultReviver;
+  }
+  nodeTransforms = {
+    Block: function(node) {
+      var expressions;
+      expressions = node.expressions;
+      if (!expressions || expressions.length !== 1) {
+        throw new SyntaxError(syntaxErrorMessage(node, 'One top level value expected'));
+      }
+      return transformNode(expressions[0]);
+    },
+    Value: function(node) {
+      return transformNode(node.base);
+    },
+    Bool: function(node) {
+      return node.val === 'true';
+    },
+    BooleanLiteral: function(node) {
+      return node.value === 'true';
+    },
+    Null: function() {
+      return null;
+    },
+    NullLiteral: function() {
+      return null;
+    },
+    Literal: function(node) {
+      var err, value;
+      value = node.value;
+      try {
+        switch (value.charAt(0)) {
+          case "'":
+          case '"':
+            return parseStringLiteral(value);
+          case '/':
+            return parseRegExpLiteral(value);
+          default:
+            return JSON.parse(value);
+        }
+      } catch (error) {
+        err = error;
+        throw new SyntaxError(syntaxErrorMessage(node, err.message));
+      }
+    },
+    NumberLiteral: function(node) {
+      return Number(node.value);
+    },
+    StringLiteral: function(node) {
+      return parseStringLiteral(node.value);
+    },
+    RegexLiteral: function(node) {
+      return parseRegExpLiteral(node.value);
+    },
+    Arr: function(node) {
+      return node.objects.map(transformNode);
+    },
+    Obj: function(node) {
+      return node.properties.reduce(function(outObject, property) {
+        var keyName, value, variable;
+        variable = property.variable, value = property.value;
+        if (!variable) {
+          return outObject;
+        }
+        keyName = transformKey(variable);
+        value = transformNode(value);
+        outObject[keyName] = reviver.call(outObject, keyName, value);
+        return outObject;
+      }, {});
+    },
+    Op: function(node) {
+      var left, right;
+      if (node.second != null) {
+        left = transformNode(node.first);
+        right = transformNode(node.second);
+        switch (node.operator) {
+          case '-':
+            return left - right;
+          case '+':
+            return left + right;
+          case '*':
+            return left * right;
+          case '/':
+            return left / right;
+          case '%':
+            return left % right;
+          case '&':
+            return left & right;
+          case '|':
+            return left | right;
+          case '^':
+            return left ^ right;
+          case '<<':
+            return left << right;
+          case '>>>':
+            return left >>> right;
+          case '>>':
+            return left >> right;
+          default:
+            throw new SyntaxError(syntaxErrorMessage(node, "Unknown binary operator " + node.operator));
+        }
+      } else {
+        switch (node.operator) {
+          case '-':
+            return -transformNode(node.first);
+          case '~':
+            return ~transformNode(node.first);
+          default:
+            throw new SyntaxError(syntaxErrorMessage(node, "Unknown unary operator " + node.operator));
+        }
+      }
+    },
+    Parens: function(node) {
+      var expressions;
+      expressions = node.body.expressions;
+      if (!expressions || expressions.length !== 1) {
+        throw new SyntaxError(syntaxErrorMessage(node, 'Parenthesis may only contain one expression'));
+      }
+      return transformNode(expressions[0]);
+    }
+  };
+  isLiteral = function(csNode) {
+    return LiteralTypes.some(function(LiteralType) {
+      return csNode instanceof LiteralType;
+    });
+  };
+  transformKey = function(csNode) {
+    var type, value;
+    type = nodeTypeString(csNode);
+    if (type !== 'Value') {
+      throw new SyntaxError(syntaxErrorMessage(csNode, type + " used as key"));
+    }
+    value = csNode.base.value;
+    switch (value.charAt(0)) {
+      case "'":
+      case '"':
+        return parseStringLiteral(value);
+      default:
+        return value;
+    }
+  };
+  transformNode = function(csNode) {
+    var transform, type;
+    type = nodeTypeString(csNode);
+    transform = nodeTransforms[type];
+    if (!transform) {
+      throw new SyntaxError(syntaxErrorMessage(csNode, "Unexpected " + type));
+    }
+    return transform(csNode);
+  };
+  if (typeof reviver !== 'function') {
+    throw new TypeError("reviver has to be a function");
+  }
+  coffeeAst = nodes(source.toString('utf8'));
+  parsed = transformNode(coffeeAst);
+  if (reviver === defaultReviver) {
+    return parsed;
+  }
+  contextObj = {};
+  contextObj[''] = parsed;
+  return reviver.call(contextObj, '', parsed);
+};
+
+module.exports = parse;
diff --git a/lib/stringify.js b/lib/stringify.js
new file mode 100644
index 0000000..145302f
--- /dev/null
+++ b/lib/stringify.js
@@ -0,0 +1,164 @@
+
+/*
+Copyright (c) 2014, Groupon, Inc.
+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 GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+var SPACES, isObject, jsIdentifierRE, newlineWrap, tripleQuotesRE,
+  indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+jsIdentifierRE = /^[a-z_$][a-z0-9_$]*$/i;
+
+tripleQuotesRE = new RegExp("'''", 'g');
+
+SPACES = '          ';
+
+newlineWrap = function(str) {
+  return str && ("\n" + str + "\n");
+};
+
+isObject = function(obj) {
+  return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
+};
+
+module.exports = function(data, visitor, indent) {
+  var indentLine, indentLines, n, normalized, ref, visitArray, visitNode, visitObject, visitString;
+  if ((ref = typeof data) === 'undefined' || ref === 'function') {
+    return void 0;
+  }
+  indent = (function() {
+    switch (typeof indent) {
+      case 'string':
+        return indent.slice(0, 10);
+      case 'number':
+        n = Math.min(10, Math.floor(indent));
+        if (indexOf.call([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], n) < 0) {
+          n = 0;
+        }
+        return SPACES.slice(0, n);
+      default:
+        return 0;
+    }
+  })();
+  indentLine = function(line) {
+    return indent + line;
+  };
+  indentLines = function(str) {
+    if (str === '') {
+      return str;
+    }
+    return str.split('\n').map(indentLine).join('\n');
+  };
+  normalized = JSON.parse(JSON.stringify(data, visitor));
+  visitString = function(str) {
+    var string;
+    if (str.indexOf('\n') === -1 || !indent) {
+      return JSON.stringify(str);
+    } else {
+      string = str.replace(/\\/g, '\\\\').replace(tripleQuotesRE, "\\'''");
+      return "'''" + (newlineWrap(indentLines(string))) + "'''";
+    }
+  };
+  visitArray = function(arr) {
+    var items, serializedItems;
+    items = arr.map(function(value) {
+      return visitNode(value, {
+        bracesRequired: true
+      });
+    });
+    serializedItems = indent ? newlineWrap(indentLines(items.join('\n'))) : items.join(',');
+    return "[" + serializedItems + "]";
+  };
+  visitObject = function(obj, arg) {
+    var bracesRequired, key, keypairs, serializedKeyPairs, serializedValue, value;
+    bracesRequired = arg.bracesRequired;
+    keypairs = (function() {
+      var results;
+      results = [];
+      for (key in obj) {
+        value = obj[key];
+        if (!key.match(jsIdentifierRE)) {
+          key = JSON.stringify(key);
+        }
+        serializedValue = visitNode(value, {
+          bracesRequired: !indent
+        });
+        if (indent) {
+          serializedValue = isObject(value) && Object.keys(value).length > 0 ? "\n" + (indentLines(serializedValue)) : " " + serializedValue;
+        }
+        results.push(key + ":" + serializedValue);
+      }
+      return results;
+    })();
+    if (keypairs.length === 0) {
+      return '{}';
+    } else if (indent) {
+      serializedKeyPairs = keypairs.join('\n');
+      if (bracesRequired) {
+        return "{" + (newlineWrap(indentLines(serializedKeyPairs))) + "}";
+      } else {
+        return serializedKeyPairs;
+      }
+    } else {
+      serializedKeyPairs = keypairs.join(',');
+      if (bracesRequired) {
+        return "{" + serializedKeyPairs + "}";
+      } else {
+        return serializedKeyPairs;
+      }
+    }
+  };
+  visitNode = function(node, options) {
+    if (options == null) {
+      options = {};
+    }
+    switch (typeof node) {
+      case 'boolean':
+        return "" + node;
+      case 'number':
+        if (isFinite(node)) {
+          return "" + node;
+        } else {
+          return 'null';
+        }
+        break;
+      case 'string':
+        return visitString(node, options);
+      case 'object':
+        if (node === null) {
+          return 'null';
+        } else if (Array.isArray(node)) {
+          return visitArray(node, options);
+        } else {
+          return visitObject(node, options);
+        }
+    }
+  };
+  return visitNode(normalized);
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2760e9a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,53 @@
+{
+  "name": "cson-parser",
+  "version": "1.3.4",
+  "description": "Safe parsing of CSON files",
+  "license": "BSD-3-Clause",
+  "main": "lib/cson-parser.js",
+  "homepage": "https://github.com/groupon/cson-parser",
+  "repository": {
+    "type": "git",
+    "url": "git+ssh://git@github.com/groupon/cson-parser"
+  },
+  "bugs": {
+    "url": "https://github.com/groupon/cson-parser/issues"
+  },
+  "scripts": {
+    "build": "rm -rf lib && coffee --no-header -cbo lib src",
+    "pretest": "npm run build",
+    "test": "mocha",
+    "posttest": "nlm verify",
+    "watch": "coffee --no-header -wcbo lib src & nodemon -w lib -w test -e coffee,js,json -x \"mocha\""
+  },
+  "nlm": {
+    "license": {
+      "files": [
+        "src"
+      ]
+    }
+  },
+  "dependencies": {
+    "coffee-script": "^1.10.0"
+  },
+  "devDependencies": {
+    "assertive": "^2.0.0",
+    "mocha": "^2.0.0",
+    "nlm": "^2.0.0",
+    "nodemon": "^1.0.0"
+  },
+  "author": {
+    "name": "Groupon",
+    "email": "opensource at groupon.com"
+  },
+  "keywords": [
+    "cson",
+    "parser"
+  ],
+  "files": [
+    "*.js",
+    "lib"
+  ],
+  "publishConfig": {
+    "registry": "https://registry.npmjs.org"
+  }
+}
diff --git a/src/cson-parser.coffee b/src/cson-parser.coffee
new file mode 100644
index 0000000..4f250b8
--- /dev/null
+++ b/src/cson-parser.coffee
@@ -0,0 +1,36 @@
+###
+Copyright (c) 2014, Groupon, Inc.
+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 GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+###
+
+stringify = require './stringify'
+parse = require './parse'
+
+module.exports = CSON = { stringify, parse }
diff --git a/src/parse.coffee b/src/parse.coffee
new file mode 100644
index 0000000..b0c29c7
--- /dev/null
+++ b/src/parse.coffee
@@ -0,0 +1,196 @@
+###
+Copyright (c) 2014, Groupon, Inc.
+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 GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+###
+
+{runInThisContext} = require 'vm'
+
+{nodes} = require 'coffee-script'
+
+defaultReviver = (key, value) -> value
+
+getFunctionNameIE = (fn) ->
+  # fn.name is only standard in ES2015+ and won't work in IE
+  # This takes the source of the function and extracts the name,
+  # e.g. 'function fooBar ()' becomes fooBar.
+  csNode.constructor.toString()
+    .match(/^function\s*([^( ]+)/)[1]
+
+nodeTypeString = (csNode) ->
+  csNode.constructor.name ? getFunctionNameIE(csNode.constructor)
+
+syntaxErrorMessage = (csNode, msg) ->
+  {
+    first_line: lineIdx
+    first_column: columnIdx
+  } = csNode.locationData
+  line = lineIdx + 1 if lineIdx?
+  column = columnIdx + 1 if columnIdx?
+  "Syntax error on line #{line}, column #{column}: #{msg}"
+
+parseStringLiteral = (literal) ->
+  # In theory this could be replaced by properly resolving
+  # escape sequences etc.
+  # We trust the coffee-script lexer to make sure it's just
+  # a strings and running it has no side effects.
+  runInThisContext literal
+
+parseRegExpLiteral = (literal) ->
+  # In theory this could be replaced by properly resolving
+  # escape sequences etc.
+  # We trust the coffee-script lexer to make sure it's just
+  # a regular expression and running it has no side effects.
+  runInThisContext literal
+
+# See:
+# http://www.ecma-international.org/ecma-262/5.1/#sec-15.12.2
+parse = (source, reviver = defaultReviver) ->
+  nodeTransforms =
+    Block: (node) ->
+      {expressions} = node
+      if !expressions || expressions.length != 1
+        throw new SyntaxError syntaxErrorMessage(node, 'One top level value expected')
+
+      transformNode expressions[0]
+
+    Value: (node) ->
+      transformNode node.base
+
+    Bool: (node) -> # coffee-script at 1.10.0
+      node.val == 'true'
+    BooleanLiteral: (node) -> # coffee-script at 1.11.0
+      node.value == 'true'
+
+    Null: -> null # coffee-script at 1.10.0
+    NullLiteral: -> null # coffee-script at 1.11.0
+
+    Literal: (node) -> # coffee-script at 1.10.0
+      {value} = node
+      try
+        switch value.charAt 0
+          when "'", '"' then parseStringLiteral value
+          when '/' then parseRegExpLiteral value
+          else JSON.parse value
+      catch err
+        throw new SyntaxError syntaxErrorMessage(node, err.message)
+    NumberLiteral: (node) -> # coffee-script at 1.11.0
+      Number(node.value)
+    StringLiteral: (node) -> # coffee-script at 1.11.0
+      parseStringLiteral node.value
+    RegexLiteral: (node) -> # coffee-script at 1.11.0
+      parseRegExpLiteral node.value
+
+    Arr: (node) ->
+      node.objects.map transformNode
+
+    Obj: (node) ->
+      node.properties.reduce(
+        (outObject, property) ->
+          {variable, value} = property
+          return outObject unless variable
+          keyName = transformKey variable
+          value = transformNode value
+          outObject[keyName] =
+            reviver.call outObject, keyName, value
+          outObject
+        {}
+      )
+
+    Op: (node) ->
+      if node.second?
+        left = transformNode node.first
+        right = transformNode node.second
+        switch node.operator
+          when '-' then left - right
+          when '+' then left + right
+          when '*' then left * right
+          when '/' then left / right
+          when '%' then left % right
+          when '&' then left & right
+          when '|' then left | right
+          when '^' then left ^ right
+          when '<<' then left << right
+          when '>>>' then left >>> right
+          when '>>' then left >> right
+          else
+            throw new SyntaxError syntaxErrorMessage(
+              node, "Unknown binary operator #{node.operator}"
+            )
+      else
+        switch node.operator
+          when '-' then -transformNode(node.first)
+          when '~' then ~transformNode(node.first)
+          else
+            throw new SyntaxError syntaxErrorMessage(
+              node, "Unknown unary operator #{node.operator}"
+            )
+
+    Parens: (node) ->
+      {expressions} = node.body
+      if !expressions || expressions.length != 1
+        throw new SyntaxError syntaxErrorMessage(
+          node, 'Parenthesis may only contain one expression'
+        )
+
+      transformNode expressions[0]
+
+  isLiteral = (csNode) ->
+    LiteralTypes.some (LiteralType) -> csNode instanceof LiteralType
+
+  transformKey = (csNode) ->
+    type = nodeTypeString csNode
+    if type != 'Value'
+      throw new SyntaxError syntaxErrorMessage(csNode, "#{type} used as key")
+
+    {value} = csNode.base
+    switch value.charAt 0
+      when "'", '"' then parseStringLiteral value
+      else value
+
+  transformNode = (csNode) ->
+    type = nodeTypeString csNode
+    transform = nodeTransforms[type]
+
+    unless transform
+      throw new SyntaxError syntaxErrorMessage(csNode, "Unexpected #{type}")
+
+    transform csNode
+
+  if typeof reviver != 'function'
+    throw new TypeError "reviver has to be a function"
+
+  coffeeAst = nodes source.toString 'utf8'
+  parsed = transformNode(coffeeAst)
+  return parsed if reviver == defaultReviver
+  contextObj = {}
+  contextObj[''] = parsed
+  reviver.call contextObj, '', parsed
+
+module.exports = parse
diff --git a/src/stringify.coffee b/src/stringify.coffee
new file mode 100644
index 0000000..6e3dbd5
--- /dev/null
+++ b/src/stringify.coffee
@@ -0,0 +1,134 @@
+###
+Copyright (c) 2014, Groupon, Inc.
+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 GROUPON nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+###
+
+# There are multiple ways to express the same thing in CSON, so trying to
+# make `CSON.stringify(CSON.parse(str)) == str` work is doomed to fail
+# but we can at least make output look a lot nicer than JSON.stringify's.
+jsIdentifierRE = /^[a-z_$][a-z0-9_$]*$/i
+tripleQuotesRE = new RegExp "'''", 'g' # some syntax hilighters hate on /'''/g
+
+SPACES = '          ' # 10 spaces
+
+newlineWrap = (str) ->
+  str and "\n#{ str }\n"
+
+isObject = (obj) ->
+  typeof obj == 'object' && obj != null && !Array.isArray(obj)
+
+# See:
+# http://www.ecma-international.org/ecma-262/5.1/#sec-15.12.3
+module.exports = (data, visitor, indent) ->
+  return undefined if typeof data in ['undefined', 'function']
+
+  # pick an indent style much as JSON.stringify does
+  indent = switch typeof indent
+    when 'string' then indent.slice 0, 10
+
+    when 'number'
+      n = Math.min 10, Math.floor indent
+      n = 0 unless n in [1..10] # do not bail on NaN and similar
+      SPACES.slice(0, n)
+
+    else 0
+
+  indentLine = (line) -> indent + line
+
+  indentLines = (str) ->
+    return str if str == ''
+    str.split('\n').map(indentLine).join('\n')
+
+  # have the native JSON serializer do visitor transforms & normalization for us
+  normalized = JSON.parse JSON.stringify data, visitor
+
+  visitString = (str) ->
+    if str.indexOf('\n') == -1 or !indent
+      JSON.stringify str
+    else
+      string = str
+        .replace /\\/g, '\\\\' # escape backslashes in source string
+        .replace tripleQuotesRE, "\\'''"
+      "'''#{ newlineWrap indentLines string }'''"
+
+  visitArray = (arr) ->
+    items = arr.map (value) -> visitNode value, bracesRequired: true
+
+    serializedItems =
+      if indent
+        newlineWrap indentLines items.join '\n'
+      else
+        items.join ','
+
+    "[#{ serializedItems }]"
+
+  visitObject = (obj, {bracesRequired}) ->
+    keypairs = for key, value of obj
+      key = JSON.stringify key unless key.match jsIdentifierRE
+      serializedValue = visitNode value, bracesRequired: !indent
+      if indent
+        serializedValue =
+          if isObject(value) and Object.keys(value).length > 0
+            "\n#{ indentLines serializedValue }"
+          else
+            " #{ serializedValue }"
+      "#{ key }:#{ serializedValue }"
+
+    if keypairs.length is 0
+      '{}'
+    else if indent
+      serializedKeyPairs = keypairs.join '\n'
+      if bracesRequired
+        "{#{ newlineWrap indentLines serializedKeyPairs }}"
+      else
+        serializedKeyPairs
+    else
+      serializedKeyPairs = keypairs.join ','
+      if bracesRequired
+        "{#{ serializedKeyPairs }}"
+      else
+        serializedKeyPairs
+
+  visitNode = (node, options = {}) ->
+    switch typeof node
+      when 'boolean' then "#{node}"
+
+      when 'number'
+        if isFinite node then "#{node}"
+        else 'null' # NaN, Infinity and -Infinity
+
+      when 'string' then visitString node, options
+
+      when 'object'
+        if node == null then 'null'
+        else if Array.isArray(node) then visitArray node, options
+        else visitObject node, options
+
+  visitNode normalized
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000..1083fea
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1,2 @@
+--compilers test.coffee:coffee-script/register
+--recursive
diff --git a/test/parse.test.coffee b/test/parse.test.coffee
new file mode 100644
index 0000000..e998086
--- /dev/null
+++ b/test/parse.test.coffee
@@ -0,0 +1,215 @@
+
+CSON = require '../'
+assert = require 'assertive'
+os = require 'os'
+
+compilesTo = (source, expected) ->
+  assert.deepEqual expected, CSON.parse(source)
+
+describe 'CSON.parse', ->
+  it 'parses an empty object', ->
+    compilesTo '{}', {}
+
+  it 'parses boolean values', ->
+    compilesTo 'true', true
+    compilesTo 'yes', true
+    compilesTo 'on', true
+    compilesTo 'false', false
+    compilesTo 'no', false
+    compilesTo 'off', false
+
+  it 'parses numbers', ->
+    compilesTo '0.42', 0.42
+    compilesTo '42', 42
+    compilesTo '1.2e+4', 1.2e+4
+
+  it 'parses arrays', ->
+    compilesTo '[ 1, 2, a: "str" ]', [ 1, 2, a: 'str' ]
+    compilesTo(
+      """
+      [
+        1
+        2
+        a: 'str'
+      ]
+      """
+      [ 1, 2, a: 'str' ]
+    )
+
+  it 'parses null', ->
+    compilesTo 'null', null
+
+  it 'does not allow undefined', ->
+    err = assert.throws ->
+      CSON.parse 'undefined'
+
+    assert.match /^Syntax error on line 1, column 1: Unexpected Undefined/, err.message
+
+  it 'allows line comments', ->
+    compilesTo 'true # line comment', true
+
+  it 'allows multi-line comments', ->
+    compilesTo(
+      """
+      a:
+        ###
+        This is a comment
+        spanning multiple lines
+        ###
+        c: 3
+      """
+      { a: { c: 3 } }
+    )
+
+  it 'allows multi-line strings', ->
+    compilesTo(
+      """
+      \"""Some long
+      string
+      \"""
+      """
+      "Some long#{os.EOL}string"
+    )
+
+  it 'does not allow using assignments', ->
+    err = assert.throws ->
+      CSON.parse 'a = 3'
+    assert.equal 'Syntax error on line 1, column 1: Unexpected Assign', err.message
+
+    err = assert.throws ->
+      CSON.parse 'a ?= 3'
+    assert.equal 'Syntax error on line 1, column 1: Unexpected Assign', err.message
+
+  it 'does not allow referencing variables', ->
+    err = assert.throws ->
+      CSON.parse 'a: foo'
+    assert.match /Syntax error on line 1, column 4: Unexpected (token o|IdentifierLiteral)/, err.message
+
+    err = assert.throws ->
+      CSON.parse 'a: process.env.NODE_ENV'
+    assert.match /Syntax error on line 1, column 4: Unexpected (token p|IdentifierLiteral)/, err.message
+
+  it 'does not allow Infinity or -Infinity', ->
+    err = assert.throws ->
+      CSON.parse 'a: Infinity'
+    assert.match /^Syntax error on line 1, column 4: Unexpected (token I|InfinityLiteral)/, err.message
+
+    err = assert.throws ->
+      CSON.parse 'a: -Infinity'
+    assert.match /Syntax error on line 1, column 5: Unexpected (token I|InfinityLiteral)/, err.message
+
+  it 'does allow simple mathematical operations', ->
+    compilesTo '(2 + 3) * 4', ((2 + 3) * 4)
+    compilesTo '2 + 3 * 4', (2 + 3 * 4)
+    compilesTo 'fetchIntervalMs: 1000 * 60 * 5', fetchIntervalMs: (1000 * 60 * 5)
+    compilesTo '2 / 4', (2 / 4)
+    compilesTo '5 - 1', (5 - 1)
+    compilesTo '3 % 2', (3 % 2)
+
+  it 'allows bit operations', ->
+    compilesTo '5 & 6', (5 & 6)
+    compilesTo '1 | 2', (1 | 2)
+    compilesTo '~0', (~0)
+    compilesTo '3 ^ 5', (3 ^ 5)
+    compilesTo '1 << 3', (1 << 3)
+    compilesTo '8 >> 3', (8 >> 3)
+    compilesTo '-9 >>> 2', (-9 >>> 2)
+
+  it 'allows hard tabs in strings', ->
+    compilesTo 'a: "x\ty"', a: 'x\ty'
+
+  it 'parses simple regular expressions', ->
+    compilesTo 'a: /^[a-d]*/g', a: /^[a-d]*/g
+
+  it 'parses complex multi-line regular expressions', ->
+    compilesTo '''
+      syntax:
+        identifier: /\\b[a-z_][a-z_0-9]*\\b/i
+        operator: /// ^ (
+          ?: [-=]>             # function
+           | [-+*/%<>&|^!?=]=  # compound assign / compare
+           | >>>=?             # zero-fill right shift
+           | ([-+:])           # doubles
+           | ([&|<>])          # logic / shift
+           | \\?\\.            # soak access
+           | \\.{2,3}          # range or splat
+        ) ///
+    ''', {
+      syntax:
+        identifier: /\b[a-z_][a-z_0-9]*\b/i
+        operator: /// ^ (
+          ?: [-=]>             # function
+           | [-+*/%<>&|^!?=]=  # compound assign / compare
+           | >>>=?             # zero-fill right shift
+           | ([-+:])           # doubles
+           | ([&|<>])          # logic / shift
+           | \?\.              # soak access
+           | \.{2,3}           # range or splat
+        ) ///
+    }
+
+  it 'parses nested objects', ->
+    compilesTo(
+      """
+      a:
+        b: c: false
+        "d": 44
+        3: "t"
+      e: 'str'
+      """
+      { a: { b: { c: false }, d: 44, 3: 't' }, e: 'str' }
+    )
+
+  it 'parses nested objects in arrays', ->
+    compilesTo(
+      """
+      o:
+        [
+                  a: 'x'
+                  b: 'y'
+                  c:
+                      d: 'z'
+            ,
+                  a: 'x'
+                  b: 'y'
+        ]
+      """
+      o: [
+        { a: 'x', b: 'y', c: { d: 'z' } }
+        { a: 'x', b: 'y' }
+      ]
+    )
+
+  describe 'reviver functions', ->
+    calls = expected = source = reviver = null
+    beforeEach ->
+      calls = []
+      reviver = (key, value) ->
+        # Test: called on parent object
+        @x = 'magic' if key == '4'
+
+        calls.push key
+        if typeof value == 'number' then value * 2
+        else value
+
+      source = JSON.stringify {
+        "1": 1, "2": 2,"3": {"4": 4, "5": {"6": 6}}
+      }
+      expected =
+        1: 2
+        2: 4
+        3: { x: 'magic', 4: 8, 5: { 6: 12 } }
+
+    it 'supports them', ->
+      assert.deepEqual(
+        expected, CSON.parse(source, reviver)
+      )
+      # See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
+      assert.deepEqual calls, [ '1', '2', '4', '6', '5', '3', '' ]
+
+    it 'works just like JSON.parse', ->
+      assert.deepEqual(
+        expected, JSON.parse(source, reviver)
+      )
+      # See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
+      assert.deepEqual calls, [ '1', '2', '4', '6', '5', '3', '' ]
diff --git a/test/stringify.test.coffee b/test/stringify.test.coffee
new file mode 100644
index 0000000..a09c467
--- /dev/null
+++ b/test/stringify.test.coffee
@@ -0,0 +1,154 @@
+
+{ equal } = require 'assertive'
+
+CSON = require '../'
+cson = (obj, visitor, space = 2) -> CSON.stringify obj, visitor, space
+
+describe 'CSON.stringify', ->
+  it 'handles null', ->
+    equal 'null', cson null
+
+  it 'handles boolean values', ->
+    equal 'true',  cson true
+    equal 'false', cson false
+
+  it 'handles the empty object', ->
+    equal '{}', cson {}
+
+  it 'handles the empty array', ->
+    equal '[]', cson []
+
+  it 'handles numbers', ->
+    equal '0.42',    cson 0.42
+    equal '42',      cson 42
+    equal '1.2e+90', cson 1.2e+90
+
+  it 'handles single-line strings', ->
+    equal '"hello!"', cson 'hello!'
+
+  it 'handles multi-line strings', ->
+    equal """
+      '''
+        I am your average multi-line string,
+        and I have a sneaky \\''' in here, too
+      '''
+    """, cson """
+      I am your average multi-line string,
+      and I have a sneaky ''' in here, too
+    """
+
+  it 'handles multi-line strings (with 0 indentation)', ->
+    equal """
+    "I am your average multi-line string,\\nand I have a sneaky ''' in here, too"
+    """, cson """
+      I am your average multi-line string,
+      and I have a sneaky ''' in here, too
+    """, null, 0
+
+  it 'handles multi-line strings w/ backslash', ->
+    test = '\\\n\\'
+    expected = "'''\n  \\\\\n  \\\\\n'''"
+    equal test, CSON.parse(cson test)
+    equal expected, cson test
+
+  it 'handles arrays', ->
+    equal '''
+      [
+        [
+          1
+        ]
+        null
+        []
+        {
+          a: "str"
+        }
+        {}
+      ]
+    ''', cson [ [1], null, [], a: 'str', {} ]
+
+  it 'handles arrays (with 0 indentation)', ->
+    equal '''
+    [[1],null,[],{a:"str"},{}]
+    ''', cson [ [1], null, [], a: 'str', {} ], null, 0
+
+  it 'handles objects', ->
+    equal '''
+      "": "empty"
+      "non\\nidentifier": true
+      default: false
+      emptyObject: {}
+      nested:
+        string: "too"
+      array: [
+        {}
+        []
+      ]
+    ''', cson {
+      '': 'empty'
+      "non\nidentifier": true
+      default: false
+      emptyObject: {}
+      nested: {
+        string: 'too'
+      }
+      array: [
+        {}
+        []
+      ]
+    }
+
+  it 'handles objects (with 0 indentation)', ->
+    equal '''
+    "":"empty","non\\nidentifier":true,default:false,nested:{string:"too"},array:[{},[]]
+    ''', cson {
+      '': 'empty'
+      "non\nidentifier": true
+      default: false
+      nested: {
+        string: 'too'
+      }
+      array: [
+        {}
+        []
+      ]
+    }, null, 0
+
+  it 'handles NaN and +/-Infinity like JSON.stringify does', ->
+    equal 'null', cson NaN
+    equal 'null', cson +Infinity
+    equal 'null', cson -Infinity
+
+  it 'handles undefined like JSON.stringify does', ->
+    equal undefined, cson undefined
+
+  it 'handles functions like JSON.stringify does', ->
+    equal undefined, cson ->
+
+  it 'accepts no more than ten indentation steps, just like JSON.stringify', ->
+    equal '''
+      x:
+                "don't": "be silly, will'ya?"
+    ''', cson { x: { "don't": "be silly, will'ya?" } }, null, Infinity
+
+  it 'lets people that really want to indent with tabs', ->
+    equal '''
+      x:
+      \t\t"super-tabby": true
+    ''', cson { x: { 'super-tabby': yes } }, null, '\t\t'
+
+  it 'handles indentation by NaN', ->
+    equal '[1]', cson([ 1 ], null, NaN)
+
+  it 'handles indentation by floating point numbers', ->
+    equal '[\n   1\n]', cson([ 1 ], null, 3.9)
+
+  it 'is bug compatible with JSON.stringify for non-whitespace indention', ->
+    equal '''
+      x:
+      ecma-262strange: true
+    ''', cson { x: { strange: yes } }, null, 'ecma-262'
+
+  it 'handles visitor functions', ->
+    equal '''
+      keep: 1
+    ''', cson {filter: 'me', keep: 1}, (k, v) -> v unless typeof v is 'string'

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-cson-parser.git



More information about the Pkg-javascript-commits mailing list