[Pkg-javascript-commits] [node-levn] 01/10: Import Upstream version 0.3.0

Praveen Arimbrathodiyil praveen at moszumanska.debian.org
Thu Oct 13 06:08:59 UTC 2016


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

praveen pushed a commit to branch master
in repository node-levn.

commit 0493303399c629d2393801c05faa8fce0d3ab4ef
Author: Praveen Arimbrathodiyil <praveen at debian.org>
Date:   Tue Oct 11 17:40:27 2016 +0530

    Import Upstream version 0.3.0
---
 .gitignore           |   4 +
 .travis.yml          |   3 +
 LICENSE              |  22 ++++
 Makefile             |  42 ++++++++
 README.md            | 196 +++++++++++++++++++++++++++++++++
 lib/cast.js          | 298 +++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/coerce.js        | 285 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/index.js         |  22 ++++
 lib/parse-string.js  | 113 +++++++++++++++++++
 lib/parse.js         | 102 ++++++++++++++++++
 package.json         |  47 ++++++++
 package.json.ls      |  42 ++++++++
 src/cast.ls          | 103 ++++++++++++++++++
 src/index.ls         |  15 +++
 src/parse-string.ls  |  96 +++++++++++++++++
 test/cast.ls         | 297 ++++++++++++++++++++++++++++++++++++++++++++++++++
 test/index.ls        |   6 ++
 test/parse-string.ls | 108 +++++++++++++++++++
 18 files changed, 1801 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..273d5bc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.swp
+node_modules
+coverage
+*.t
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..baa0031
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - 0.8
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..525b118
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) George Zahariev
+
+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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..46cc62b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,42 @@
+default: all
+
+SRC = $(shell find src -name "*.ls" -type f | sort)
+LIB = $(SRC:src/%.ls=lib/%.js)
+
+LS = node_modules/livescript
+LSC = node_modules/.bin/lsc
+MOCHA = node_modules/.bin/mocha
+MOCHA2 = node_modules/.bin/_mocha
+ISTANBUL = node_modules/.bin/istanbul
+
+package.json: package.json.ls
+	$(LSC) --compile package.json.ls
+
+lib:
+	mkdir -p lib/
+
+lib/%.js: src/%.ls lib
+	$(LSC) --compile --output lib "$<"
+
+.PHONY: build test coverage dev-install loc clean
+
+all: build
+
+build: $(LIB) package.json
+
+test: build
+	$(MOCHA) --reporter dot --ui tdd --compilers ls:$(LS)
+
+coverage: build
+	$(ISTANBUL) cover $(MOCHA2) -- --reporter dot --ui tdd --compilers ls:$(LS)
+
+dev-install: package.json
+	npm install .
+
+loc:
+	wc -l $(SRC)
+
+clean:
+	rm -f package.json
+	rm -rf lib
+	rm -rf coverage
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bb9ffea
--- /dev/null
+++ b/README.md
@@ -0,0 +1,196 @@
+# levn [![Build Status](https://travis-ci.org/gkz/levn.png)](https://travis-ci.org/gkz/levn) <a name="levn" />
+__Light ECMAScript (JavaScript) Value Notation__
+Levn is a library which allows you to parse a string into a JavaScript value based on an expected type. It is meant for short amounts of human entered data (eg. config files, command line arguments).
+
+Levn aims to concisely describe JavaScript values in text, and allow for the extraction and validation of those values. Levn uses [type-check](https://github.com/gkz/type-check) for its type format, and to validate the results. MIT license. Version 0.3.0.
+
+__How is this different than JSON?__ levn is meant to be written by humans only, is (due to the previous point) much more concise, can be validated against supplied types, has regex and date literals, and can easily be extended with custom types. On the other hand, it is probably slower and thus less efficient at transporting large amounts of data, which is fine since this is not its purpose.
+
+    npm install levn
+
+For updates on levn, [follow me on twitter](https://twitter.com/gkzahariev).
+
+
+## Quick Examples
+
+```js
+var parse = require('levn').parse;
+parse('Number', '2');      // 2
+parse('String', '2');      // '2'
+parse('String', 'levn');   // 'levn'
+parse('String', 'a b');    // 'a b'
+parse('Boolean', 'true');  // true
+
+parse('Date', '#2011-11-11#'); // (Date object)
+parse('Date', '2011-11-11');   // (Date object)
+parse('RegExp', '/[a-z]/gi');  // /[a-z]/gi
+parse('RegExp', 're');         // /re/
+parse('Int', '2');             // 2
+
+parse('Number | String', 'str'); // 'str'
+parse('Number | String', '2');   // 2
+
+parse('[Number]', '[1,2,3]');                      // [1,2,3]
+parse('(String, Boolean)', '(hi, false)');         // ['hi', false]
+parse('{a: String, b: Number}', '{a: str, b: 2}'); // {a: 'str', b: 2}
+
+// at the top level, you can ommit surrounding delimiters
+parse('[Number]', '1,2,3');                      // [1,2,3]
+parse('(String, Boolean)', 'hi, false');         // ['hi', false]
+parse('{a: String, b: Number}', 'a: str, b: 2'); // {a: 'str', b: 2}
+
+// wildcard - auto choose type
+parse('*', '[hi,(null,[42]),{k: true}]'); // ['hi', [null, [42]], {k: true}]
+```
+## Usage
+
+`require('levn');` returns an object that exposes three properties. `VERSION` is the current version of the library as a string. `parse` and `parsedTypeParse` are functions.
+
+```js
+// parse(type, input, options);
+parse('[Number]', '1,2,3'); // [1, 2, 3]
+
+// parsedTypeParse(parsedType, input, options);
+var parsedType = require('type-check').parseType('[Number]');
+parsedTypeParse(parsedType, '1,2,3'); // [1, 2, 3]
+```
+
+### parse(type, input, options)
+
+`parse` casts the string `input` into a JavaScript value according to the specified `type` in the [type format](https://github.com/gkz/type-check#type-format) (and taking account the optional `options`) and returns the resulting JavaScript value.
+
+##### arguments
+* type - `String` - the type written in the [type format](https://github.com/gkz/type-check#type-format) which to check against
+* input - `String` - the value written in the [levn format](#levn-format)
+* options - `Maybe Object` - an optional parameter specifying additional [options](#options)
+
+##### returns
+`*` - the resulting JavaScript value
+
+##### example
+```js
+parse('[Number]', '1,2,3'); // [1, 2, 3]
+```
+
+### parsedTypeParse(parsedType, input, options)
+
+`parsedTypeParse` casts the string `input` into a JavaScript value according to the specified `type` which has already been parsed (and taking account the optional `options`) and returns the resulting JavaScript value. You can parse a type using the [type-check](https://github.com/gkz/type-check) library's `parseType` function.
+
+##### arguments
+* type - `Object` - the type in the parsed type format which to check against
+* input - `String` - the value written in the [levn format](#levn-format)
+* options - `Maybe Object` - an optional parameter specifying additional [options](#options)
+
+##### returns
+`*` - the resulting JavaScript value
+
+##### example
+```js
+var parsedType = require('type-check').parseType('[Number]');
+parsedTypeParse(parsedType, '1,2,3'); // [1, 2, 3]
+```
+
+## Levn Format
+
+Levn can use the type information you provide to choose the appropriate value to produce from the input. For the same input, it will choose a different output value depending on the type provided. For example, `parse('Number', '2')` will produce the number `2`, but `parse('String', '2')` will produce the string `"2"`.
+
+If you do not provide type information, and simply use `*`, levn will parse the input according the unambiguous "explicit" mode, which we will now detail - you can also set the `explicit` option to true manually in the [options](#options).
+
+* `"string"`, `'string'` are parsed as a String, eg. `"a msg"` is `"a msg"`
+* `#date#` is parsed as a Date, eg. `#2011-11-11#` is `new Date('2011-11-11')`
+* `/regexp/flags` is parsed as a RegExp, eg. `/re/gi` is `/re/gi`
+* `undefined`, `null`, `NaN`, `true`, and `false` are all their JavaScript equivalents
+* `[element1, element2, etc]` is an Array, and the casting procedure is recursively applied to each element. Eg. `[1,2,3]` is `[1,2,3]`.
+* `(element1, element2, etc)` is an tuple, and the casting procedure is recursively applied to each element. Eg. `(1, a)` is `(1, a)` (is `[1, 'a']`).
+* `{key1: val1, key2: val2, ...}` is an Object, and the casting procedure is recursively applied to each property. Eg. `{a: 1, b: 2}` is `{a: 1, b: 2}`.
+* Any test which does not fall under the above, and which does not contain special characters (`[``]``(``)``{``}``:``,`) is a string, eg. `$12- blah` is `"$12- blah"`.
+
+If you do provide type information, you can make your input more concise as the program already has some information about what it expects. Please see the [type format](https://github.com/gkz/type-check#type-format) section of [type-check](https://github.com/gkz/type-check) for more information about how to specify types. There are some rules about what levn can do with the information:
+
+* If a String is expected, and only a String, all characters of the input (including any special ones) will become part of the output. Eg. `[({})]` is `"[({})]"`, and `"hi"` is `'"hi"'`.
+* If a Date is expected, the surrounding `#` can be omitted from date literals. Eg. `2011-11-11` is `new Date('2011-11-11')`.
+* If a RegExp is expected, no flags need to be specified, and the regex is not using any of the special characters,the opening and closing `/` can be omitted - this will have the affect of setting the source of the regex to the input. Eg. `regex` is `/regex/`.
+* If an Array is expected, and it is the root node (at the top level), the opening `[` and closing `]` can be omitted. Eg. `1,2,3` is `[1,2,3]`.
+* If a tuple is expected, and it is the root node (at the top level), the opening `(` and closing `)` can be omitted. Eg. `1, a` is `(1, a)` (is `[1, 'a']`).
+* If an Object is expected, and it is the root node (at the top level), the opening `{` and closing `}` can be omitted. Eg `a: 1, b: 2` is `{a: 1, b: 2}`.
+
+If you list multiple types (eg. `Number | String`), it will first attempt to cast to the first type and then validate - if the validation fails it will move on to the next type and so forth, left to right. You must be careful as some types will succeed with any input, such as String. Thus put String at the end of your list. In non-explicit mode, Date and RegExp will succeed with a large variety of input - also be careful with these and list them near the end if not last in your list.
+
+Whitespace between special characters and elements is inconsequential.
+
+## Options
+
+Options is an object. It is an optional parameter to the `parse` and `parsedTypeParse` functions.
+
+### Explicit
+
+A `Boolean`. By default it is `false`.
+
+__Example:__
+
+```js
+parse('RegExp', 're', {explicit: false});          // /re/
+parse('RegExp', 're', {explicit: true});           // Error: ... does not type check...
+parse('RegExp | String', 're', {explicit: true});  // 're'
+```
+
+`explicit` sets whether to be in explicit mode or not. Using `*` automatically activates explicit mode. For more information, read the [levn format](#levn-format) section.
+
+### customTypes
+
+An `Object`. Empty `{}` by default.
+
+__Example:__
+
+```js
+var options = {
+  customTypes: {
+    Even: {
+      typeOf: 'Number',
+      validate: function (x) {
+        return x % 2 === 0;
+      },
+      cast: function (x) {
+        return {type: 'Just', value: parseInt(x)};
+      }
+    }
+  }
+}
+parse('Even', '2', options); // 2
+parse('Even', '3', options); // Error: Value: "3" does not type check...
+```
+
+__Another Example:__
+```js
+function Person(name, age){
+  this.name = name;
+  this.age = age;
+}
+var options = {
+  customTypes: {
+    Person: {
+      typeOf: 'Object',
+      validate: function (x) {
+        x instanceof Person;
+      },
+      cast: function (value, options, typesCast) {
+        var name, age;
+        if ({}.toString.call(value).slice(8, -1) !== 'Object') {
+          return {type: 'Nothing'};
+        }
+        name = typesCast(value.name, [{type: 'String'}], options);
+        age = typesCast(value.age, [{type: 'Numger'}], options);
+        return {type: 'Just', value: new Person(name, age)};
+    }
+  }
+}
+parse('Person', '{name: Laura, age: 25}', options); // Person {name: 'Laura', age: 25}
+```
+
+`customTypes` is an object whose keys are the name of the types, and whose values are an object with three properties, `typeOf`, `validate`, and `cast`. For more information about `typeOf` and `validate`, please see the [custom types](https://github.com/gkz/type-check#custom-types) section of type-check.
+
+`cast` is a function which receives three arguments, the value under question, options, and the typesCast function. In `cast`, attempt to cast the value into the specified type. If you are successful, return an object in the format `{type: 'Just', value: CAST-VALUE}`, if you know it won't work, return `{type: 'Nothing'}`.  You can use the `typesCast` function to cast any child values. Remember to pass `options` to it. In your function you can also check for `options.explicit` and act acc [...]
+
+## Technical About
+
+`levn` is written in [LiveScript](http://livescript.net/) - a language that compiles to JavaScript. It uses [type-check](https://github.com/gkz/type-check) to both parse types and validate values. It also uses the [prelude.ls](http://preludels.com/) library.
diff --git a/lib/cast.js b/lib/cast.js
new file mode 100644
index 0000000..411e29d
--- /dev/null
+++ b/lib/cast.js
@@ -0,0 +1,298 @@
+// Generated by LiveScript 1.4.0
+(function(){
+  var parsedTypeCheck, types, toString$ = {}.toString;
+  parsedTypeCheck = require('type-check').parsedTypeCheck;
+  types = {
+    '*': function(value, options){
+      switch (toString$.call(value).slice(8, -1)) {
+      case 'Array':
+        return typeCast(value, {
+          type: 'Array'
+        }, options);
+      case 'Object':
+        return typeCast(value, {
+          type: 'Object'
+        }, options);
+      default:
+        return {
+          type: 'Just',
+          value: typesCast(value, [
+            {
+              type: 'Undefined'
+            }, {
+              type: 'Null'
+            }, {
+              type: 'NaN'
+            }, {
+              type: 'Boolean'
+            }, {
+              type: 'Number'
+            }, {
+              type: 'Date'
+            }, {
+              type: 'RegExp'
+            }, {
+              type: 'Array'
+            }, {
+              type: 'Object'
+            }, {
+              type: 'String'
+            }
+          ], (options.explicit = true, options))
+        };
+      }
+    },
+    Undefined: function(it){
+      if (it === 'undefined' || it === void 8) {
+        return {
+          type: 'Just',
+          value: void 8
+        };
+      } else {
+        return {
+          type: 'Nothing'
+        };
+      }
+    },
+    Null: function(it){
+      if (it === 'null') {
+        return {
+          type: 'Just',
+          value: null
+        };
+      } else {
+        return {
+          type: 'Nothing'
+        };
+      }
+    },
+    NaN: function(it){
+      if (it === 'NaN') {
+        return {
+          type: 'Just',
+          value: NaN
+        };
+      } else {
+        return {
+          type: 'Nothing'
+        };
+      }
+    },
+    Boolean: function(it){
+      if (it === 'true') {
+        return {
+          type: 'Just',
+          value: true
+        };
+      } else if (it === 'false') {
+        return {
+          type: 'Just',
+          value: false
+        };
+      } else {
+        return {
+          type: 'Nothing'
+        };
+      }
+    },
+    Number: function(it){
+      return {
+        type: 'Just',
+        value: +it
+      };
+    },
+    Int: function(it){
+      return {
+        type: 'Just',
+        value: +it
+      };
+    },
+    Float: function(it){
+      return {
+        type: 'Just',
+        value: +it
+      };
+    },
+    Date: function(value, options){
+      var that;
+      if (that = /^\#([\s\S]*)\#$/.exec(value)) {
+        return {
+          type: 'Just',
+          value: new Date(+that[1] || that[1])
+        };
+      } else if (options.explicit) {
+        return {
+          type: 'Nothing'
+        };
+      } else {
+        return {
+          type: 'Just',
+          value: new Date(+value || value)
+        };
+      }
+    },
+    RegExp: function(value, options){
+      var that;
+      if (that = /^\/([\s\S]*)\/([gimy]*)$/.exec(value)) {
+        return {
+          type: 'Just',
+          value: new RegExp(that[1], that[2])
+        };
+      } else if (options.explicit) {
+        return {
+          type: 'Nothing'
+        };
+      } else {
+        return {
+          type: 'Just',
+          value: new RegExp(value)
+        };
+      }
+    },
+    Array: function(value, options){
+      return castArray(value, {
+        of: [{
+          type: '*'
+        }]
+      }, options);
+    },
+    Object: function(value, options){
+      return castFields(value, {
+        of: {}
+      }, options);
+    },
+    String: function(it){
+      var that;
+      if (toString$.call(it).slice(8, -1) !== 'String') {
+        return {
+          type: 'Nothing'
+        };
+      }
+      if (that = it.match(/^'([\s\S]*)'$/)) {
+        return {
+          type: 'Just',
+          value: that[1].replace(/\\'/g, "'")
+        };
+      } else if (that = it.match(/^"([\s\S]*)"$/)) {
+        return {
+          type: 'Just',
+          value: that[1].replace(/\\"/g, '"')
+        };
+      } else {
+        return {
+          type: 'Just',
+          value: it
+        };
+      }
+    }
+  };
+  function castArray(node, type, options){
+    var typeOf, element;
+    if (toString$.call(node).slice(8, -1) !== 'Array') {
+      return {
+        type: 'Nothing'
+      };
+    }
+    typeOf = type.of;
+    return {
+      type: 'Just',
+      value: (function(){
+        var i$, ref$, len$, results$ = [];
+        for (i$ = 0, len$ = (ref$ = node).length; i$ < len$; ++i$) {
+          element = ref$[i$];
+          results$.push(typesCast(element, typeOf, options));
+        }
+        return results$;
+      }())
+    };
+  }
+  function castTuple(node, type, options){
+    var result, i, i$, ref$, len$, types, cast;
+    if (toString$.call(node).slice(8, -1) !== 'Array') {
+      return {
+        type: 'Nothing'
+      };
+    }
+    result = [];
+    i = 0;
+    for (i$ = 0, len$ = (ref$ = type.of).length; i$ < len$; ++i$) {
+      types = ref$[i$];
+      cast = typesCast(node[i], types, options);
+      if (toString$.call(cast).slice(8, -1) !== 'Undefined') {
+        result.push(cast);
+      }
+      i++;
+    }
+    if (node.length <= i) {
+      return {
+        type: 'Just',
+        value: result
+      };
+    } else {
+      return {
+        type: 'Nothing'
+      };
+    }
+  }
+  function castFields(node, type, options){
+    var typeOf, key, value;
+    if (toString$.call(node).slice(8, -1) !== 'Object') {
+      return {
+        type: 'Nothing'
+      };
+    }
+    typeOf = type.of;
+    return {
+      type: 'Just',
+      value: (function(){
+        var ref$, resultObj$ = {};
+        for (key in ref$ = node) {
+          value = ref$[key];
+          resultObj$[typesCast(key, [{
+            type: 'String'
+          }], options)] = typesCast(value, typeOf[key] || [{
+            type: '*'
+          }], options);
+        }
+        return resultObj$;
+      }())
+    };
+  }
+  function typeCast(node, typeObj, options){
+    var type, structure, castFunc, ref$;
+    type = typeObj.type, structure = typeObj.structure;
+    if (type) {
+      castFunc = ((ref$ = options.customTypes[type]) != null ? ref$.cast : void 8) || types[type];
+      if (!castFunc) {
+        throw new Error("Type not defined: " + type + ".");
+      }
+      return castFunc(node, options, typesCast);
+    } else {
+      switch (structure) {
+      case 'array':
+        return castArray(node, typeObj, options);
+      case 'tuple':
+        return castTuple(node, typeObj, options);
+      case 'fields':
+        return castFields(node, typeObj, options);
+      }
+    }
+  }
+  function typesCast(node, types, options){
+    var i$, len$, type, ref$, valueType, value;
+    for (i$ = 0, len$ = types.length; i$ < len$; ++i$) {
+      type = types[i$];
+      ref$ = typeCast(node, type, options), valueType = ref$.type, value = ref$.value;
+      if (valueType === 'Nothing') {
+        continue;
+      }
+      if (parsedTypeCheck([type], value, {
+        customTypes: options.customTypes
+      })) {
+        return value;
+      }
+    }
+    throw new Error("Value " + JSON.stringify(node) + " does not type check against " + JSON.stringify(types) + ".");
+  }
+  module.exports = typesCast;
+}).call(this);
diff --git a/lib/coerce.js b/lib/coerce.js
new file mode 100644
index 0000000..027b6da
--- /dev/null
+++ b/lib/coerce.js
@@ -0,0 +1,285 @@
+// Generated by LiveScript 1.2.0
+(function(){
+  var parsedTypeCheck, types, toString$ = {}.toString;
+  parsedTypeCheck = require('type-check').parsedTypeCheck;
+  types = {
+    '*': function(it){
+      switch (toString$.call(it).slice(8, -1)) {
+      case 'Array':
+        return coerceType(it, {
+          type: 'Array'
+        });
+      case 'Object':
+        return coerceType(it, {
+          type: 'Object'
+        });
+      default:
+        return {
+          type: 'Just',
+          value: coerceTypes(it, [
+            {
+              type: 'Undefined'
+            }, {
+              type: 'Null'
+            }, {
+              type: 'NaN'
+            }, {
+              type: 'Boolean'
+            }, {
+              type: 'Number'
+            }, {
+              type: 'Date'
+            }, {
+              type: 'RegExp'
+            }, {
+              type: 'Array'
+            }, {
+              type: 'Object'
+            }, {
+              type: 'String'
+            }
+          ], {
+            explicit: true
+          })
+        };
+      }
+    },
+    Undefined: function(it){
+      if (it === 'undefined' || it === void 8) {
+        return {
+          type: 'Just',
+          value: void 8
+        };
+      } else {
+        return {
+          type: 'Nothing'
+        };
+      }
+    },
+    Null: function(it){
+      if (it === 'null') {
+        return {
+          type: 'Just',
+          value: null
+        };
+      } else {
+        return {
+          type: 'Nothing'
+        };
+      }
+    },
+    NaN: function(it){
+      if (it === 'NaN') {
+        return {
+          type: 'Just',
+          value: NaN
+        };
+      } else {
+        return {
+          type: 'Nothing'
+        };
+      }
+    },
+    Boolean: function(it){
+      if (it === 'true') {
+        return {
+          type: 'Just',
+          value: true
+        };
+      } else if (it === 'false') {
+        return {
+          type: 'Just',
+          value: false
+        };
+      } else {
+        return {
+          type: 'Nothing'
+        };
+      }
+    },
+    Number: function(it){
+      return {
+        type: 'Just',
+        value: +it
+      };
+    },
+    Int: function(it){
+      return {
+        type: 'Just',
+        value: parseInt(it)
+      };
+    },
+    Float: function(it){
+      return {
+        type: 'Just',
+        value: parseFloat(it)
+      };
+    },
+    Date: function(value, options){
+      var that;
+      if (that = /^\#(.*)\#$/.exec(value)) {
+        return {
+          type: 'Just',
+          value: new Date(+that[1] || that[1])
+        };
+      } else if (options.explicit) {
+        return {
+          type: 'Nothing'
+        };
+      } else {
+        return {
+          type: 'Just',
+          value: new Date(+value || value)
+        };
+      }
+    },
+    RegExp: function(value, options){
+      var that;
+      if (that = /^\/(.*)\/([gimy]*)$/.exec(value)) {
+        return {
+          type: 'Just',
+          value: new RegExp(that[1], that[2])
+        };
+      } else if (options.explicit) {
+        return {
+          type: 'Nothing'
+        };
+      } else {
+        return {
+          type: 'Just',
+          value: new RegExp(value)
+        };
+      }
+    },
+    Array: function(it){
+      return coerceArray(it, {
+        of: [{
+          type: '*'
+        }]
+      });
+    },
+    Object: function(it){
+      return coerceFields(it, {
+        of: {}
+      });
+    },
+    String: function(it){
+      var that;
+      if (toString$.call(it).slice(8, -1) !== 'String') {
+        return {
+          type: 'Nothing'
+        };
+      }
+      if (that = it.match(/^'(.*)'$/)) {
+        return {
+          type: 'Just',
+          value: that[1]
+        };
+      } else if (that = it.match(/^"(.*)"$/)) {
+        return {
+          type: 'Just',
+          value: that[1]
+        };
+      } else {
+        return {
+          type: 'Just',
+          value: it
+        };
+      }
+    }
+  };
+  function coerceArray(node, type){
+    var typeOf, element;
+    if (toString$.call(node).slice(8, -1) !== 'Array') {
+      return {
+        type: 'Nothing'
+      };
+    }
+    typeOf = type.of;
+    return {
+      type: 'Just',
+      value: (function(){
+        var i$, ref$, len$, results$ = [];
+        for (i$ = 0, len$ = (ref$ = node).length; i$ < len$; ++i$) {
+          element = ref$[i$];
+          results$.push(coerceTypes(element, typeOf));
+        }
+        return results$;
+      }())
+    };
+  }
+  function coerceTuple(node, type){
+    var result, i$, ref$, len$, i, types, that;
+    if (toString$.call(node).slice(8, -1) !== 'Array') {
+      return {
+        type: 'Nothing'
+      };
+    }
+    result = [];
+    for (i$ = 0, len$ = (ref$ = type.of).length; i$ < len$; ++i$) {
+      i = i$;
+      types = ref$[i$];
+      if (that = coerceTypes(node[i], types)) {
+        result.push(that);
+      }
+    }
+    return {
+      type: 'Just',
+      value: result
+    };
+  }
+  function coerceFields(node, type){
+    var typeOf, key, value;
+    if (toString$.call(node).slice(8, -1) !== 'Object') {
+      return {
+        type: 'Nothing'
+      };
+    }
+    typeOf = type.of;
+    return {
+      type: 'Just',
+      value: (function(){
+        var ref$, results$ = {};
+        for (key in ref$ = node) {
+          value = ref$[key];
+          results$[key] = coerceTypes(value, typeOf[key] || [{
+            type: '*'
+          }]);
+        }
+        return results$;
+      }())
+    };
+  }
+  function coerceType(node, typeObj, options){
+    var type, structure, coerceFunc;
+    type = typeObj.type, structure = typeObj.structure;
+    if (type) {
+      coerceFunc = types[type];
+      return coerceFunc(node, options);
+    } else {
+      switch (structure) {
+      case 'array':
+        return coerceArray(node, typeObj);
+      case 'tuple':
+        return coerceTuple(node, typeObj);
+      case 'fields':
+        return coerceFields(node, typeObj);
+      }
+    }
+  }
+  function coerceTypes(node, types, options){
+    var i$, len$, type, ref$, valueType, value;
+    for (i$ = 0, len$ = types.length; i$ < len$; ++i$) {
+      type = types[i$];
+      ref$ = coerceType(node, type, options), valueType = ref$.type, value = ref$.value;
+      if (valueType === 'Nothing') {
+        continue;
+      }
+      if (parsedTypeCheck([type], value)) {
+        return value;
+      }
+    }
+    throw new Error("Value " + JSON.stringify(node) + " does not type check against " + JSON.stringify(types) + ".");
+  }
+  module.exports = coerceTypes;
+}).call(this);
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 0000000..4adae30
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,22 @@
+// Generated by LiveScript 1.4.0
+(function(){
+  var parseString, cast, parseType, VERSION, parsedTypeParse, parse;
+  parseString = require('./parse-string');
+  cast = require('./cast');
+  parseType = require('type-check').parseType;
+  VERSION = '0.3.0';
+  parsedTypeParse = function(parsedType, string, options){
+    options == null && (options = {});
+    options.explicit == null && (options.explicit = false);
+    options.customTypes == null && (options.customTypes = {});
+    return cast(parseString(parsedType, string, options), parsedType, options);
+  };
+  parse = function(type, string, options){
+    return parsedTypeParse(parseType(type), string, options);
+  };
+  module.exports = {
+    VERSION: VERSION,
+    parse: parse,
+    parsedTypeParse: parsedTypeParse
+  };
+}).call(this);
diff --git a/lib/parse-string.js b/lib/parse-string.js
new file mode 100644
index 0000000..d573975
--- /dev/null
+++ b/lib/parse-string.js
@@ -0,0 +1,113 @@
+// Generated by LiveScript 1.4.0
+(function(){
+  var reject, special, tokenRegex;
+  reject = require('prelude-ls').reject;
+  function consumeOp(tokens, op){
+    if (tokens[0] === op) {
+      return tokens.shift();
+    } else {
+      throw new Error("Expected '" + op + "', but got '" + tokens[0] + "' instead in " + JSON.stringify(tokens) + ".");
+    }
+  }
+  function maybeConsumeOp(tokens, op){
+    if (tokens[0] === op) {
+      return tokens.shift();
+    }
+  }
+  function consumeList(tokens, arg$, hasDelimiters){
+    var open, close, result, untilTest;
+    open = arg$[0], close = arg$[1];
+    if (hasDelimiters) {
+      consumeOp(tokens, open);
+    }
+    result = [];
+    untilTest = "," + (hasDelimiters ? close : '');
+    while (tokens.length && (hasDelimiters && tokens[0] !== close)) {
+      result.push(consumeElement(tokens, untilTest));
+      maybeConsumeOp(tokens, ',');
+    }
+    if (hasDelimiters) {
+      consumeOp(tokens, close);
+    }
+    return result;
+  }
+  function consumeArray(tokens, hasDelimiters){
+    return consumeList(tokens, ['[', ']'], hasDelimiters);
+  }
+  function consumeTuple(tokens, hasDelimiters){
+    return consumeList(tokens, ['(', ')'], hasDelimiters);
+  }
+  function consumeFields(tokens, hasDelimiters){
+    var result, untilTest, key;
+    if (hasDelimiters) {
+      consumeOp(tokens, '{');
+    }
+    result = {};
+    untilTest = "," + (hasDelimiters ? '}' : '');
+    while (tokens.length && (!hasDelimiters || tokens[0] !== '}')) {
+      key = consumeValue(tokens, ':');
+      consumeOp(tokens, ':');
+      result[key] = consumeElement(tokens, untilTest);
+      maybeConsumeOp(tokens, ',');
+    }
+    if (hasDelimiters) {
+      consumeOp(tokens, '}');
+    }
+    return result;
+  }
+  function consumeValue(tokens, untilTest){
+    var out;
+    untilTest == null && (untilTest = '');
+    out = '';
+    while (tokens.length && -1 === untilTest.indexOf(tokens[0])) {
+      out += tokens.shift();
+    }
+    return out;
+  }
+  function consumeElement(tokens, untilTest){
+    switch (tokens[0]) {
+    case '[':
+      return consumeArray(tokens, true);
+    case '(':
+      return consumeTuple(tokens, true);
+    case '{':
+      return consumeFields(tokens, true);
+    default:
+      return consumeValue(tokens, untilTest);
+    }
+  }
+  function consumeTopLevel(tokens, types, options){
+    var ref$, type, structure, origTokens, result, finalResult, x$, y$;
+    ref$ = types[0], type = ref$.type, structure = ref$.structure;
+    origTokens = tokens.concat();
+    if (!options.explicit && types.length === 1 && ((!type && structure) || (type === 'Array' || type === 'Object'))) {
+      result = structure === 'array' || type === 'Array'
+        ? consumeArray(tokens, tokens[0] === '[')
+        : structure === 'tuple'
+          ? consumeTuple(tokens, tokens[0] === '(')
+          : consumeFields(tokens, tokens[0] === '{');
+      finalResult = tokens.length ? consumeElement(structure === 'array' || type === 'Array'
+        ? (x$ = origTokens, x$.unshift('['), x$.push(']'), x$)
+        : (y$ = origTokens, y$.unshift('('), y$.push(')'), y$)) : result;
+    } else {
+      finalResult = consumeElement(tokens);
+    }
+    return finalResult;
+  }
+  special = /\[\]\(\)}{:,/.source;
+  tokenRegex = RegExp('("(?:\\\\"|[^"])*")|(\'(?:\\\\\'|[^\'])*\')|(/(?:\\\\/|[^/])*/[a-zA-Z]*)|(#.*#)|([' + special + '])|([^\\s' + special + '](?:\\s*[^\\s' + special + ']+)*)|\\s*');
+  module.exports = function(types, string, options){
+    var tokens, node;
+    options == null && (options = {});
+    if (!options.explicit && types.length === 1 && types[0].type === 'String') {
+      return "'" + string.replace(/\\'/g, "\\\\'") + "'";
+    }
+    tokens = reject(not$, string.split(tokenRegex));
+    node = consumeTopLevel(tokens, types, options);
+    if (!node) {
+      throw new Error("Error parsing '" + string + "'.");
+    }
+    return node;
+  };
+  function not$(x){ return !x; }
+}).call(this);
diff --git a/lib/parse.js b/lib/parse.js
new file mode 100644
index 0000000..2beff0f
--- /dev/null
+++ b/lib/parse.js
@@ -0,0 +1,102 @@
+// Generated by LiveScript 1.2.0
+(function(){
+  var reject, special, tokenRegex;
+  reject = require('prelude-ls').reject;
+  function consumeOp(tokens, op){
+    if (tokens[0] === op) {
+      return tokens.shift();
+    } else {
+      throw new Error("Expected '" + op + "', but got '" + tokens[0] + "' instead in " + JSON.stringify(tokens) + ".");
+    }
+  }
+  function maybeConsumeOp(tokens, op){
+    if (tokens[0] === op) {
+      return tokens.shift();
+    }
+  }
+  function consumeList(tokens, delimiters, hasDelimiters){
+    var result;
+    if (hasDelimiters) {
+      consumeOp(tokens, delimiters[0]);
+    }
+    result = [];
+    while (tokens.length && tokens[0] !== delimiters[1]) {
+      result.push(consumeElement(tokens));
+      maybeConsumeOp(tokens, ',');
+    }
+    if (hasDelimiters) {
+      consumeOp(tokens, delimiters[1]);
+    }
+    return result;
+  }
+  function consumeArray(tokens, hasDelimiters){
+    return consumeList(tokens, ['[', ']'], hasDelimiters);
+  }
+  function consumeTuple(tokens, hasDelimiters){
+    return consumeList(tokens, ['(', ')'], hasDelimiters);
+  }
+  function consumeFields(tokens, hasDelimiters){
+    var result, key;
+    if (hasDelimiters) {
+      consumeOp(tokens, '{');
+    }
+    result = {};
+    while (tokens.length && (!hasDelimiters || tokens[0] !== '}')) {
+      key = tokens.shift();
+      consumeOp(tokens, ':');
+      result[key] = consumeElement(tokens);
+      maybeConsumeOp(tokens, ',');
+    }
+    if (hasDelimiters) {
+      consumeOp(tokens, '}');
+    }
+    return result;
+  }
+  function consumeElement(tokens){
+    switch (tokens[0]) {
+    case '[':
+      return consumeArray(tokens, true);
+    case '(':
+      return consumeTuple(tokens, true);
+    case '{':
+      return consumeFields(tokens, true);
+    default:
+      return tokens.shift();
+    }
+  }
+  function consumeTopLevel(tokens, types){
+    var ref$, type, structure, origTokens, result, finalResult, x$, y$;
+    ref$ = types[0], type = ref$.type, structure = ref$.structure;
+    origTokens = tokens.concat();
+    if (types.length === 1 && (structure || (type === 'Array' || type === 'Object'))) {
+      result = structure === 'array' || type === 'Array'
+        ? consumeArray(tokens, tokens[0] === '[')
+        : structure === 'tuple'
+          ? consumeTuple(tokens, tokens[0] === '(')
+          : consumeFields(tokens, tokens[0] === '{');
+      finalResult = tokens.length ? consumeElement(structure === 'array' || type === 'Array'
+        ? (x$ = origTokens, x$.unshift('['), x$.push(']'), x$)
+        : (y$ = origTokens, y$.unshift('('), y$.push(')'), y$)) : result;
+    } else {
+      finalResult = consumeElement(tokens);
+    }
+    if (tokens.length && origTokens.length) {
+      throw new Error("Unable to parse " + JSON.stringify(origTokens) + " of type " + JSON.stringify(types) + ".");
+    } else {
+      return finalResult;
+    }
+  }
+  special = /\[\]\(\)}{:,/.source;
+  tokenRegex = RegExp('("(?:[^"]|\\\\")*")|(\'(?:[^\']|\\\\\')*\')|(#.*#)|(/(?:\\\\/|[^/])*/[gimy]*)|([' + special + '])|([^\\s' + special + ']+)|\\s*');
+  module.exports = function(string, types){
+    var tokens, node;
+    tokens = reject(function(it){
+      return !it || /^\s+$/.test(it);
+    }, string.split(tokenRegex));
+    node = consumeTopLevel(tokens, types);
+    if (!node) {
+      throw new Error("Error parsing '" + string + "'.");
+    }
+    return node;
+  };
+}).call(this);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..56dfbc4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,47 @@
+{
+  "name": "levn",
+  "version": "0.3.0",
+  "author": "George Zahariev <z at georgezahariev.com>",
+  "description": "Light ECMAScript (JavaScript) Value Notation - human written, concise, typed, flexible",
+  "homepage": "https://github.com/gkz/levn",
+  "keywords": [
+    "levn",
+    "light",
+    "ecmascript",
+    "value",
+    "notation",
+    "json",
+    "typed",
+    "human",
+    "concise",
+    "typed",
+    "flexible"
+  ],
+  "files": [
+    "lib",
+    "README.md",
+    "LICENSE"
+  ],
+  "main": "./lib/",
+  "bugs": "https://github.com/gkz/levn/issues",
+  "license": "MIT",
+  "engines": {
+    "node": ">= 0.8.0"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/gkz/levn.git"
+  },
+  "scripts": {
+    "test": "make test"
+  },
+  "dependencies": {
+    "prelude-ls": "~1.1.2",
+    "type-check": "~0.3.2"
+  },
+  "devDependencies": {
+    "livescript": "~1.4.0",
+    "mocha": "~2.3.4",
+    "istanbul": "~0.4.1"
+  }
+}
diff --git a/package.json.ls b/package.json.ls
new file mode 100644
index 0000000..1e282fb
--- /dev/null
+++ b/package.json.ls
@@ -0,0 +1,42 @@
+name: 'levn'
+version: '0.3.0'
+
+author: 'George Zahariev <z at georgezahariev.com>'
+description: 'Light ECMAScript (JavaScript) Value Notation - human written, concise, typed, flexible'
+homepage: 'https://github.com/gkz/levn'
+keywords:
+  'levn'
+  'light'
+  'ecmascript'
+  'value'
+  'notation'
+  'json'
+  'typed'
+  'human'
+  'concise'
+  'typed'
+  'flexible'
+files:
+  'lib'
+  'README.md'
+  'LICENSE'
+main: './lib/'
+
+bugs: 'https://github.com/gkz/levn/issues'
+license: 'MIT'
+engines:
+  node: '>= 0.8.0'
+repository:
+  type: 'git'
+  url: 'git://github.com/gkz/levn.git'
+scripts:
+  test: "make test"
+
+dependencies:
+  'prelude-ls': '~1.1.2'
+  'type-check': '~0.3.2'
+
+dev-dependencies:
+  livescript: '~1.4.0'
+  mocha: '~2.3.4'
+  istanbul: '~0.4.1'
diff --git a/src/cast.ls b/src/cast.ls
new file mode 100644
index 0000000..d13ad97
--- /dev/null
+++ b/src/cast.ls
@@ -0,0 +1,103 @@
+{parsed-type-check} = require 'type-check'
+
+types =
+  '*': (value, options) ->
+    switch typeof! value
+    | 'Array'   => type-cast value, {type: 'Array'}, options
+    | 'Object'  => type-cast value, {type: 'Object'}, options
+    | otherwise => type: 'Just', value: types-cast value, [
+      * type: 'Undefined'
+      * type: 'Null'
+      * type: 'NaN'
+      * type: 'Boolean'
+      * type: 'Number'
+      * type: 'Date'
+      * type: 'RegExp'
+      * type: 'Array'
+      * type: 'Object'
+      * type: 'String'
+    ], options <<< {+explicit}
+  Undefined: -> if it is 'undefined' or it is void then {type: 'Just', value: void} else {type: 'Nothing'}
+  Null: -> if it is 'null' then {type: 'Just', value: null} else {type: 'Nothing'}
+  NaN: -> if it is 'NaN' then {type: 'Just', value: NaN} else {type: 'Nothing'}
+  Boolean: ->
+    if it is 'true'
+      type: 'Just', value: true
+    else if it is 'false'
+      type: 'Just', value: false
+    else
+      type: 'Nothing'
+  Number: -> type: 'Just', value: +it
+  Int: -> type: 'Just', value: +it
+  Float: -> type: 'Just', value: +it
+  Date: (value, options) ->
+    if /^\#([\s\S]*)\#$/.exec value
+      type: 'Just', value: new Date (+that.1 or that.1)
+    else if options.explicit
+      type: 'Nothing'
+    else
+      type: 'Just', value: new Date (+value or value)
+  RegExp: (value, options) ->
+    if /^\/([\s\S]*)\/([gimy]*)$/.exec value
+      type: 'Just', value: new RegExp that.1, that.2
+    else if options.explicit
+      type: 'Nothing'
+    else
+      type: 'Just', value: new RegExp value
+  Array: (value, options) -> cast-array value, {of: [{type: '*'}]}, options
+  Object: (value, options) -> cast-fields value, {of: {}}, options
+  String: ->
+    return type: 'Nothing' unless typeof! it is 'String'
+    if it.match /^'([\s\S]*)'$/
+      type: 'Just', value: that.1.replace /\\'/g, "'"
+    else if it.match /^"([\s\S]*)"$/
+      type: 'Just', value: that.1.replace /\\"/g, '"'
+    else
+      type: 'Just', value: it
+
+function cast-array node, type, options
+  return {type: 'Nothing'} unless typeof! node is 'Array'
+  type-of = type.of
+  type: 'Just', value: [types-cast element, type-of, options for element in node]
+
+function cast-tuple node, type, options
+  return {type: 'Nothing'} unless typeof! node is 'Array'
+  result = []
+  i = 0
+  for types in type.of
+    cast = types-cast node[i], types, options
+    result.push cast if typeof! cast isnt 'Undefined'
+    i++
+  if node.length <= i
+    type: 'Just', value: result
+  else
+    type: 'Nothing'
+
+function cast-fields node, type, options
+  return {type: 'Nothing'} unless typeof! node is 'Object'
+  type-of = type.of
+
+  type: 'Just'
+  value:
+    {[(types-cast key, [{type: 'String'}], options), types-cast value, (type-of[key] or [{type: '*'}]), options] for key, value of node}
+
+function type-cast node, type-obj, options
+  {type, structure} = type-obj
+  if type
+    cast-func = options.custom-types[type]?.cast or types[type]
+    throw new Error "Type not defined: #type." unless cast-func
+    cast-func node, options, types-cast
+  else
+    switch structure
+    | 'array'  => cast-array node, type-obj, options
+    | 'tuple'  => cast-tuple node, type-obj, options
+    | 'fields' => cast-fields node, type-obj, options
+
+function types-cast node, types, options
+  for type in types
+    {type: value-type, value} = type-cast node, type, options
+    continue if value-type is 'Nothing'
+    return value if parsed-type-check [type], value, {custom-types: options.custom-types}
+  throw new Error "Value #{ JSON.stringify node} does not type check against #{ JSON.stringify types }."
+
+module.exports = types-cast
diff --git a/src/index.ls b/src/index.ls
new file mode 100644
index 0000000..9a9d226
--- /dev/null
+++ b/src/index.ls
@@ -0,0 +1,15 @@
+parse-string = require './parse-string'
+cast = require './cast'
+{parse-type} = require 'type-check'
+
+VERSION = '0.3.0'
+
+parsed-type-parse = (parsed-type, string, options = {}) ->
+  options.explicit ?= false
+  options.custom-types ?= {}
+  cast (parse-string parsed-type, string, options), parsed-type, options
+
+parse = (type, string, options) ->
+  parsed-type-parse (parse-type type), string, options
+
+module.exports = {VERSION, parse, parsed-type-parse}
diff --git a/src/parse-string.ls b/src/parse-string.ls
new file mode 100644
index 0000000..6b9793e
--- /dev/null
+++ b/src/parse-string.ls
@@ -0,0 +1,96 @@
+{reject} = require 'prelude-ls'
+
+function consume-op tokens, op
+  if tokens.0 is op
+    tokens.shift!
+  else
+    throw new Error "Expected '#op', but got '#{tokens.0}' instead in #{ JSON.stringify tokens }."
+
+function maybe-consume-op tokens, op
+  tokens.shift! if tokens.0 is op
+
+function consume-list tokens, [open, close], has-delimiters
+  consume-op tokens, open if has-delimiters
+  result = []
+  until-test = ",#{ if has-delimiters then close else '' }"
+  while tokens.length and (has-delimiters and tokens.0 isnt close)
+    result.push consume-element tokens, until-test
+    maybe-consume-op tokens, ','
+  consume-op tokens, close if has-delimiters
+  result
+
+function consume-array tokens, has-delimiters
+  consume-list tokens, <[ [ ] ]>, has-delimiters
+
+function consume-tuple tokens, has-delimiters
+  consume-list tokens, <[ ( ) ]>, has-delimiters
+
+function consume-fields tokens, has-delimiters
+  consume-op tokens, '{' if has-delimiters
+  result = {}
+  until-test = ",#{ if has-delimiters then '}' else ''}"
+  while tokens.length and (not has-delimiters or tokens.0 isnt '}')
+    key = consume-value tokens, ':'
+    consume-op tokens, ':'
+    result[key] = consume-element tokens, until-test
+    maybe-consume-op tokens, ','
+  consume-op tokens, '}' if has-delimiters
+  result
+
+function consume-value tokens, until-test = ''
+  out = ''
+  while tokens.length and -1 is until-test.index-of tokens.0
+    out += tokens.shift!
+  out
+
+function consume-element tokens, until-test
+  switch tokens.0
+  | '[' => consume-array tokens, true
+  | '(' => consume-tuple tokens, true
+  | '{' => consume-fields tokens, true
+  |  _  => consume-value tokens, until-test
+
+function consume-top-level tokens, types, options
+  {type, structure} = types.0
+  orig-tokens = tokens.concat!
+  if not options.explicit and types.length is 1 and ((not type and structure) or type in <[ Array Object ]>)
+    result = if structure is 'array' or type is 'Array'
+      consume-array tokens, tokens.0 is '['
+    else if structure is 'tuple'
+      consume-tuple tokens, tokens.0 is '('
+    else # structure is fields or type is 'Object'
+      consume-fields tokens, tokens.0 is '{'
+
+    final-result = if tokens.length
+      consume-element if structure is 'array' or type is 'Array'
+        orig-tokens
+          ..unshift '['
+          ..push ']'
+      else # tuple
+        orig-tokens
+          ..unshift '('
+          ..push ')'
+    else
+      result
+  else
+    final-result = consume-element tokens
+  final-result
+
+special = /\[\]\(\)}{:,/.source
+token-regex = //
+    ("(?:\\"|[^"])*")          # "string"
+  | ('(?:\\'|[^'])*')          # 'string'
+  | (/(?:\\/|[^/])*/[a-zA-Z]*) # /reg-exp/flags
+  | (#.*#)                     # # date #
+  | ([#special])               # special
+  | ([^\s#special](?:\s*[^\s#special]+)*) # everything else
+  | \s*
+//
+
+module.exports = (types, string, options = {}) ->
+  if not options.explicit and types.length is 1 and types.0.type is 'String'
+    return "'#{ string.replace /\\'/g "\\\\'" }'"
+  tokens = reject (not), string.split token-regex
+  node = consume-top-level tokens, types, options
+  throw new Error "Error parsing '#string'." unless node
+  node
diff --git a/test/cast.ls b/test/cast.ls
new file mode 100644
index 0000000..9e53edc
--- /dev/null
+++ b/test/cast.ls
@@ -0,0 +1,297 @@
+levn = require '..'
+{deep-equal: deep-equal, strict-equal: equal, throws} = require 'assert'
+{is-it-NaN} = require 'prelude-ls'
+
+q = (type, input, expected, options) ->
+  result = levn.parse type, input, options
+  equal (typeof! result), (typeof! expected)
+  if is-it-NaN expected
+    is-it-NaN result
+  else
+    switch typeof! expected
+    | 'Array' 'Object' => deep-equal result, expected
+    | 'Date'           => equal result.get-time!, expected.get-time!
+    | 'RegExp'         => equal result.to-string!, expected.to-string!
+    | otherwise        => equal result, expected
+
+suite 'cast' ->
+  test 'Undefined' ->
+    q 'Undefined', 'undefined', void
+    throws (-> q 'Undefined', 'null'), /Value "null" does not type check against/
+
+  test 'Null' ->
+    q 'Null', 'null', null
+    throws (-> q 'Null', 'undefined'), /Value "undefined" does not type check against/
+
+  test 'NaN' ->
+    q 'NaN', 'NaN', NaN
+    throws (-> q 'NaN', '1'), /Value "1" does not type check against/
+
+  test 'Boolean' ->
+    q 'Boolean', 'true', true
+    q 'Boolean', 'false', false
+    throws (-> q 'Boolean', '0'), /Value "0" does not type check against/
+
+  test 'Number' ->
+    q 'Number', '2', 2
+    q 'Number', '-2', -2
+    q 'Number', '2.1', 2.1
+    q 'Number', '-2.1', -2.1
+    throws (-> q 'Number', 'NaN'), /Value "NaN" does not type check against/
+
+  test 'Int' ->
+    q 'Int', '2', 2
+    q 'Int', '-2', -2
+    throws (-> q 'Int', '2.1'), /Value "2.1" does not type check against/
+    throws (-> q 'Int', 'NaN'), /Value "NaN" does not type check against/
+
+  test 'Float' ->
+    q 'Float', '2', 2
+    q 'Float', '-2', -2
+    q 'Float', '2.1', 2.1
+    q 'Float', '-2.1', -2.1
+    throws (-> q 'Float', 'NaN'), /Value "NaN" does not type check against/
+
+  test 'Date' ->
+    q 'Date', '2011-11-11', new Date '2011-11-11'
+    q 'Date', '#2011-11-11#', new Date '2011-11-11'
+    q 'Date', '1320969600000', new Date '2011-11-11'
+    q 'Date', '#1320969600000#', new Date '2011-11-11'
+    throws (-> q 'Date', '#2011-13#'), /Value "#2011-13#" does not type check against/
+
+  test 'RegExp' ->
+    q 'RegExp', 'hi', /hi/
+    q 'RegExp', '/hi/', /hi/
+    q 'RegExp', '/h\\/i/', /h\/i/
+    q 'RegExp', '/hi/ig', /hi/ig
+    q 'RegExp', '/^(hi)|[a-zA-Z]*:there$/g', /^(hi)|[a-zA-Z]*:there$/g
+
+  test 'Array' ->
+    q 'Array', '[1,2,3]', [1,2,3]
+    q 'Array', '1,2,3', [1,2,3]
+    q 'Array', '"hi"', ["hi"]
+    q 'Array', '[]', []
+
+  test 'Object' ->
+    q 'Object', '{x: 2, y: hello}', {x: 2, y: 'hello'}
+    q 'Object', 'x: 2, y: hello', {x: 2, y: 'hello'}
+    q 'Object', '{}', {}
+
+  test 'JSON is valid Object' ->
+    json = '''
+    {
+         "key": "object",
+         "empty": false,
+         "time_nano": 19608,
+         "validate": {"b": true},
+         "sizes": [1, 2, 3]
+    }
+    '''
+    q 'Object', json, {key: 'object', -empty, time_nano: 19608, validate: {+b}, sizes: [1,2,3]}
+
+  test 'String' ->
+    q 'String', '2', '2'
+    q 'String', 'one two three', 'one two three'
+    q 'String', 'blah "hi \\" there:"', 'blah "hi \\" there:"'
+    q 'String', "blah 'hi \\' \\' there:'", "blah 'hi \\' \\' there:'"
+    q 'String', '[2]', '[2]'
+    q 'String', '{2: [], ()}', '{2: [], ()}'
+
+  test 'String using quotes' ->
+    q 'String', "'one[two]three'", '\'one[two]three\''
+    q 'String', '"before"after"', '"before"after"'
+    q 'String', '"hi"', '"hi"'
+    q 'String', '"h\n\ni"', '"h\n\ni"'
+
+  test 'multiple' ->
+    q 'Number | String', '2', 2
+    q 'Number | String', 'str', 'str'
+
+  suite 'array' ->
+    test 'regular' ->
+      q '[Number]', '[1, 2, 3]', [1 2 3]
+
+    test 'children' ->
+      q '[String]', '[1, hi, 3]', ['1' 'hi' '3']
+
+    test 'no delimiters' ->
+      q '[Number]', '1, 2, 3', [1 2 3]
+
+    test 'space after comma with string content' ->
+      q '[String]', 'node, browser', ['node', 'browser']
+
+    test 'trailing comma' ->
+      q '[Number]', '[1, 2, 3,]', [1 2 3]
+
+    test 'empty' ->
+      q '[Number]', '[]', []
+
+    test 'nested' ->
+      q '[[Number]]', '[[1, 2],[3,4],[5,6]]', [[1 2] [3 4] [5 6]]
+      q '[[Number]]', '[1,2],[3,4],[5,6]', [[1 2] [3 4] [5 6]]
+
+    test 'nope' ->
+      throws (-> q '[Number]', '[hi, there]'), /Value "hi" does not type check against/
+
+  suite 'tuple' ->
+    test 'simple' ->
+      q '(Number, String)' '(2, hi)', [2, 'hi']
+      q '(Number, Boolean)' '(2, false)', [2, false]
+      q '(Number, Null)' '(2, null)', [2, null]
+
+    test 'no delimiters' ->
+      q '(Number, String)' '2, hi', [2 'hi']
+
+    test 'trailing comma' ->
+      q '(Number, String)' '(2, hi,)', [2, 'hi']
+
+    test 'nested' ->
+      q '((Boolean, String), Number)' '(true, hi), 2', [[true, 'hi'], 2]
+
+    test 'attempt to cast non-array' ->
+      q '(Number, String) | Number' '(2, hi)', [2, 'hi']
+      q '(Number, String) | Number' '2', 2
+
+    test 'maybe' ->
+      q '(Number, Maybe String)' '(2)', [2]
+      q '(Number, Maybe String)' '2', [2]
+
+      q '(Number, Maybe String)' '(2, undefined)', [2]
+      q '(Number, Maybe String)' '2,undefined', [2]
+
+    test 'nope' ->
+      throws (-> q '(Number, String)' '(hi, 2)'), /Value "hi" does not type check against/
+      throws (-> q '(Number, String)' '(2)'), /does not type check/
+      throws (-> q '(Number, Number)' '(1,2,3)'), /does not type check/
+
+  suite 'fields' ->
+    test 'basic' ->
+      q '{x: Number}', '{x: 2}', {x: 2}
+
+    test 'no delimiters' ->
+      q '{x: Number}', 'x: 2', {x: 2}
+
+    test 'trailing comma' ->
+      q '{x: Number}', '{x: 2,}', {x: 2}
+
+    test 'multiple keys' ->
+      q '{x: Number, y: String}', '{x: 2, y: yo}', {x: 2, y: 'yo'}
+
+    test 'nested' ->
+      q '{obj: {z: String}, x: {y: Boolean}}', 'obj: {z: hi}, x: {y: true}', {obj: {z:'hi'},x:{+y}}
+
+    test 'etc' ->
+      q '{x: Number, ...}', '{x: 2, y: hi}', {x: 2, y: 'hi'}
+
+    test 'maybe' ->
+      q '{x: Number, y: Maybe String}', '{x: 2}', {x: 2}
+
+    test 'with type' ->
+      q 'RegExp{source: String}', '/[a-z]/g', /[a-z]/g
+
+    test 'nope' ->
+      throws (-> q '{x: Number}', '{x: hi}'), /Value "hi" does not type check against/
+      throws (-> q '{x: Number}', '{x: 2, y: hi}'), /does not type check/
+      throws (-> q '{x: Number, y: String}', '{x: 2}'), /does not type check/
+
+  suite 'wildcard' ->
+    test 'undefined' ->
+      q '*', 'undefined', void
+
+    test 'null' ->
+      q '*', 'null', null
+
+    test 'null' ->
+      q '*', 'NaN', NaN
+
+    test 'bool' ->
+      q '*', 'true', true
+      q '*', 'false', false
+
+    test 'number' ->
+      q '*', '0', 0
+      q '*', '1', 1
+      q '*', '-1', -1
+      q '*', '1.1', 1.1
+      q '*', '-1.1', -1.1
+
+    test 'string' ->
+      q '*', 'hi', 'hi'
+      q '*', '2011-11-11', '2011-11-11'
+      q '*', '"\'"', "'"
+      q '*', '"\\""', '"'
+      q '*', '"\\"\\""', '""'
+
+    test 'quoted string' ->
+      q '*', '"hello there"', 'hello there'
+      q '*', '"undefined"', 'undefined'
+      q '*', '"void"', 'void'
+      q '*', '"true"', 'true'
+      q '*', '"false"', 'false'
+      q '*', '"2"', '2'
+
+    test 'date' ->
+      q '*', '#2011-11-11#', new Date '2011-11-11'
+      q '*', '#1320969600000#', new Date '2011-11-11'
+
+    test 'regex' ->
+      q '*', '/hi/', /hi/
+      q '*', '/hi/ig', /hi/ig
+      q '*', '/\\//', /\//
+      q '*', '/^(hi) |[a-zA-Z]*:there$/g', /^(hi) |[a-zA-Z]*:there$/g
+
+    test 'array' ->
+      q '*', '[1,2,3]', [1,2,3]
+      q '*', '[]', []
+
+    test 'tuple' ->
+      q '*', '(1,2)', [1,2]
+
+    test 'object' ->
+      q '*', '{x: 2, y: hello}', {x: 2, y: 'hello'}
+      q '*', '{}', {}
+
+  suite 'nested mixed' ->
+    test 'array of tuples' ->
+      q '[(Number, String)]', '[(1, hi),(3,"hello there"),(5,yo)]',
+          [[1 'hi'], [3 'hello there'] [5 'yo']]
+
+    test 'array of objects' ->
+      q '[{x: Number}]', '[{x: 2}, {x: 3}]', [{x: 2}, {x: 3}]
+
+    test 'wildcard' ->
+      q '*', '[hi,(null,[42]),{k: true}]', ['hi', [null, [42]], {k: true}]
+
+  suite 'options' ->
+    test 'explicit' ->
+      q 'Date | String', '2011-11-11', (new Date '2011-11-11'), {-explicit}
+      q 'Date | String', '2011-11-11', '2011-11-11', {+explicit}
+
+      q 'RegExp', 're', /re/, {-explicit}
+      throws (-> q 'RegExp', 're', null, {+explicit}), /Value "re" does not type check/
+
+    test 'custom-types' ->
+      !function Person name, age
+        @name = name
+        @age = age
+      options =
+        custom-types:
+          Even:
+            type-of: 'Number'
+            validate: -> it % 2 is 0
+            cast: -> {type: 'Just', value: parse-int it}
+          Person:
+            type-of: 'Object'
+            validate: (instanceof Person)
+            cast: (value, options, types-cast) ->
+              return {type: 'Nothing'} unless typeof! value is 'Object'
+              name = types-cast value.name, [type: 'String'], options
+              age = types-cast value.age, [type: 'Number'], options
+              {type: 'Just', value: new Person name, age}
+      q 'Even', '2', 2, options
+      throws (-> q 'Even', '3', null, options), /Value "3" does not type check/
+
+      q 'Person', '{name: Arnold, age: 25}', (new Person 'Arnold', 25), options
+
+      throws (-> q 'FAKE', '3', , options), /Type not defined: FAKE/
+      throws (-> q 'FAKE', '3'), /Type not defined: FAKE/
diff --git a/test/index.ls b/test/index.ls
new file mode 100644
index 0000000..5e7e79c
--- /dev/null
+++ b/test/index.ls
@@ -0,0 +1,6 @@
+levn = require '..'
+{strict-equal: equal} = require 'assert'
+
+suite 'index' ->
+  test 'version' ->
+    equal levn.VERSION, (require '../package.json').version
diff --git a/test/parse-string.ls b/test/parse-string.ls
new file mode 100644
index 0000000..2a5f062
--- /dev/null
+++ b/test/parse-string.ls
@@ -0,0 +1,108 @@
+parse-string = require '../lib/parse-string'
+{deep-equal, strict-equal: equal, throws} = require 'assert'
+{parse-type} = require 'type-check'
+
+q = (input, type, expected, options) ->
+  result = parse-string (parse-type type), input, options
+  switch typeof! expected
+  | 'Array', 'Object' => deep-equal result, expected
+  | otherwise         => equal result, expected
+
+suite 'parse-string' ->
+  test '"string"' ->
+    q 'string', 'String', '\'string\''
+    q '"string"', 'String', '\'"string"\''
+    q 'one\\"two', 'String', '\'one\\"two\''
+    q '"string"', '*', '"string"'
+    q "string", 'String', '\'string\''
+    q "'string'", '*', '\'string\''
+    q 'string with spaces and: {[(})]', 'String', '\'string with spaces and: {[(})]\''
+    q 'string with spaces and: {[(})]', '*', 'string with spaces and:{[(})]'
+    q 'string with spaces and: {[(})]', 'String', 'string with spaces and:{[(})]', {+explicit}
+
+  test '#date#' ->
+    q '#2011-11-11#', 'Date', '#2011-11-11#'
+    q '#2011-11-11#', '*', '#2011-11-11#'
+    q '#[] () :: { }#', 'Date', '#[] () :: { }#'
+    q '#[] () :: { }#', '*', '#[] () :: { }#'
+
+  test '/regexp/flags' ->
+    q '/regexp/ig', 'RegExp', '/regexp/ig'
+    q '/reg\\/exp/ig', 'RegExp', '/reg\\/exp/ig'
+    q '/regexp/ig', '*', '/regexp/ig'
+    q '/[ ] {:}/ig', 'RegExp', '/[ ] {:}/ig'
+    q '/[ ] {:}/ig', '*', '/[ ] {:}/ig'
+
+  test '[array]' ->
+    q '[1,2,3]', 'Array', ['1','2','3']
+    q '[1,2,3]', '[Number]', ['1','2','3']
+    q '[1,2,3]', '*', ['1','2','3']
+    q '[one two , three four]', '[String]', ['one two','three four']
+    q '[one:two, three:four]', '[String]', ['one:two','three:four']
+
+    q '[1,2,3,]', '*', ['1','2','3']
+    q '[1, 2, 3, ]', '*', ['1','2','3']
+
+
+    q '[]', 'Array', []
+    q '[]', '[Number]', []
+    q '[]', '*', []
+
+    q '1,2,3', '[Number]', ['1','2','3']
+    q '1,2,3', 'Array', ['1','2','3']
+
+    q '1, 2, 3', '[Number]', ['1','2','3']
+    q '1, 2, 3', 'Array', ['1','2','3']
+
+    q '', '[Number]', []
+    q '', 'Array', []
+
+    q '[1,2],[3,4]', 'Array', [['1','2'],['3','4']]
+    q '[1,2],[3,4]', '[[Number]]', [['1','2'],['3','4']]
+
+  test '(tuple)' ->
+    q '(1,2)', '(Number, Number)', ['1', '2']
+    q '(1,2)', '*', ['1', '2']
+
+    q '(1, 2)', '(Number, Number)', ['1', '2']
+    q '(1, 2)', '*', ['1', '2']
+
+    q '(one two , 2)', '(String, Number)', ['one two', '2']
+
+    q '1,2', '(Number, Number)', ['1', '2']
+    q '1, 2', '(Number, Number)', ['1', '2']
+
+    q '(1,2),(3,4)', '((Number,Number),(Number,Number))', [['1','2'],['3','4']]
+
+  test '{object}' ->
+    q '{x: 2, y: 3}', 'Object', {x: '2', y: '3'}
+    q '{x: 2, y: 3}', '{...}', {x: '2', y: '3'}
+    q '{x: 2, y: 3}', 'RegExp{...}', {x: '2', y: '3'}
+    q '{x: 2, y: 3}', '{x: Number, y: Number}', {x: '2', y: '3'}
+    q '{x: 2, y: 3}', '{x: Number, y: Number}', {x: '2', y: '3'}
+    q '{[x]: 2, y(): 3}', '*', {'[x]': '2', 'y()': '3'}
+    q '{x: 2():, y: 3][}', '*', {x: '2():', y: '3]['}
+
+    q '', 'Object', {}
+    q '', '{...}', {}
+    q '', '{x: Number, y: Number}', {}
+
+    throws (-> q '{x}', '*'), /Expected ':', but got 'undefined' instead/
+
+    q 'x: 2, y: 3', 'Object', {x: '2', y: '3'}
+    q 'x: 2, y: 3', '{x: Number, y: Number}', {x: '2', y: '3'}
+
+  test 'etc' ->
+    q 'hi', '*', 'hi'
+    q 'this is a string', '*', 'this is a string'
+    q '&$-1234asdfasw#!.+=%', '*', '&$-1234asdfasw#!.+=%'
+    q 'x: 2, y: 3', '*', 'x:2,y:3'
+    q '1,2', '*', '1,2'
+    q '1,2,3', '*', '1,2,3'
+
+  test 'explicit' ->
+    q '1,2,3', '*', '1,2,3', {+explicit}
+    q '1,2,3', 'Array', '1,2,3', {+explicit}
+
+  test 'nothing' ->
+    throws (-> q '', '*'), /Error parsing ''/

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



More information about the Pkg-javascript-commits mailing list