[Pkg-javascript-commits] [node-yamljs] 01/04: New upstream version 0.3.0

Michael Lustfield mtecknology at debian.org
Mon Dec 11 04:21:01 UTC 2017


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

mtecknology pushed a commit to branch master
in repository node-yamljs.

commit b66478189da92aa2fd48b49429d290b1ac526389
Author: Michael Lustfield <michael at lustfield.net>
Date:   Sat Nov 18 15:13:52 2017 -0600

    New upstream version 0.3.0
---
 .gitignore                          |   5 +
 .travis.yml                         |   7 +
 Cakefile                            | 117 +++++++
 LICENSE                             |  19 ++
 README.md                           | 154 +++++++++
 bin/json2yaml                       | 186 +++++++++++
 bin/yaml2json                       | 200 +++++++++++
 bower.json                          |  19 ++
 cli/json2yaml.js                    | 185 ++++++++++
 cli/yaml2json.js                    | 199 +++++++++++
 demo/demo.html                      | 114 +++++++
 index.js                            |   3 +
 lib/Dumper.js                       |  53 +++
 lib/Escaper.js                      |  56 ++++
 lib/Exception/DumpException.js      |  27 ++
 lib/Exception/ParseException.js     |  27 ++
 lib/Exception/ParseMore.js          |  27 ++
 lib/Inline.js                       | 485 +++++++++++++++++++++++++++
 lib/Parser.js                       | 603 +++++++++++++++++++++++++++++++++
 lib/Pattern.js                      | 119 +++++++
 lib/Unescaper.js                    |  83 +++++
 lib/Utils.js                        | 297 ++++++++++++++++
 lib/Yaml.js                         |  93 ++++++
 package.json                        |  34 ++
 src/Dumper.coffee                   |  56 ++++
 src/Escaper.coffee                  |  80 +++++
 src/Exception/DumpException.coffee  |  12 +
 src/Exception/ParseException.coffee |  12 +
 src/Exception/ParseMore.coffee      |  12 +
 src/Inline.coffee                   | 488 +++++++++++++++++++++++++++
 src/Parser.coffee                   | 651 ++++++++++++++++++++++++++++++++++++
 src/Pattern.coffee                  | 144 ++++++++
 src/Unescaper.coffee                |  96 ++++++
 src/Utils.coffee                    | 349 +++++++++++++++++++
 src/Yaml.coffee                     | 104 ++++++
 35 files changed, 5116 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8da5ccb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+node_modules
+/doc/
+
+/lib/debug/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..3d496c3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+  - "5.0"
+before_script:
+  - npm install -g coffee-script
+  - npm install -g browserify
+  - npm install -g uglify-js
diff --git a/Cakefile b/Cakefile
new file mode 100644
index 0000000..edc0878
--- /dev/null
+++ b/Cakefile
@@ -0,0 +1,117 @@
+
+{exec, spawn}   = require 'child_process'
+fs              = require 'fs'
+path            = require 'path'
+esc             = (arg) -> (''+arg).replace(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/gm, '\\').replace(/\n/g, "'\n'").replace(/^$/, "''")
+
+srcDir = path.normalize __dirname+'/src'
+libDir = path.normalize __dirname+'/lib'
+libDebugDir = path.normalize __dirname+'/lib/debug'
+distDir = path.normalize __dirname+'/dist'
+cliDir = path.normalize __dirname+'/cli'
+binDir = path.normalize __dirname+'/bin'
+specDir = path.normalize __dirname+'/test/spec'
+modulesDir = path.normalize __dirname+'/node_modules'
+
+task 'build', 'build project', ->
+
+    # Compile
+    do compile = ->
+        unless fs.existsSync libDir
+            fs.mkdirSync libDir
+        unless fs.existsSync libDir+'/Exception'
+            fs.mkdirSync libDir+'/Exception'
+        toCompile = 'Yaml Utils Unescaper Pattern Parser Inline Escaper Dumper Exception/ParseException Exception/ParseMore Exception/DumpException'.split ' '
+        do compileOne = ->
+            name = toCompile.shift()
+            outputDir = (if '/' in name then libDir+'/Exception' else libDir)
+            exec 'coffee -b -o '+esc(outputDir)+' -c '+esc(srcDir+'/'+name+'.coffee'), (err, res) ->
+                if err then throw err
+
+                console.log "Compiled #{name}.js"
+                if toCompile.length
+                    compileOne()
+                else
+                    debugCompile()
+
+    # Debug compile
+    debugCompile = ->
+        unless fs.existsSync libDebugDir
+            fs.mkdirSync libDebugDir
+        unless fs.existsSync libDebugDir+'/Exception'
+            fs.mkdirSync libDebugDir+'/Exception'
+        toCompile = 'Yaml Utils Unescaper Pattern Parser Inline Escaper Dumper Exception/ParseException Exception/ParseMore Exception/DumpException'.split ' '
+        do compileOne = ->
+            name = toCompile.shift()
+            outputDir = (if '/' in name then libDebugDir+'/Exception' else libDebugDir)
+            exec 'coffee -m -b -o '+esc(outputDir)+' -c '+esc(srcDir+'/'+name+'.coffee'), (err, res) ->
+                if err then throw err
+
+                console.log "Compiled #{name}.js (debug)"
+                if toCompile.length
+                    compileOne()
+                else
+                    browserify()
+
+    # Browserify
+    unless fs.existsSync distDir
+        fs.mkdirSync distDir
+    browserify = ->
+        exec 'browserify -t coffeeify --extension=".coffee" '+esc(srcDir+'/Yaml.coffee')+' > '+esc(distDir+'/yaml.js'), (err, res) ->
+            if err then throw err
+
+            console.log "Browserified yaml.js"
+            exec 'browserify --debug -t coffeeify --extension=".coffee" '+esc(srcDir+'/Yaml.coffee')+' > '+esc(distDir+'/yaml.debug.js'), (err, res) ->
+                if err then throw err
+
+                console.log "Browserified yaml.js (debug)"
+                minify()
+
+    # Minify
+    minify = ->
+        exec 'uglifyjs --mangle sort '+esc(distDir+'/yaml.js')+' > '+esc(distDir+'/yaml.min.js'), (err, res) ->
+            if err then throw err
+
+            console.log "Minified yaml.min.js"
+            compileSpec()
+
+    # Compile spec
+    compileSpec = ->
+        exec 'coffee -b -c '+esc(specDir+'/YamlSpec.coffee'), (err, res) ->
+            if err then throw err
+
+            console.log "Compiled YamlSpec.js"
+            compileCLI()
+
+    # Compile CLI
+    compileCLI = ->
+        unless fs.existsSync binDir
+            fs.mkdirSync binDir
+
+        # yaml2json
+        str = fs.readFileSync cliDir+'/yaml2json.js'
+        str = "#!/usr/bin/env node\n" + str
+        fs.writeFileSync binDir+'/yaml2json', str
+        fs.chmodSync binDir+'/yaml2json', '755'
+        console.log "Bundled yaml2json"
+
+        # json2yaml
+        str = fs.readFileSync cliDir+'/json2yaml.js'
+        str = "#!/usr/bin/env node\n" + str
+        fs.writeFileSync binDir+'/json2yaml', str
+        fs.chmodSync binDir+'/json2yaml', '755'
+        console.log "Bundled json2yaml"
+
+
+task 'test', 'test project', ->
+
+    # Test
+    spawn 'node', [modulesDir+'/jasmine-node/lib/jasmine-node/cli.js', '--verbose', '--coffee', specDir+'/YamlSpec.coffee'], stdio: "inherit"
+
+
+task 'doc', 'generate documentation', ->
+
+    # Generate
+    spawn 'codo', [srcDir], stdio: "inherit"
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8adaf06
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Jeremy Faivre
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8159b6b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,154 @@
+yaml.js
+=======
+
+![Build status](https://travis-ci.org/jeremyfa/yaml.js.svg?branch=develop)
+
+Standalone JavaScript YAML 1.2 Parser & Encoder. Works under node.js and all major browsers. Also brings command line YAML/JSON conversion tools.
+
+Mainly inspired from [Symfony Yaml Component](https://github.com/symfony/Yaml).
+
+How to use
+----------
+
+Import yaml.js in your html page:
+
+``` html
+<script type="text/javascript" src="yaml.js"></script>
+```
+
+Parse yaml string:
+
+``` js
+nativeObject = YAML.parse(yamlString);
+```
+
+Dump native object into yaml string:
+
+``` js
+yamlString = YAML.stringify(nativeObject[, inline /* @integer depth to start using inline notation at */[, spaces /* @integer number of spaces to use for indentation */] ]);
+```
+
+Load yaml file:
+
+``` js
+nativeObject = YAML.load('file.yml');
+```
+
+Load yaml file:
+
+``` js
+YAML.load('file.yml', function(result)
+{
+    nativeObject = result;
+});
+```
+
+Use with node.js
+----------------
+
+Install module:
+
+``` bash
+npm install yamljs
+```
+
+Use it:
+
+``` js
+YAML = require('yamljs');
+
+// parse YAML string
+nativeObject = YAML.parse(yamlString);
+
+// Generate YAML
+yamlString = YAML.stringify(nativeObject, 4);
+
+// Load yaml file using YAML.load
+nativeObject = YAML.load('myfile.yml');
+```
+
+Command line tools
+------------------
+
+You can enable the command line tools by installing yamljs as a global module:
+
+``` bash
+npm install -g yamljs
+```
+
+Then, two cli commands should become available: **yaml2json** and **json2yaml**. They let you convert YAML to JSON and JSON to YAML very easily.
+
+**yaml2json**
+
+```
+usage: yaml2json [-h] [-v] [-p] [-i INDENTATION] [-s] [-r] [-w] input
+
+Positional arguments:
+  input                 YAML file or directory containing YAML files.
+
+Optional arguments:
+  -h, --help            Show this help message and exit.
+  -v, --version         Show program's version number and exit.
+  -p, --pretty          Output pretty (indented) JSON.
+  -i INDENTATION, --indentation INDENTATION
+                        Number of space characters used to indent code (use 
+                        with --pretty, default: 2).
+  -s, --save            Save output inside JSON file(s) with the same name.
+  -r, --recursive       If the input is a directory, also find YAML files in 
+                        sub-directories recursively.
+  -w, --watch           Watch for changes.
+```
+
+**json2yaml**
+
+```
+usage: json2yaml [-h] [-v] [-d DEPTH] [-i INDENTATION] [-s] [-r] [-w] input
+
+Positional arguments:
+  input                 JSON file or directory containing JSON files.
+
+Optional arguments:
+  -h, --help            Show this help message and exit.
+  -v, --version         Show program's version number and exit.
+  -d DEPTH, --depth DEPTH
+                        Set minimum level of depth before generating inline 
+                        YAML (default: 2).
+  -i INDENTATION, --indentation INDENTATION
+                        Number of space characters used to indent code 
+                        (default: 2).
+  -s, --save            Save output inside YML file(s) with the same name.
+  -r, --recursive       If the input is a directory, also find JSON files in 
+                        sub-directories recursively.
+  -w, --watch           Watch for changes.
+```
+
+**examples**
+
+``` bash
+# Convert YAML to JSON and output resulting JSON on the console
+yaml2json myfile.yml
+
+# Store output inside a JSON file
+yaml2json myfile.yml > output.json
+
+# Output "pretty" (indented) JSON
+yaml2json myfile.yml --pretty
+
+# Save the output inside a file called myfile.json
+yaml2json myfile.yml --pretty --save
+
+# Watch a full directory and convert any YAML file into its JSON equivalent
+yaml2json mydirectory --pretty --save --recursive
+
+# Convert JSON to YAML and store output inside a JSON file
+json2yaml myfile.json > output.yml
+
+# Output YAML that will be inlined only after 8 levels of indentation
+json2yaml myfile.json --depth 8
+
+# Save the output inside a file called myfile.json with 4 spaces for each indentation
+json2yaml myfile.json --indentation 4
+
+# Watch a full directory and convert any JSON file into its YAML equivalent
+json2yaml mydirectory --pretty --save --recursive
+
diff --git a/bin/json2yaml b/bin/json2yaml
new file mode 100755
index 0000000..f0a8791
--- /dev/null
+++ b/bin/json2yaml
@@ -0,0 +1,186 @@
+#!/usr/bin/env node
+
+/**
+ * yaml2json cli program
+ */
+ 
+var YAML = require('../lib/Yaml.js');
+
+var ArgumentParser = require('argparse').ArgumentParser;
+var cli = new ArgumentParser({
+    prog:           "json2yaml", 
+    version:        require('../package.json').version,
+    addHelp:        true
+});
+
+cli.addArgument(
+    ['-d', '--depth'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Set minimum level of depth before generating inline YAML (default: 2).'
+    }
+);
+
+cli.addArgument(
+    ['-i', '--indentation'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Number of space characters used to indent code (default: 2).',
+    }
+);
+
+cli.addArgument(
+    ['-s', '--save'],
+    {
+        help:   'Save output inside YML file(s) with the same name.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-r', '--recursive'],
+    {
+        help:   'If the input is a directory, also find JSON files in sub-directories recursively.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-w', '--watch'],
+    {
+        help:   'Watch for changes.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(['input'], {
+    help:   'JSON file or directory containing JSON files or - to read JSON from stdin.'
+});
+
+try {
+    var options = cli.parseArgs();
+    var path = require('path');
+    var fs   = require('fs');
+    var glob = require('glob');
+    
+    var rootPath = process.cwd();
+    var parsePath = function(input) {
+        if (input == '-') return '-';
+        var output;
+        if (!(input != null)) {
+            return rootPath;
+        }
+        output = path.normalize(input);
+        if (output.length === 0) {
+            return rootPath;
+        }
+        if (output.charAt(0) !== '/') {
+            output = path.normalize(rootPath + '/./' + output);
+        }
+        if (output.length > 1 && output.charAt(output.length - 1) === '/') {
+            return output.substr(0, output.length - 1);
+        }
+        return output;
+    };
+
+    // Find files
+    var findFiles = function(input) {
+        if (input != '-' && input != null) {
+            var isDirectory = fs.statSync(input).isDirectory();
+            var files = [];
+
+            if (!isDirectory) {
+                files.push(input);
+            }
+            else {
+                if (options.recursive) {
+                    files = files.concat(glob.sync(input+'/**/*.json'));
+                }
+                else {
+                    files = files.concat(glob.sync(input+'/*.json'));
+                }
+            }
+        
+            return files;
+        }
+        return null;
+    };
+
+    // Convert to JSON
+    var convertToYAML = function(input, inline, save, spaces, str) {
+        var yaml;
+        if (inline == null) inline = 2;
+        if (spaces == null) spaces = 2;
+        
+        if (str == null) {
+            str = ''+fs.readFileSync(input);
+        }
+        yaml = YAML.dump(JSON.parse(str), inline, spaces);
+    
+        if (!save || input == null) {
+            // Ouput result
+            process.stdout.write(yaml);
+        }
+        else {
+            var output;
+            if (input.substring(input.length-5) == '.json') {
+                output = input.substr(0, input.length-5) + '.yaml';
+            }
+            else {
+                output = input + '.yaml';
+            }
+        
+            // Write file
+            var file = fs.openSync(output, 'w+');
+            fs.writeSync(file, yaml);
+            fs.closeSync(file);
+            process.stdout.write("saved "+output+"\n");
+        }
+    };
+
+    var input = parsePath(options.input);
+    var mtimes = [];
+
+    var runCommand = function() {
+        try {
+            var files = findFiles(input);
+            if (files != null) {
+                var len = files.length;
+                for (var i = 0; i < len; i++) {
+                    var file = files[i];
+                    var stat = fs.statSync(file);
+                    var time = stat.mtime.getTime();
+                    if (!stat.isDirectory()) {
+                        if (!mtimes[file] || mtimes[file] < time) {
+                            mtimes[file] = time;
+                            convertToYAML(file, options.depth, options.save, options.indentation);
+                        }
+                    }
+                }
+            } else {
+                // Read from STDIN
+                var stdin = process.openStdin();
+                var data = "";
+                stdin.on('data', function(chunk) {
+                    data += chunk;
+                });
+                stdin.on('end', function() {
+                    convertToYAML(null, options.depth, options.save, options.indentation, data);
+                });
+            }
+        } catch (e) {
+            process.stderr.write((e.message ? e.message : e)+"\n");
+        }
+    };
+
+    if (!options.watch) {
+        runCommand();
+    } else {
+        runCommand();
+        setInterval(runCommand, 1000);
+    } 
+} catch (e) {
+    process.stderr.write((e.message ? e.message : e)+"\n");
+}
diff --git a/bin/yaml2json b/bin/yaml2json
new file mode 100755
index 0000000..550230c
--- /dev/null
+++ b/bin/yaml2json
@@ -0,0 +1,200 @@
+#!/usr/bin/env node
+
+/**
+ * yaml2json cli program
+ */
+ 
+var YAML = require('../lib/Yaml.js');
+
+var ArgumentParser = require('argparse').ArgumentParser;
+var cli = new ArgumentParser({
+    prog:           "yaml2json", 
+    version:        require('../package.json').version,
+    addHelp:        true
+});
+
+cli.addArgument(
+    ['-p', '--pretty'],
+    {
+        help:   'Output pretty (indented) JSON.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-i', '--indentation'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Number of space characters used to indent code (use with --pretty, default: 2).',
+    }
+);
+
+cli.addArgument(
+    ['-s', '--save'],
+    {
+        help:   'Save output inside JSON file(s) with the same name.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-r', '--recursive'],
+    {
+        help:   'If the input is a directory, also find YAML files in sub-directories recursively.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-w', '--watch'],
+    {
+        help:   'Watch for changes.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(['input'], {
+    help:   'YAML file or directory containing YAML files or - to read YAML from stdin.'
+});
+
+try {
+    var options = cli.parseArgs();
+    var path = require('path');
+    var fs   = require('fs');
+    var glob = require('glob');
+    
+    var rootPath = process.cwd();
+    var parsePath = function(input) {
+        if (input == '-') return '-';
+        var output;
+        if (!(input != null)) {
+            return rootPath;
+        }
+        output = path.normalize(input);
+        if (output.length === 0) {
+            return rootPath;
+        }
+        if (output.charAt(0) !== '/') {
+            output = path.normalize(rootPath + '/./' + output);
+        }
+        if (output.length > 1 && output.charAt(output.length - 1) === '/') {
+            return output.substr(0, output.length - 1);
+        }
+        return output;
+    };
+
+    // Find files
+    var findFiles = function(input) {
+        if (input != '-' && input != null) {
+            var isDirectory = fs.statSync(input).isDirectory();
+            var files = [];
+
+            if (!isDirectory) {
+                files.push(input);
+            }
+            else {
+                if (options.recursive) {
+                    files = files.concat(glob.sync(input+'/**/*.yml'));
+                    files = files.concat(glob.sync(input+'/**/*.yaml'));
+                }
+                else {
+                    files = files.concat(glob.sync(input+'/*.yml'));
+                    files = files.concat(glob.sync(input+'/*.yaml'));
+                }
+            }
+        
+            return files;
+        }
+        return null;
+    };
+
+    // Convert to JSON
+    var convertToJSON = function(input, pretty, save, spaces, str) {
+        var json;
+        if (spaces == null) spaces = 2;
+        if (str != null) {
+            if (pretty) {
+                json = JSON.stringify(YAML.parse(str), null, spaces);
+            }
+            else {
+                json = JSON.stringify(YAML.parse(str));
+            }
+        } else {
+            if (pretty) {
+                json = JSON.stringify(YAML.parseFile(input), null, spaces);
+            }
+            else {
+                json = JSON.stringify(YAML.parseFile(input));
+            }
+        }
+    
+        if (!save || input == null) {
+            // Ouput result
+            process.stdout.write(json+"\n");
+        }
+        else {
+            var output;
+            if (input.substring(input.length-4) == '.yml') {
+                output = input.substr(0, input.length-4) + '.json';
+            }
+            else if (input.substring(input.length-5) == '.yaml') {
+                output = input.substr(0, input.length-5) + '.json';
+            }
+            else {
+                output = input + '.json';
+            }
+        
+            // Write file
+            var file = fs.openSync(output, 'w+');
+            fs.writeSync(file, json);
+            fs.closeSync(file);
+            process.stdout.write("saved "+output+"\n");
+        }
+    };
+
+    var input = parsePath(options.input);
+    var mtimes = [];
+
+    var runCommand = function() {
+        try {
+            var files = findFiles(input);
+            if (files != null) {
+                var len = files.length;
+
+                for (var i = 0; i < len; i++) {
+                    var file = files[i];
+                    var stat = fs.statSync(file);
+                    var time = stat.mtime.getTime();
+                    if (!stat.isDirectory()) {
+                        if (!mtimes[file] || mtimes[file] < time) {
+                            mtimes[file] = time;
+                            convertToJSON(file, options.pretty, options.save, options.indentation);
+                        }
+                    }
+                }
+            } else {
+                // Read from STDIN
+                var stdin = process.openStdin();
+                var data = "";
+                stdin.on('data', function(chunk) {
+                    data += chunk;
+                });
+                stdin.on('end', function() {
+                    convertToJSON(null, options.pretty, options.save, options.indentation, data);
+                });
+            }
+        } catch (e) {
+            process.stderr.write((e.message ? e.message : e)+"\n");
+        }
+    };
+
+    if (!options.watch) {
+        runCommand();
+    } else {
+        runCommand();
+        setInterval(runCommand, 1000);
+    } 
+} catch (e) {
+    process.stderr.write((e.message ? e.message : e)+"\n");
+}
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..2895ae8
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,19 @@
+{
+  "name": "yaml.js",
+  "main": "dist/yaml.js",
+  "version": "0.3.0",
+  "homepage": "https://github.com/jeremyfa/yaml.js",
+  "authors": [
+    "Jeremy Faivre <contact at jeremyfa.com>"
+  ],
+  "description": "Standalone JavaScript YAML 1.2 Parser & Encoder. Works under node.js and all major browsers. Also brings command line YAML/JSON conversion tools.",
+  "keywords": [
+    "yaml"
+  ],
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components"
+  ]
+}
diff --git a/cli/json2yaml.js b/cli/json2yaml.js
new file mode 100644
index 0000000..4c849b2
--- /dev/null
+++ b/cli/json2yaml.js
@@ -0,0 +1,185 @@
+
+/**
+ * yaml2json cli program
+ */
+ 
+var YAML = require('../lib/Yaml.js');
+
+var ArgumentParser = require('argparse').ArgumentParser;
+var cli = new ArgumentParser({
+    prog:           "json2yaml", 
+    version:        require('../package.json').version,
+    addHelp:        true
+});
+
+cli.addArgument(
+    ['-d', '--depth'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Set minimum level of depth before generating inline YAML (default: 2).'
+    }
+);
+
+cli.addArgument(
+    ['-i', '--indentation'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Number of space characters used to indent code (default: 2).',
+    }
+);
+
+cli.addArgument(
+    ['-s', '--save'],
+    {
+        help:   'Save output inside YML file(s) with the same name.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-r', '--recursive'],
+    {
+        help:   'If the input is a directory, also find JSON files in sub-directories recursively.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-w', '--watch'],
+    {
+        help:   'Watch for changes.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(['input'], {
+    help:   'JSON file or directory containing JSON files or - to read JSON from stdin.'
+});
+
+try {
+    var options = cli.parseArgs();
+    var path = require('path');
+    var fs   = require('fs');
+    var glob = require('glob');
+    
+    var rootPath = process.cwd();
+    var parsePath = function(input) {
+        if (input == '-') return '-';
+        var output;
+        if (!(input != null)) {
+            return rootPath;
+        }
+        output = path.normalize(input);
+        if (output.length === 0) {
+            return rootPath;
+        }
+        if (output.charAt(0) !== '/') {
+            output = path.normalize(rootPath + '/./' + output);
+        }
+        if (output.length > 1 && output.charAt(output.length - 1) === '/') {
+            return output.substr(0, output.length - 1);
+        }
+        return output;
+    };
+
+    // Find files
+    var findFiles = function(input) {
+        if (input != '-' && input != null) {
+            var isDirectory = fs.statSync(input).isDirectory();
+            var files = [];
+
+            if (!isDirectory) {
+                files.push(input);
+            }
+            else {
+                if (options.recursive) {
+                    files = files.concat(glob.sync(input+'/**/*.json'));
+                }
+                else {
+                    files = files.concat(glob.sync(input+'/*.json'));
+                }
+            }
+        
+            return files;
+        }
+        return null;
+    };
+
+    // Convert to JSON
+    var convertToYAML = function(input, inline, save, spaces, str) {
+        var yaml;
+        if (inline == null) inline = 2;
+        if (spaces == null) spaces = 2;
+        
+        if (str == null) {
+            str = ''+fs.readFileSync(input);
+        }
+        yaml = YAML.dump(JSON.parse(str), inline, spaces);
+    
+        if (!save || input == null) {
+            // Ouput result
+            process.stdout.write(yaml);
+        }
+        else {
+            var output;
+            if (input.substring(input.length-5) == '.json') {
+                output = input.substr(0, input.length-5) + '.yaml';
+            }
+            else {
+                output = input + '.yaml';
+            }
+        
+            // Write file
+            var file = fs.openSync(output, 'w+');
+            fs.writeSync(file, yaml);
+            fs.closeSync(file);
+            process.stdout.write("saved "+output+"\n");
+        }
+    };
+
+    var input = parsePath(options.input);
+    var mtimes = [];
+
+    var runCommand = function() {
+        try {
+            var files = findFiles(input);
+            if (files != null) {
+                var len = files.length;
+                for (var i = 0; i < len; i++) {
+                    var file = files[i];
+                    var stat = fs.statSync(file);
+                    var time = stat.mtime.getTime();
+                    if (!stat.isDirectory()) {
+                        if (!mtimes[file] || mtimes[file] < time) {
+                            mtimes[file] = time;
+                            convertToYAML(file, options.depth, options.save, options.indentation);
+                        }
+                    }
+                }
+            } else {
+                // Read from STDIN
+                var stdin = process.openStdin();
+                var data = "";
+                stdin.on('data', function(chunk) {
+                    data += chunk;
+                });
+                stdin.on('end', function() {
+                    convertToYAML(null, options.depth, options.save, options.indentation, data);
+                });
+            }
+        } catch (e) {
+            process.stderr.write((e.message ? e.message : e)+"\n");
+        }
+    };
+
+    if (!options.watch) {
+        runCommand();
+    } else {
+        runCommand();
+        setInterval(runCommand, 1000);
+    } 
+} catch (e) {
+    process.stderr.write((e.message ? e.message : e)+"\n");
+}
diff --git a/cli/yaml2json.js b/cli/yaml2json.js
new file mode 100644
index 0000000..662201c
--- /dev/null
+++ b/cli/yaml2json.js
@@ -0,0 +1,199 @@
+
+/**
+ * yaml2json cli program
+ */
+ 
+var YAML = require('../lib/Yaml.js');
+
+var ArgumentParser = require('argparse').ArgumentParser;
+var cli = new ArgumentParser({
+    prog:           "yaml2json", 
+    version:        require('../package.json').version,
+    addHelp:        true
+});
+
+cli.addArgument(
+    ['-p', '--pretty'],
+    {
+        help:   'Output pretty (indented) JSON.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-i', '--indentation'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Number of space characters used to indent code (use with --pretty, default: 2).',
+    }
+);
+
+cli.addArgument(
+    ['-s', '--save'],
+    {
+        help:   'Save output inside JSON file(s) with the same name.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-r', '--recursive'],
+    {
+        help:   'If the input is a directory, also find YAML files in sub-directories recursively.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-w', '--watch'],
+    {
+        help:   'Watch for changes.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(['input'], {
+    help:   'YAML file or directory containing YAML files or - to read YAML from stdin.'
+});
+
+try {
+    var options = cli.parseArgs();
+    var path = require('path');
+    var fs   = require('fs');
+    var glob = require('glob');
+    
+    var rootPath = process.cwd();
+    var parsePath = function(input) {
+        if (input == '-') return '-';
+        var output;
+        if (!(input != null)) {
+            return rootPath;
+        }
+        output = path.normalize(input);
+        if (output.length === 0) {
+            return rootPath;
+        }
+        if (output.charAt(0) !== '/') {
+            output = path.normalize(rootPath + '/./' + output);
+        }
+        if (output.length > 1 && output.charAt(output.length - 1) === '/') {
+            return output.substr(0, output.length - 1);
+        }
+        return output;
+    };
+
+    // Find files
+    var findFiles = function(input) {
+        if (input != '-' && input != null) {
+            var isDirectory = fs.statSync(input).isDirectory();
+            var files = [];
+
+            if (!isDirectory) {
+                files.push(input);
+            }
+            else {
+                if (options.recursive) {
+                    files = files.concat(glob.sync(input+'/**/*.yml'));
+                    files = files.concat(glob.sync(input+'/**/*.yaml'));
+                }
+                else {
+                    files = files.concat(glob.sync(input+'/*.yml'));
+                    files = files.concat(glob.sync(input+'/*.yaml'));
+                }
+            }
+        
+            return files;
+        }
+        return null;
+    };
+
+    // Convert to JSON
+    var convertToJSON = function(input, pretty, save, spaces, str) {
+        var json;
+        if (spaces == null) spaces = 2;
+        if (str != null) {
+            if (pretty) {
+                json = JSON.stringify(YAML.parse(str), null, spaces);
+            }
+            else {
+                json = JSON.stringify(YAML.parse(str));
+            }
+        } else {
+            if (pretty) {
+                json = JSON.stringify(YAML.parseFile(input), null, spaces);
+            }
+            else {
+                json = JSON.stringify(YAML.parseFile(input));
+            }
+        }
+    
+        if (!save || input == null) {
+            // Ouput result
+            process.stdout.write(json+"\n");
+        }
+        else {
+            var output;
+            if (input.substring(input.length-4) == '.yml') {
+                output = input.substr(0, input.length-4) + '.json';
+            }
+            else if (input.substring(input.length-5) == '.yaml') {
+                output = input.substr(0, input.length-5) + '.json';
+            }
+            else {
+                output = input + '.json';
+            }
+        
+            // Write file
+            var file = fs.openSync(output, 'w+');
+            fs.writeSync(file, json);
+            fs.closeSync(file);
+            process.stdout.write("saved "+output+"\n");
+        }
+    };
+
+    var input = parsePath(options.input);
+    var mtimes = [];
+
+    var runCommand = function() {
+        try {
+            var files = findFiles(input);
+            if (files != null) {
+                var len = files.length;
+
+                for (var i = 0; i < len; i++) {
+                    var file = files[i];
+                    var stat = fs.statSync(file);
+                    var time = stat.mtime.getTime();
+                    if (!stat.isDirectory()) {
+                        if (!mtimes[file] || mtimes[file] < time) {
+                            mtimes[file] = time;
+                            convertToJSON(file, options.pretty, options.save, options.indentation);
+                        }
+                    }
+                }
+            } else {
+                // Read from STDIN
+                var stdin = process.openStdin();
+                var data = "";
+                stdin.on('data', function(chunk) {
+                    data += chunk;
+                });
+                stdin.on('end', function() {
+                    convertToJSON(null, options.pretty, options.save, options.indentation, data);
+                });
+            }
+        } catch (e) {
+            process.stderr.write((e.message ? e.message : e)+"\n");
+        }
+    };
+
+    if (!options.watch) {
+        runCommand();
+    } else {
+        runCommand();
+        setInterval(runCommand, 1000);
+    } 
+} catch (e) {
+    process.stderr.write((e.message ? e.message : e)+"\n");
+}
diff --git a/demo/demo.html b/demo/demo.html
new file mode 100644
index 0000000..101ed53
--- /dev/null
+++ b/demo/demo.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+	<style type="text/css">
+	/*
+	Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+	Code licensed under the BSD License:
+	http://developer.yahoo.com/yui/license.html
+	version: 3.2.0
+	build: 2676
+	*/
+	html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal; [...]
+	
+	/*
+	 * Custom styles
+	 */
+	body {
+		width: 100%;
+		overflow: hidden;
+	}
+	#parse {
+		border: none;
+		background-color: white;
+		color: black;
+		z-index: 3;
+		position: absolute;
+		right: 50%;
+		top: 0;
+		width: 100px;
+	}
+	#yaml {
+		color: white;
+		background-color: black;
+		font-family: "Courier New";
+		font-size: 14px;
+		width: 50%;
+		border: none;
+		position: absolute;
+		top: 0;
+		left: 0;
+		z-index: 1;
+		height: 100%;
+	}
+	#result {
+		color: black;
+		background-color: white;
+		font-family: "Courier New";
+		font-size: 12px;
+		width: 50%;		
+		border: none;
+		position: absolute;
+		top: 0;
+		left: 50%;
+		overflow: auto;
+		z-index: 2;
+		height: 100%;
+        vertical-align: top;
+        overflow: auto;
+	}
+	#tests {
+		width: 50%;		
+		border: none;
+		position: absolute;
+		top: 0;
+		left: 50%;
+		z-index: 2;
+	}
+	</style>
+	
+	<!-- standalone yaml.js library -->
+	<script type="text/javascript" src="../dist/yaml.debug.js"></script>
+		
+	<title>yaml.js demo</title>
+</head>
+
+<body>
+<form action="" onsubmit="return false;">
+	<textarea name="yaml" id="yaml" cols="70" rows="20">--- !clarkevans.com/^invoice
+invoice: 34843
+date   : 2001-01-23
+bill-to: &id001
+    given  : Chris
+    family : Dumars
+    address:
+        lines: |
+            458 Walkman Dr.
+            Suite #292
+        city    : Royal Oak
+        state   : MI
+        postal  : 48046
+ship-to: *id001
+product:
+    - sku         : "BL394D"
+      quantity    : 4
+      description : Basketball
+      price       : 450.00
+    - sku         : BL4438H
+      quantity    : 1
+      description : Super Hoop
+      price       : 2392.00
+tax  : 251.42
+total: 4443.52
+comments: >
+    Late afternoon is best.
+    Backup contact is Nancy
+    Billsmer @ 338-4338.
+</textarea>
+<input type="button" id="parse" name="parse" value="Parse »" onclick="document.getElementById('result').innerHTML='<pre>'+JSON.stringify(YAML.parse(document.getElementById('yaml').value), null, 4)+'</pre>'" />
+<div id="result"></div>
+
+</form>
+</body>
+</html>
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..d9643d8
--- /dev/null
+++ b/index.js
@@ -0,0 +1,3 @@
+
+var Yaml = require('./lib/Yaml');
+module.exports = Yaml;
diff --git a/lib/Dumper.js b/lib/Dumper.js
new file mode 100644
index 0000000..6a842c4
--- /dev/null
+++ b/lib/Dumper.js
@@ -0,0 +1,53 @@
+// Generated by CoffeeScript 1.12.4
+var Dumper, Inline, Utils;
+
+Utils = require('./Utils');
+
+Inline = require('./Inline');
+
+Dumper = (function() {
+  function Dumper() {}
+
+  Dumper.indentation = 4;
+
+  Dumper.prototype.dump = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    var i, key, len, output, prefix, value, willBeInlined;
+    if (inline == null) {
+      inline = 0;
+    }
+    if (indent == null) {
+      indent = 0;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    output = '';
+    prefix = (indent ? Utils.strRepeat(' ', indent) : '');
+    if (inline <= 0 || typeof input !== 'object' || input instanceof Date || Utils.isEmpty(input)) {
+      output += prefix + Inline.dump(input, exceptionOnInvalidType, objectEncoder);
+    } else {
+      if (input instanceof Array) {
+        for (i = 0, len = input.length; i < len; i++) {
+          value = input[i];
+          willBeInlined = inline - 1 <= 0 || typeof value !== 'object' || Utils.isEmpty(value);
+          output += prefix + '-' + (willBeInlined ? ' ' : "\n") + this.dump(value, inline - 1, (willBeInlined ? 0 : indent + this.indentation), exceptionOnInvalidType, objectEncoder) + (willBeInlined ? "\n" : '');
+        }
+      } else {
+        for (key in input) {
+          value = input[key];
+          willBeInlined = inline - 1 <= 0 || typeof value !== 'object' || Utils.isEmpty(value);
+          output += prefix + Inline.dump(key, exceptionOnInvalidType, objectEncoder) + ':' + (willBeInlined ? ' ' : "\n") + this.dump(value, inline - 1, (willBeInlined ? 0 : indent + this.indentation), exceptionOnInvalidType, objectEncoder) + (willBeInlined ? "\n" : '');
+        }
+      }
+    }
+    return output;
+  };
+
+  return Dumper;
+
+})();
+
+module.exports = Dumper;
diff --git a/lib/Escaper.js b/lib/Escaper.js
new file mode 100644
index 0000000..96b4a2e
--- /dev/null
+++ b/lib/Escaper.js
@@ -0,0 +1,56 @@
+// Generated by CoffeeScript 1.12.4
+var Escaper, Pattern;
+
+Pattern = require('./Pattern');
+
+Escaper = (function() {
+  var ch;
+
+  function Escaper() {}
+
+  Escaper.LIST_ESCAPEES = ['\\', '\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", (ch = String.fromCharCode)(0x0085), ch(0x00A0), ch(0x2028), ch(0x2029)];
+
+  Escaper.LIST_ESCAPED = ['\\\\', '\\"', '\\"', '\\"', "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f", "\\N", "\\_", "\\L", "\\P"];
+
+  Escaper.MAPPING_ESCAPEES_TO_ESCAPED = (function() {
+    var i, j, mapping, ref;
+    mapping = {};
+    for (i = j = 0, ref = Escaper.LIST_ESCAPEES.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+      mapping[Escaper.LIST_ESCAPEES[i]] = Escaper.LIST_ESCAPED[i];
+    }
+    return mapping;
+  })();
+
+  Escaper.PATTERN_CHARACTERS_TO_ESCAPE = new Pattern('[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9');
+
+  Escaper.PATTERN_MAPPING_ESCAPEES = new Pattern(Escaper.LIST_ESCAPEES.join('|').split('\\').join('\\\\'));
+
+  Escaper.PATTERN_SINGLE_QUOTING = new Pattern('[\\s\'":{}[\\],&*#?]|^[-?|<>=!%@`]');
+
+  Escaper.requiresDoubleQuoting = function(value) {
+    return this.PATTERN_CHARACTERS_TO_ESCAPE.test(value);
+  };
+
+  Escaper.escapeWithDoubleQuotes = function(value) {
+    var result;
+    result = this.PATTERN_MAPPING_ESCAPEES.replace(value, (function(_this) {
+      return function(str) {
+        return _this.MAPPING_ESCAPEES_TO_ESCAPED[str];
+      };
+    })(this));
+    return '"' + result + '"';
+  };
+
+  Escaper.requiresSingleQuoting = function(value) {
+    return this.PATTERN_SINGLE_QUOTING.test(value);
+  };
+
+  Escaper.escapeWithSingleQuotes = function(value) {
+    return "'" + value.replace(/'/g, "''") + "'";
+  };
+
+  return Escaper;
+
+})();
+
+module.exports = Escaper;
diff --git a/lib/Exception/DumpException.js b/lib/Exception/DumpException.js
new file mode 100644
index 0000000..80f61ac
--- /dev/null
+++ b/lib/Exception/DumpException.js
@@ -0,0 +1,27 @@
+// Generated by CoffeeScript 1.12.4
+var DumpException,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+DumpException = (function(superClass) {
+  extend(DumpException, superClass);
+
+  function DumpException(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  DumpException.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<DumpException> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<DumpException> ' + this.message;
+    }
+  };
+
+  return DumpException;
+
+})(Error);
+
+module.exports = DumpException;
diff --git a/lib/Exception/ParseException.js b/lib/Exception/ParseException.js
new file mode 100644
index 0000000..a50ffc5
--- /dev/null
+++ b/lib/Exception/ParseException.js
@@ -0,0 +1,27 @@
+// Generated by CoffeeScript 1.12.4
+var ParseException,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+ParseException = (function(superClass) {
+  extend(ParseException, superClass);
+
+  function ParseException(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  ParseException.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<ParseException> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<ParseException> ' + this.message;
+    }
+  };
+
+  return ParseException;
+
+})(Error);
+
+module.exports = ParseException;
diff --git a/lib/Exception/ParseMore.js b/lib/Exception/ParseMore.js
new file mode 100644
index 0000000..88c8776
--- /dev/null
+++ b/lib/Exception/ParseMore.js
@@ -0,0 +1,27 @@
+// Generated by CoffeeScript 1.12.4
+var ParseMore,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+ParseMore = (function(superClass) {
+  extend(ParseMore, superClass);
+
+  function ParseMore(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  ParseMore.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<ParseMore> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<ParseMore> ' + this.message;
+    }
+  };
+
+  return ParseMore;
+
+})(Error);
+
+module.exports = ParseMore;
diff --git a/lib/Inline.js b/lib/Inline.js
new file mode 100644
index 0000000..aaf980b
--- /dev/null
+++ b/lib/Inline.js
@@ -0,0 +1,485 @@
+// Generated by CoffeeScript 1.12.4
+var DumpException, Escaper, Inline, ParseException, ParseMore, Pattern, Unescaper, Utils,
+  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; };
+
+Pattern = require('./Pattern');
+
+Unescaper = require('./Unescaper');
+
+Escaper = require('./Escaper');
+
+Utils = require('./Utils');
+
+ParseException = require('./Exception/ParseException');
+
+ParseMore = require('./Exception/ParseMore');
+
+DumpException = require('./Exception/DumpException');
+
+Inline = (function() {
+  function Inline() {}
+
+  Inline.REGEX_QUOTED_STRING = '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')';
+
+  Inline.PATTERN_TRAILING_COMMENTS = new Pattern('^\\s*#.*$');
+
+  Inline.PATTERN_QUOTED_SCALAR = new Pattern('^' + Inline.REGEX_QUOTED_STRING);
+
+  Inline.PATTERN_THOUSAND_NUMERIC_SCALAR = new Pattern('^(-|\\+)?[0-9,]+(\\.[0-9]+)?$');
+
+  Inline.PATTERN_SCALAR_BY_DELIMITERS = {};
+
+  Inline.settings = {};
+
+  Inline.configure = function(exceptionOnInvalidType, objectDecoder) {
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = null;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.settings.exceptionOnInvalidType = exceptionOnInvalidType;
+    this.settings.objectDecoder = objectDecoder;
+  };
+
+  Inline.parse = function(value, exceptionOnInvalidType, objectDecoder) {
+    var context, result;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.settings.exceptionOnInvalidType = exceptionOnInvalidType;
+    this.settings.objectDecoder = objectDecoder;
+    if (value == null) {
+      return '';
+    }
+    value = Utils.trim(value);
+    if (0 === value.length) {
+      return '';
+    }
+    context = {
+      exceptionOnInvalidType: exceptionOnInvalidType,
+      objectDecoder: objectDecoder,
+      i: 0
+    };
+    switch (value.charAt(0)) {
+      case '[':
+        result = this.parseSequence(value, context);
+        ++context.i;
+        break;
+      case '{':
+        result = this.parseMapping(value, context);
+        ++context.i;
+        break;
+      default:
+        result = this.parseScalar(value, null, ['"', "'"], context);
+    }
+    if (this.PATTERN_TRAILING_COMMENTS.replace(value.slice(context.i), '') !== '') {
+      throw new ParseException('Unexpected characters near "' + value.slice(context.i) + '".');
+    }
+    return result;
+  };
+
+  Inline.dump = function(value, exceptionOnInvalidType, objectEncoder) {
+    var ref, result, type;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    if (value == null) {
+      return 'null';
+    }
+    type = typeof value;
+    if (type === 'object') {
+      if (value instanceof Date) {
+        return value.toISOString();
+      } else if (objectEncoder != null) {
+        result = objectEncoder(value);
+        if (typeof result === 'string' || (result != null)) {
+          return result;
+        }
+      }
+      return this.dumpObject(value);
+    }
+    if (type === 'boolean') {
+      return (value ? 'true' : 'false');
+    }
+    if (Utils.isDigits(value)) {
+      return (type === 'string' ? "'" + value + "'" : String(parseInt(value)));
+    }
+    if (Utils.isNumeric(value)) {
+      return (type === 'string' ? "'" + value + "'" : String(parseFloat(value)));
+    }
+    if (type === 'number') {
+      return (value === 2e308 ? '.Inf' : (value === -2e308 ? '-.Inf' : (isNaN(value) ? '.NaN' : value)));
+    }
+    if (Escaper.requiresDoubleQuoting(value)) {
+      return Escaper.escapeWithDoubleQuotes(value);
+    }
+    if (Escaper.requiresSingleQuoting(value)) {
+      return Escaper.escapeWithSingleQuotes(value);
+    }
+    if ('' === value) {
+      return '""';
+    }
+    if (Utils.PATTERN_DATE.test(value)) {
+      return "'" + value + "'";
+    }
+    if ((ref = value.toLowerCase()) === 'null' || ref === '~' || ref === 'true' || ref === 'false') {
+      return "'" + value + "'";
+    }
+    return value;
+  };
+
+  Inline.dumpObject = function(value, exceptionOnInvalidType, objectSupport) {
+    var j, key, len1, output, val;
+    if (objectSupport == null) {
+      objectSupport = null;
+    }
+    if (value instanceof Array) {
+      output = [];
+      for (j = 0, len1 = value.length; j < len1; j++) {
+        val = value[j];
+        output.push(this.dump(val));
+      }
+      return '[' + output.join(', ') + ']';
+    } else {
+      output = [];
+      for (key in value) {
+        val = value[key];
+        output.push(this.dump(key) + ': ' + this.dump(val));
+      }
+      return '{' + output.join(', ') + '}';
+    }
+  };
+
+  Inline.parseScalar = function(scalar, delimiters, stringDelimiters, context, evaluate) {
+    var i, joinedDelimiters, match, output, pattern, ref, ref1, strpos, tmp;
+    if (delimiters == null) {
+      delimiters = null;
+    }
+    if (stringDelimiters == null) {
+      stringDelimiters = ['"', "'"];
+    }
+    if (context == null) {
+      context = null;
+    }
+    if (evaluate == null) {
+      evaluate = true;
+    }
+    if (context == null) {
+      context = {
+        exceptionOnInvalidType: this.settings.exceptionOnInvalidType,
+        objectDecoder: this.settings.objectDecoder,
+        i: 0
+      };
+    }
+    i = context.i;
+    if (ref = scalar.charAt(i), indexOf.call(stringDelimiters, ref) >= 0) {
+      output = this.parseQuotedScalar(scalar, context);
+      i = context.i;
+      if (delimiters != null) {
+        tmp = Utils.ltrim(scalar.slice(i), ' ');
+        if (!(ref1 = tmp.charAt(0), indexOf.call(delimiters, ref1) >= 0)) {
+          throw new ParseException('Unexpected characters (' + scalar.slice(i) + ').');
+        }
+      }
+    } else {
+      if (!delimiters) {
+        output = scalar.slice(i);
+        i += output.length;
+        strpos = output.indexOf(' #');
+        if (strpos !== -1) {
+          output = Utils.rtrim(output.slice(0, strpos));
+        }
+      } else {
+        joinedDelimiters = delimiters.join('|');
+        pattern = this.PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters];
+        if (pattern == null) {
+          pattern = new Pattern('^(.+?)(' + joinedDelimiters + ')');
+          this.PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] = pattern;
+        }
+        if (match = pattern.exec(scalar.slice(i))) {
+          output = match[1];
+          i += output.length;
+        } else {
+          throw new ParseException('Malformed inline YAML string (' + scalar + ').');
+        }
+      }
+      if (evaluate) {
+        output = this.evaluateScalar(output, context);
+      }
+    }
+    context.i = i;
+    return output;
+  };
+
+  Inline.parseQuotedScalar = function(scalar, context) {
+    var i, match, output;
+    i = context.i;
+    if (!(match = this.PATTERN_QUOTED_SCALAR.exec(scalar.slice(i)))) {
+      throw new ParseMore('Malformed inline YAML string (' + scalar.slice(i) + ').');
+    }
+    output = match[0].substr(1, match[0].length - 2);
+    if ('"' === scalar.charAt(i)) {
+      output = Unescaper.unescapeDoubleQuotedString(output);
+    } else {
+      output = Unescaper.unescapeSingleQuotedString(output);
+    }
+    i += match[0].length;
+    context.i = i;
+    return output;
+  };
+
+  Inline.parseSequence = function(sequence, context) {
+    var e, i, isQuoted, len, output, ref, value;
+    output = [];
+    len = sequence.length;
+    i = context.i;
+    i += 1;
+    while (i < len) {
+      context.i = i;
+      switch (sequence.charAt(i)) {
+        case '[':
+          output.push(this.parseSequence(sequence, context));
+          i = context.i;
+          break;
+        case '{':
+          output.push(this.parseMapping(sequence, context));
+          i = context.i;
+          break;
+        case ']':
+          return output;
+        case ',':
+        case ' ':
+        case "\n":
+          break;
+        default:
+          isQuoted = ((ref = sequence.charAt(i)) === '"' || ref === "'");
+          value = this.parseScalar(sequence, [',', ']'], ['"', "'"], context);
+          i = context.i;
+          if (!isQuoted && typeof value === 'string' && (value.indexOf(': ') !== -1 || value.indexOf(":\n") !== -1)) {
+            try {
+              value = this.parseMapping('{' + value + '}');
+            } catch (error) {
+              e = error;
+            }
+          }
+          output.push(value);
+          --i;
+      }
+      ++i;
+    }
+    throw new ParseMore('Malformed inline YAML string ' + sequence);
+  };
+
+  Inline.parseMapping = function(mapping, context) {
+    var done, i, key, len, output, shouldContinueWhileLoop, value;
+    output = {};
+    len = mapping.length;
+    i = context.i;
+    i += 1;
+    shouldContinueWhileLoop = false;
+    while (i < len) {
+      context.i = i;
+      switch (mapping.charAt(i)) {
+        case ' ':
+        case ',':
+        case "\n":
+          ++i;
+          context.i = i;
+          shouldContinueWhileLoop = true;
+          break;
+        case '}':
+          return output;
+      }
+      if (shouldContinueWhileLoop) {
+        shouldContinueWhileLoop = false;
+        continue;
+      }
+      key = this.parseScalar(mapping, [':', ' ', "\n"], ['"', "'"], context, false);
+      i = context.i;
+      done = false;
+      while (i < len) {
+        context.i = i;
+        switch (mapping.charAt(i)) {
+          case '[':
+            value = this.parseSequence(mapping, context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            break;
+          case '{':
+            value = this.parseMapping(mapping, context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            break;
+          case ':':
+          case ' ':
+          case "\n":
+            break;
+          default:
+            value = this.parseScalar(mapping, [',', '}'], ['"', "'"], context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            --i;
+        }
+        ++i;
+        if (done) {
+          break;
+        }
+      }
+    }
+    throw new ParseMore('Malformed inline YAML string ' + mapping);
+  };
+
+  Inline.evaluateScalar = function(scalar, context) {
+    var cast, date, exceptionOnInvalidType, firstChar, firstSpace, firstWord, objectDecoder, raw, scalarLower, subValue, trimmedScalar;
+    scalar = Utils.trim(scalar);
+    scalarLower = scalar.toLowerCase();
+    switch (scalarLower) {
+      case 'null':
+      case '':
+      case '~':
+        return null;
+      case 'true':
+        return true;
+      case 'false':
+        return false;
+      case '.inf':
+        return 2e308;
+      case '.nan':
+        return 0/0;
+      case '-.inf':
+        return 2e308;
+      default:
+        firstChar = scalarLower.charAt(0);
+        switch (firstChar) {
+          case '!':
+            firstSpace = scalar.indexOf(' ');
+            if (firstSpace === -1) {
+              firstWord = scalarLower;
+            } else {
+              firstWord = scalarLower.slice(0, firstSpace);
+            }
+            switch (firstWord) {
+              case '!':
+                if (firstSpace !== -1) {
+                  return parseInt(this.parseScalar(scalar.slice(2)));
+                }
+                return null;
+              case '!str':
+                return Utils.ltrim(scalar.slice(4));
+              case '!!str':
+                return Utils.ltrim(scalar.slice(5));
+              case '!!int':
+                return parseInt(this.parseScalar(scalar.slice(5)));
+              case '!!bool':
+                return Utils.parseBoolean(this.parseScalar(scalar.slice(6)), false);
+              case '!!float':
+                return parseFloat(this.parseScalar(scalar.slice(7)));
+              case '!!timestamp':
+                return Utils.stringToDate(Utils.ltrim(scalar.slice(11)));
+              default:
+                if (context == null) {
+                  context = {
+                    exceptionOnInvalidType: this.settings.exceptionOnInvalidType,
+                    objectDecoder: this.settings.objectDecoder,
+                    i: 0
+                  };
+                }
+                objectDecoder = context.objectDecoder, exceptionOnInvalidType = context.exceptionOnInvalidType;
+                if (objectDecoder) {
+                  trimmedScalar = Utils.rtrim(scalar);
+                  firstSpace = trimmedScalar.indexOf(' ');
+                  if (firstSpace === -1) {
+                    return objectDecoder(trimmedScalar, null);
+                  } else {
+                    subValue = Utils.ltrim(trimmedScalar.slice(firstSpace + 1));
+                    if (!(subValue.length > 0)) {
+                      subValue = null;
+                    }
+                    return objectDecoder(trimmedScalar.slice(0, firstSpace), subValue);
+                  }
+                }
+                if (exceptionOnInvalidType) {
+                  throw new ParseException('Custom object support when parsing a YAML file has been disabled.');
+                }
+                return null;
+            }
+            break;
+          case '0':
+            if ('0x' === scalar.slice(0, 2)) {
+              return Utils.hexDec(scalar);
+            } else if (Utils.isDigits(scalar)) {
+              return Utils.octDec(scalar);
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else {
+              return scalar;
+            }
+            break;
+          case '+':
+            if (Utils.isDigits(scalar)) {
+              raw = scalar;
+              cast = parseInt(raw);
+              if (raw === String(cast)) {
+                return cast;
+              } else {
+                return raw;
+              }
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+          case '-':
+            if (Utils.isDigits(scalar.slice(1))) {
+              if ('0' === scalar.charAt(1)) {
+                return -Utils.octDec(scalar.slice(1));
+              } else {
+                raw = scalar.slice(1);
+                cast = parseInt(raw);
+                if (raw === String(cast)) {
+                  return -cast;
+                } else {
+                  return -raw;
+                }
+              }
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+          default:
+            if (date = Utils.stringToDate(scalar)) {
+              return date;
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+        }
+    }
+  };
+
+  return Inline;
+
+})();
+
+module.exports = Inline;
diff --git a/lib/Parser.js b/lib/Parser.js
new file mode 100644
index 0000000..93237bf
--- /dev/null
+++ b/lib/Parser.js
@@ -0,0 +1,603 @@
+// Generated by CoffeeScript 1.12.4
+var Inline, ParseException, ParseMore, Parser, Pattern, Utils;
+
+Inline = require('./Inline');
+
+Pattern = require('./Pattern');
+
+Utils = require('./Utils');
+
+ParseException = require('./Exception/ParseException');
+
+ParseMore = require('./Exception/ParseMore');
+
+Parser = (function() {
+  Parser.prototype.PATTERN_FOLDED_SCALAR_ALL = new Pattern('^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$');
+
+  Parser.prototype.PATTERN_FOLDED_SCALAR_END = new Pattern('(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$');
+
+  Parser.prototype.PATTERN_SEQUENCE_ITEM = new Pattern('^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_ANCHOR_VALUE = new Pattern('^&(?<ref>[^ ]+) *(?<value>.*)');
+
+  Parser.prototype.PATTERN_COMPACT_NOTATION = new Pattern('^(?<key>' + Inline.REGEX_QUOTED_STRING + '|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_MAPPING_ITEM = new Pattern('^(?<key>' + Inline.REGEX_QUOTED_STRING + '|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_DECIMAL = new Pattern('\\d+');
+
+  Parser.prototype.PATTERN_INDENT_SPACES = new Pattern('^ +');
+
+  Parser.prototype.PATTERN_TRAILING_LINES = new Pattern('(\n*)$');
+
+  Parser.prototype.PATTERN_YAML_HEADER = new Pattern('^\\%YAML[: ][\\d\\.]+.*\n', 'm');
+
+  Parser.prototype.PATTERN_LEADING_COMMENTS = new Pattern('^(\\#.*?\n)+', 'm');
+
+  Parser.prototype.PATTERN_DOCUMENT_MARKER_START = new Pattern('^\\-\\-\\-.*?\n', 'm');
+
+  Parser.prototype.PATTERN_DOCUMENT_MARKER_END = new Pattern('^\\.\\.\\.\\s*$', 'm');
+
+  Parser.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION = {};
+
+  Parser.prototype.CONTEXT_NONE = 0;
+
+  Parser.prototype.CONTEXT_SEQUENCE = 1;
+
+  Parser.prototype.CONTEXT_MAPPING = 2;
+
+  function Parser(offset) {
+    this.offset = offset != null ? offset : 0;
+    this.lines = [];
+    this.currentLineNb = -1;
+    this.currentLine = '';
+    this.refs = {};
+  }
+
+  Parser.prototype.parse = function(value, exceptionOnInvalidType, objectDecoder) {
+    var alias, allowOverwrite, block, c, context, data, e, first, i, indent, isRef, j, k, key, l, lastKey, len, len1, len2, len3, lineCount, m, matches, mergeNode, n, name, parsed, parsedItem, parser, ref, ref1, ref2, refName, refValue, val, values;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.currentLineNb = -1;
+    this.currentLine = '';
+    this.lines = this.cleanup(value).split("\n");
+    data = null;
+    context = this.CONTEXT_NONE;
+    allowOverwrite = false;
+    while (this.moveToNextLine()) {
+      if (this.isCurrentLineEmpty()) {
+        continue;
+      }
+      if ("\t" === this.currentLine[0]) {
+        throw new ParseException('A YAML file cannot contain tabs as indentation.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+      isRef = mergeNode = false;
+      if (values = this.PATTERN_SEQUENCE_ITEM.exec(this.currentLine)) {
+        if (this.CONTEXT_MAPPING === context) {
+          throw new ParseException('You cannot define a sequence item when in a mapping');
+        }
+        context = this.CONTEXT_SEQUENCE;
+        if (data == null) {
+          data = [];
+        }
+        if ((values.value != null) && (matches = this.PATTERN_ANCHOR_VALUE.exec(values.value))) {
+          isRef = matches.ref;
+          values.value = matches.value;
+        }
+        if (!(values.value != null) || '' === Utils.trim(values.value, ' ') || Utils.ltrim(values.value, ' ').indexOf('#') === 0) {
+          if (this.currentLineNb < this.lines.length - 1 && !this.isNextLineUnIndentedCollection()) {
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            data.push(parser.parse(this.getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder));
+          } else {
+            data.push(null);
+          }
+        } else {
+          if (((ref = values.leadspaces) != null ? ref.length : void 0) && (matches = this.PATTERN_COMPACT_NOTATION.exec(values.value))) {
+            c = this.getRealCurrentLineNb();
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            block = values.value;
+            indent = this.getCurrentLineIndentation();
+            if (this.isNextLineIndented(false)) {
+              block += "\n" + this.getNextEmbedBlock(indent + values.leadspaces.length + 1, true);
+            }
+            data.push(parser.parse(block, exceptionOnInvalidType, objectDecoder));
+          } else {
+            data.push(this.parseValue(values.value, exceptionOnInvalidType, objectDecoder));
+          }
+        }
+      } else if ((values = this.PATTERN_MAPPING_ITEM.exec(this.currentLine)) && values.key.indexOf(' #') === -1) {
+        if (this.CONTEXT_SEQUENCE === context) {
+          throw new ParseException('You cannot define a mapping item when in a sequence');
+        }
+        context = this.CONTEXT_MAPPING;
+        if (data == null) {
+          data = {};
+        }
+        Inline.configure(exceptionOnInvalidType, objectDecoder);
+        try {
+          key = Inline.parseScalar(values.key);
+        } catch (error) {
+          e = error;
+          e.parsedLine = this.getRealCurrentLineNb() + 1;
+          e.snippet = this.currentLine;
+          throw e;
+        }
+        if ('<<' === key) {
+          mergeNode = true;
+          allowOverwrite = true;
+          if (((ref1 = values.value) != null ? ref1.indexOf('*') : void 0) === 0) {
+            refName = values.value.slice(1);
+            if (this.refs[refName] == null) {
+              throw new ParseException('Reference "' + refName + '" does not exist.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            refValue = this.refs[refName];
+            if (typeof refValue !== 'object') {
+              throw new ParseException('YAML merge keys used with a scalar value instead of an object.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            if (refValue instanceof Array) {
+              for (i = j = 0, len = refValue.length; j < len; i = ++j) {
+                value = refValue[i];
+                if (data[name = String(i)] == null) {
+                  data[name] = value;
+                }
+              }
+            } else {
+              for (key in refValue) {
+                value = refValue[key];
+                if (data[key] == null) {
+                  data[key] = value;
+                }
+              }
+            }
+          } else {
+            if ((values.value != null) && values.value !== '') {
+              value = values.value;
+            } else {
+              value = this.getNextEmbedBlock();
+            }
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            parsed = parser.parse(value, exceptionOnInvalidType);
+            if (typeof parsed !== 'object') {
+              throw new ParseException('YAML merge keys used with a scalar value instead of an object.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            if (parsed instanceof Array) {
+              for (l = 0, len1 = parsed.length; l < len1; l++) {
+                parsedItem = parsed[l];
+                if (typeof parsedItem !== 'object') {
+                  throw new ParseException('Merge items must be objects.', this.getRealCurrentLineNb() + 1, parsedItem);
+                }
+                if (parsedItem instanceof Array) {
+                  for (i = m = 0, len2 = parsedItem.length; m < len2; i = ++m) {
+                    value = parsedItem[i];
+                    k = String(i);
+                    if (!data.hasOwnProperty(k)) {
+                      data[k] = value;
+                    }
+                  }
+                } else {
+                  for (key in parsedItem) {
+                    value = parsedItem[key];
+                    if (!data.hasOwnProperty(key)) {
+                      data[key] = value;
+                    }
+                  }
+                }
+              }
+            } else {
+              for (key in parsed) {
+                value = parsed[key];
+                if (!data.hasOwnProperty(key)) {
+                  data[key] = value;
+                }
+              }
+            }
+          }
+        } else if ((values.value != null) && (matches = this.PATTERN_ANCHOR_VALUE.exec(values.value))) {
+          isRef = matches.ref;
+          values.value = matches.value;
+        }
+        if (mergeNode) {
+
+        } else if (!(values.value != null) || '' === Utils.trim(values.value, ' ') || Utils.ltrim(values.value, ' ').indexOf('#') === 0) {
+          if (!(this.isNextLineIndented()) && !(this.isNextLineUnIndentedCollection())) {
+            if (allowOverwrite || data[key] === void 0) {
+              data[key] = null;
+            }
+          } else {
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            val = parser.parse(this.getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder);
+            if (allowOverwrite || data[key] === void 0) {
+              data[key] = val;
+            }
+          }
+        } else {
+          val = this.parseValue(values.value, exceptionOnInvalidType, objectDecoder);
+          if (allowOverwrite || data[key] === void 0) {
+            data[key] = val;
+          }
+        }
+      } else {
+        lineCount = this.lines.length;
+        if (1 === lineCount || (2 === lineCount && Utils.isEmpty(this.lines[1]))) {
+          try {
+            value = Inline.parse(this.lines[0], exceptionOnInvalidType, objectDecoder);
+          } catch (error) {
+            e = error;
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+          if (typeof value === 'object') {
+            if (value instanceof Array) {
+              first = value[0];
+            } else {
+              for (key in value) {
+                first = value[key];
+                break;
+              }
+            }
+            if (typeof first === 'string' && first.indexOf('*') === 0) {
+              data = [];
+              for (n = 0, len3 = value.length; n < len3; n++) {
+                alias = value[n];
+                data.push(this.refs[alias.slice(1)]);
+              }
+              value = data;
+            }
+          }
+          return value;
+        } else if ((ref2 = Utils.ltrim(value).charAt(0)) === '[' || ref2 === '{') {
+          try {
+            return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+          } catch (error) {
+            e = error;
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+        }
+        throw new ParseException('Unable to parse.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+      if (isRef) {
+        if (data instanceof Array) {
+          this.refs[isRef] = data[data.length - 1];
+        } else {
+          lastKey = null;
+          for (key in data) {
+            lastKey = key;
+          }
+          this.refs[isRef] = data[lastKey];
+        }
+      }
+    }
+    if (Utils.isEmpty(data)) {
+      return null;
+    } else {
+      return data;
+    }
+  };
+
+  Parser.prototype.getRealCurrentLineNb = function() {
+    return this.currentLineNb + this.offset;
+  };
+
+  Parser.prototype.getCurrentLineIndentation = function() {
+    return this.currentLine.length - Utils.ltrim(this.currentLine, ' ').length;
+  };
+
+  Parser.prototype.getNextEmbedBlock = function(indentation, includeUnindentedCollection) {
+    var data, indent, isItUnindentedCollection, newIndent, removeComments, removeCommentsPattern, unindentedEmbedBlock;
+    if (indentation == null) {
+      indentation = null;
+    }
+    if (includeUnindentedCollection == null) {
+      includeUnindentedCollection = false;
+    }
+    this.moveToNextLine();
+    if (indentation == null) {
+      newIndent = this.getCurrentLineIndentation();
+      unindentedEmbedBlock = this.isStringUnIndentedCollectionItem(this.currentLine);
+      if (!(this.isCurrentLineEmpty()) && 0 === newIndent && !unindentedEmbedBlock) {
+        throw new ParseException('Indentation problem.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+    } else {
+      newIndent = indentation;
+    }
+    data = [this.currentLine.slice(newIndent)];
+    if (!includeUnindentedCollection) {
+      isItUnindentedCollection = this.isStringUnIndentedCollectionItem(this.currentLine);
+    }
+    removeCommentsPattern = this.PATTERN_FOLDED_SCALAR_END;
+    removeComments = !removeCommentsPattern.test(this.currentLine);
+    while (this.moveToNextLine()) {
+      indent = this.getCurrentLineIndentation();
+      if (indent === newIndent) {
+        removeComments = !removeCommentsPattern.test(this.currentLine);
+      }
+      if (removeComments && this.isCurrentLineComment()) {
+        continue;
+      }
+      if (this.isCurrentLineBlank()) {
+        data.push(this.currentLine.slice(newIndent));
+        continue;
+      }
+      if (isItUnindentedCollection && !this.isStringUnIndentedCollectionItem(this.currentLine) && indent === newIndent) {
+        this.moveToPreviousLine();
+        break;
+      }
+      if (indent >= newIndent) {
+        data.push(this.currentLine.slice(newIndent));
+      } else if (Utils.ltrim(this.currentLine).charAt(0) === '#') {
+
+      } else if (0 === indent) {
+        this.moveToPreviousLine();
+        break;
+      } else {
+        throw new ParseException('Indentation problem.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+    }
+    return data.join("\n");
+  };
+
+  Parser.prototype.moveToNextLine = function() {
+    if (this.currentLineNb >= this.lines.length - 1) {
+      return false;
+    }
+    this.currentLine = this.lines[++this.currentLineNb];
+    return true;
+  };
+
+  Parser.prototype.moveToPreviousLine = function() {
+    this.currentLine = this.lines[--this.currentLineNb];
+  };
+
+  Parser.prototype.parseValue = function(value, exceptionOnInvalidType, objectDecoder) {
+    var e, foldedIndent, matches, modifiers, pos, ref, ref1, val;
+    if (0 === value.indexOf('*')) {
+      pos = value.indexOf('#');
+      if (pos !== -1) {
+        value = value.substr(1, pos - 2);
+      } else {
+        value = value.slice(1);
+      }
+      if (this.refs[value] === void 0) {
+        throw new ParseException('Reference "' + value + '" does not exist.', this.currentLine);
+      }
+      return this.refs[value];
+    }
+    if (matches = this.PATTERN_FOLDED_SCALAR_ALL.exec(value)) {
+      modifiers = (ref = matches.modifiers) != null ? ref : '';
+      foldedIndent = Math.abs(parseInt(modifiers));
+      if (isNaN(foldedIndent)) {
+        foldedIndent = 0;
+      }
+      val = this.parseFoldedScalar(matches.separator, this.PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent);
+      if (matches.type != null) {
+        Inline.configure(exceptionOnInvalidType, objectDecoder);
+        return Inline.parseScalar(matches.type + ' ' + val);
+      } else {
+        return val;
+      }
+    }
+    if ((ref1 = value.charAt(0)) === '[' || ref1 === '{' || ref1 === '"' || ref1 === "'") {
+      while (true) {
+        try {
+          return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+        } catch (error) {
+          e = error;
+          if (e instanceof ParseMore && this.moveToNextLine()) {
+            value += "\n" + Utils.trim(this.currentLine, ' ');
+          } else {
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+        }
+      }
+    } else {
+      if (this.isNextLineIndented()) {
+        value += "\n" + this.getNextEmbedBlock();
+      }
+      return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+    }
+  };
+
+  Parser.prototype.parseFoldedScalar = function(separator, indicator, indentation) {
+    var isCurrentLineBlank, j, len, line, matches, newText, notEOF, pattern, ref, text;
+    if (indicator == null) {
+      indicator = '';
+    }
+    if (indentation == null) {
+      indentation = 0;
+    }
+    notEOF = this.moveToNextLine();
+    if (!notEOF) {
+      return '';
+    }
+    isCurrentLineBlank = this.isCurrentLineBlank();
+    text = '';
+    while (notEOF && isCurrentLineBlank) {
+      if (notEOF = this.moveToNextLine()) {
+        text += "\n";
+        isCurrentLineBlank = this.isCurrentLineBlank();
+      }
+    }
+    if (0 === indentation) {
+      if (matches = this.PATTERN_INDENT_SPACES.exec(this.currentLine)) {
+        indentation = matches[0].length;
+      }
+    }
+    if (indentation > 0) {
+      pattern = this.PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation];
+      if (pattern == null) {
+        pattern = new Pattern('^ {' + indentation + '}(.*)$');
+        Parser.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern;
+      }
+      while (notEOF && (isCurrentLineBlank || (matches = pattern.exec(this.currentLine)))) {
+        if (isCurrentLineBlank) {
+          text += this.currentLine.slice(indentation);
+        } else {
+          text += matches[1];
+        }
+        if (notEOF = this.moveToNextLine()) {
+          text += "\n";
+          isCurrentLineBlank = this.isCurrentLineBlank();
+        }
+      }
+    } else if (notEOF) {
+      text += "\n";
+    }
+    if (notEOF) {
+      this.moveToPreviousLine();
+    }
+    if ('>' === separator) {
+      newText = '';
+      ref = text.split("\n");
+      for (j = 0, len = ref.length; j < len; j++) {
+        line = ref[j];
+        if (line.length === 0 || line.charAt(0) === ' ') {
+          newText = Utils.rtrim(newText, ' ') + line + "\n";
+        } else {
+          newText += line + ' ';
+        }
+      }
+      text = newText;
+    }
+    if ('+' !== indicator) {
+      text = Utils.rtrim(text);
+    }
+    if ('' === indicator) {
+      text = this.PATTERN_TRAILING_LINES.replace(text, "\n");
+    } else if ('-' === indicator) {
+      text = this.PATTERN_TRAILING_LINES.replace(text, '');
+    }
+    return text;
+  };
+
+  Parser.prototype.isNextLineIndented = function(ignoreComments) {
+    var EOF, currentIndentation, ret;
+    if (ignoreComments == null) {
+      ignoreComments = true;
+    }
+    currentIndentation = this.getCurrentLineIndentation();
+    EOF = !this.moveToNextLine();
+    if (ignoreComments) {
+      while (!EOF && this.isCurrentLineEmpty()) {
+        EOF = !this.moveToNextLine();
+      }
+    } else {
+      while (!EOF && this.isCurrentLineBlank()) {
+        EOF = !this.moveToNextLine();
+      }
+    }
+    if (EOF) {
+      return false;
+    }
+    ret = false;
+    if (this.getCurrentLineIndentation() > currentIndentation) {
+      ret = true;
+    }
+    this.moveToPreviousLine();
+    return ret;
+  };
+
+  Parser.prototype.isCurrentLineEmpty = function() {
+    var trimmedLine;
+    trimmedLine = Utils.trim(this.currentLine, ' ');
+    return trimmedLine.length === 0 || trimmedLine.charAt(0) === '#';
+  };
+
+  Parser.prototype.isCurrentLineBlank = function() {
+    return '' === Utils.trim(this.currentLine, ' ');
+  };
+
+  Parser.prototype.isCurrentLineComment = function() {
+    var ltrimmedLine;
+    ltrimmedLine = Utils.ltrim(this.currentLine, ' ');
+    return ltrimmedLine.charAt(0) === '#';
+  };
+
+  Parser.prototype.cleanup = function(value) {
+    var count, i, indent, j, l, len, len1, line, lines, ref, ref1, ref2, smallestIndent, trimmedValue;
+    if (value.indexOf("\r") !== -1) {
+      value = value.split("\r\n").join("\n").split("\r").join("\n");
+    }
+    count = 0;
+    ref = this.PATTERN_YAML_HEADER.replaceAll(value, ''), value = ref[0], count = ref[1];
+    this.offset += count;
+    ref1 = this.PATTERN_LEADING_COMMENTS.replaceAll(value, '', 1), trimmedValue = ref1[0], count = ref1[1];
+    if (count === 1) {
+      this.offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n");
+      value = trimmedValue;
+    }
+    ref2 = this.PATTERN_DOCUMENT_MARKER_START.replaceAll(value, '', 1), trimmedValue = ref2[0], count = ref2[1];
+    if (count === 1) {
+      this.offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n");
+      value = trimmedValue;
+      value = this.PATTERN_DOCUMENT_MARKER_END.replace(value, '');
+    }
+    lines = value.split("\n");
+    smallestIndent = -1;
+    for (j = 0, len = lines.length; j < len; j++) {
+      line = lines[j];
+      if (Utils.trim(line, ' ').length === 0) {
+        continue;
+      }
+      indent = line.length - Utils.ltrim(line).length;
+      if (smallestIndent === -1 || indent < smallestIndent) {
+        smallestIndent = indent;
+      }
+    }
+    if (smallestIndent > 0) {
+      for (i = l = 0, len1 = lines.length; l < len1; i = ++l) {
+        line = lines[i];
+        lines[i] = line.slice(smallestIndent);
+      }
+      value = lines.join("\n");
+    }
+    return value;
+  };
+
+  Parser.prototype.isNextLineUnIndentedCollection = function(currentIndentation) {
+    var notEOF, ret;
+    if (currentIndentation == null) {
+      currentIndentation = null;
+    }
+    if (currentIndentation == null) {
+      currentIndentation = this.getCurrentLineIndentation();
+    }
+    notEOF = this.moveToNextLine();
+    while (notEOF && this.isCurrentLineEmpty()) {
+      notEOF = this.moveToNextLine();
+    }
+    if (false === notEOF) {
+      return false;
+    }
+    ret = false;
+    if (this.getCurrentLineIndentation() === currentIndentation && this.isStringUnIndentedCollectionItem(this.currentLine)) {
+      ret = true;
+    }
+    this.moveToPreviousLine();
+    return ret;
+  };
+
+  Parser.prototype.isStringUnIndentedCollectionItem = function() {
+    return this.currentLine === '-' || this.currentLine.slice(0, 2) === '- ';
+  };
+
+  return Parser;
+
+})();
+
+module.exports = Parser;
diff --git a/lib/Pattern.js b/lib/Pattern.js
new file mode 100644
index 0000000..3cca4d3
--- /dev/null
+++ b/lib/Pattern.js
@@ -0,0 +1,119 @@
+// Generated by CoffeeScript 1.12.4
+var Pattern;
+
+Pattern = (function() {
+  Pattern.prototype.regex = null;
+
+  Pattern.prototype.rawRegex = null;
+
+  Pattern.prototype.cleanedRegex = null;
+
+  Pattern.prototype.mapping = null;
+
+  function Pattern(rawRegex, modifiers) {
+    var _char, capturingBracketNumber, cleanedRegex, i, len, mapping, name, part, subChar;
+    if (modifiers == null) {
+      modifiers = '';
+    }
+    cleanedRegex = '';
+    len = rawRegex.length;
+    mapping = null;
+    capturingBracketNumber = 0;
+    i = 0;
+    while (i < len) {
+      _char = rawRegex.charAt(i);
+      if (_char === '\\') {
+        cleanedRegex += rawRegex.slice(i, +(i + 1) + 1 || 9e9);
+        i++;
+      } else if (_char === '(') {
+        if (i < len - 2) {
+          part = rawRegex.slice(i, +(i + 2) + 1 || 9e9);
+          if (part === '(?:') {
+            i += 2;
+            cleanedRegex += part;
+          } else if (part === '(?<') {
+            capturingBracketNumber++;
+            i += 2;
+            name = '';
+            while (i + 1 < len) {
+              subChar = rawRegex.charAt(i + 1);
+              if (subChar === '>') {
+                cleanedRegex += '(';
+                i++;
+                if (name.length > 0) {
+                  if (mapping == null) {
+                    mapping = {};
+                  }
+                  mapping[name] = capturingBracketNumber;
+                }
+                break;
+              } else {
+                name += subChar;
+              }
+              i++;
+            }
+          } else {
+            cleanedRegex += _char;
+            capturingBracketNumber++;
+          }
+        } else {
+          cleanedRegex += _char;
+        }
+      } else {
+        cleanedRegex += _char;
+      }
+      i++;
+    }
+    this.rawRegex = rawRegex;
+    this.cleanedRegex = cleanedRegex;
+    this.regex = new RegExp(this.cleanedRegex, 'g' + modifiers.replace('g', ''));
+    this.mapping = mapping;
+  }
+
+  Pattern.prototype.exec = function(str) {
+    var index, matches, name, ref;
+    this.regex.lastIndex = 0;
+    matches = this.regex.exec(str);
+    if (matches == null) {
+      return null;
+    }
+    if (this.mapping != null) {
+      ref = this.mapping;
+      for (name in ref) {
+        index = ref[name];
+        matches[name] = matches[index];
+      }
+    }
+    return matches;
+  };
+
+  Pattern.prototype.test = function(str) {
+    this.regex.lastIndex = 0;
+    return this.regex.test(str);
+  };
+
+  Pattern.prototype.replace = function(str, replacement) {
+    this.regex.lastIndex = 0;
+    return str.replace(this.regex, replacement);
+  };
+
+  Pattern.prototype.replaceAll = function(str, replacement, limit) {
+    var count;
+    if (limit == null) {
+      limit = 0;
+    }
+    this.regex.lastIndex = 0;
+    count = 0;
+    while (this.regex.test(str) && (limit === 0 || count < limit)) {
+      this.regex.lastIndex = 0;
+      str = str.replace(this.regex, replacement);
+      count++;
+    }
+    return [str, count];
+  };
+
+  return Pattern;
+
+})();
+
+module.exports = Pattern;
diff --git a/lib/Unescaper.js b/lib/Unescaper.js
new file mode 100644
index 0000000..c4a707a
--- /dev/null
+++ b/lib/Unescaper.js
@@ -0,0 +1,83 @@
+// Generated by CoffeeScript 1.12.4
+var Pattern, Unescaper, Utils;
+
+Utils = require('./Utils');
+
+Pattern = require('./Pattern');
+
+Unescaper = (function() {
+  function Unescaper() {}
+
+  Unescaper.PATTERN_ESCAPED_CHARACTER = new Pattern('\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})');
+
+  Unescaper.unescapeSingleQuotedString = function(value) {
+    return value.replace(/\'\'/g, '\'');
+  };
+
+  Unescaper.unescapeDoubleQuotedString = function(value) {
+    if (this._unescapeCallback == null) {
+      this._unescapeCallback = (function(_this) {
+        return function(str) {
+          return _this.unescapeCharacter(str);
+        };
+      })(this);
+    }
+    return this.PATTERN_ESCAPED_CHARACTER.replace(value, this._unescapeCallback);
+  };
+
+  Unescaper.unescapeCharacter = function(value) {
+    var ch;
+    ch = String.fromCharCode;
+    switch (value.charAt(1)) {
+      case '0':
+        return ch(0);
+      case 'a':
+        return ch(7);
+      case 'b':
+        return ch(8);
+      case 't':
+        return "\t";
+      case "\t":
+        return "\t";
+      case 'n':
+        return "\n";
+      case 'v':
+        return ch(11);
+      case 'f':
+        return ch(12);
+      case 'r':
+        return ch(13);
+      case 'e':
+        return ch(27);
+      case ' ':
+        return ' ';
+      case '"':
+        return '"';
+      case '/':
+        return '/';
+      case '\\':
+        return '\\';
+      case 'N':
+        return ch(0x0085);
+      case '_':
+        return ch(0x00A0);
+      case 'L':
+        return ch(0x2028);
+      case 'P':
+        return ch(0x2029);
+      case 'x':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 2)));
+      case 'u':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 4)));
+      case 'U':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 8)));
+      default:
+        return '';
+    }
+  };
+
+  return Unescaper;
+
+})();
+
+module.exports = Unescaper;
diff --git a/lib/Utils.js b/lib/Utils.js
new file mode 100644
index 0000000..5d54d2d
--- /dev/null
+++ b/lib/Utils.js
@@ -0,0 +1,297 @@
+// Generated by CoffeeScript 1.12.4
+var Pattern, Utils,
+  hasProp = {}.hasOwnProperty;
+
+Pattern = require('./Pattern');
+
+Utils = (function() {
+  function Utils() {}
+
+  Utils.REGEX_LEFT_TRIM_BY_CHAR = {};
+
+  Utils.REGEX_RIGHT_TRIM_BY_CHAR = {};
+
+  Utils.REGEX_SPACES = /\s+/g;
+
+  Utils.REGEX_DIGITS = /^\d+$/;
+
+  Utils.REGEX_OCTAL = /[^0-7]/gi;
+
+  Utils.REGEX_HEXADECIMAL = /[^a-f0-9]/gi;
+
+  Utils.PATTERN_DATE = new Pattern('^' + '(?<year>[0-9][0-9][0-9][0-9])' + '-(?<month>[0-9][0-9]?)' + '-(?<day>[0-9][0-9]?)' + '(?:(?:[Tt]|[ \t]+)' + '(?<hour>[0-9][0-9]?)' + ':(?<minute>[0-9][0-9])' + ':(?<second>[0-9][0-9])' + '(?:\.(?<fraction>[0-9]*))?' + '(?:[ \t]*(?<tz>Z|(?<tz_sign>[-+])(?<tz_hour>[0-9][0-9]?)' + '(?::(?<tz_minute>[0-9][0-9]))?))?)?' + '$', 'i');
+
+  Utils.LOCAL_TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
+
+  Utils.trim = function(str, _char) {
+    var regexLeft, regexRight;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexLeft = this.REGEX_LEFT_TRIM_BY_CHAR[_char];
+    if (regexLeft == null) {
+      this.REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp('^' + _char + '' + _char + '*');
+    }
+    regexLeft.lastIndex = 0;
+    regexRight = this.REGEX_RIGHT_TRIM_BY_CHAR[_char];
+    if (regexRight == null) {
+      this.REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp(_char + '' + _char + '*$');
+    }
+    regexRight.lastIndex = 0;
+    return str.replace(regexLeft, '').replace(regexRight, '');
+  };
+
+  Utils.ltrim = function(str, _char) {
+    var regexLeft;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexLeft = this.REGEX_LEFT_TRIM_BY_CHAR[_char];
+    if (regexLeft == null) {
+      this.REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp('^' + _char + '' + _char + '*');
+    }
+    regexLeft.lastIndex = 0;
+    return str.replace(regexLeft, '');
+  };
+
+  Utils.rtrim = function(str, _char) {
+    var regexRight;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexRight = this.REGEX_RIGHT_TRIM_BY_CHAR[_char];
+    if (regexRight == null) {
+      this.REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp(_char + '' + _char + '*$');
+    }
+    regexRight.lastIndex = 0;
+    return str.replace(regexRight, '');
+  };
+
+  Utils.isEmpty = function(value) {
+    return !value || value === '' || value === '0' || (value instanceof Array && value.length === 0) || this.isEmptyObject(value);
+  };
+
+  Utils.isEmptyObject = function(value) {
+    var k;
+    return value instanceof Object && ((function() {
+      var results;
+      results = [];
+      for (k in value) {
+        if (!hasProp.call(value, k)) continue;
+        results.push(k);
+      }
+      return results;
+    })()).length === 0;
+  };
+
+  Utils.subStrCount = function(string, subString, start, length) {
+    var c, i, j, len, ref, sublen;
+    c = 0;
+    string = '' + string;
+    subString = '' + subString;
+    if (start != null) {
+      string = string.slice(start);
+    }
+    if (length != null) {
+      string = string.slice(0, length);
+    }
+    len = string.length;
+    sublen = subString.length;
+    for (i = j = 0, ref = len; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+      if (subString === string.slice(i, sublen)) {
+        c++;
+        i += sublen - 1;
+      }
+    }
+    return c;
+  };
+
+  Utils.isDigits = function(input) {
+    this.REGEX_DIGITS.lastIndex = 0;
+    return this.REGEX_DIGITS.test(input);
+  };
+
+  Utils.octDec = function(input) {
+    this.REGEX_OCTAL.lastIndex = 0;
+    return parseInt((input + '').replace(this.REGEX_OCTAL, ''), 8);
+  };
+
+  Utils.hexDec = function(input) {
+    this.REGEX_HEXADECIMAL.lastIndex = 0;
+    input = this.trim(input);
+    if ((input + '').slice(0, 2) === '0x') {
+      input = (input + '').slice(2);
+    }
+    return parseInt((input + '').replace(this.REGEX_HEXADECIMAL, ''), 16);
+  };
+
+  Utils.utf8chr = function(c) {
+    var ch;
+    ch = String.fromCharCode;
+    if (0x80 > (c %= 0x200000)) {
+      return ch(c);
+    }
+    if (0x800 > c) {
+      return ch(0xC0 | c >> 6) + ch(0x80 | c & 0x3F);
+    }
+    if (0x10000 > c) {
+      return ch(0xE0 | c >> 12) + ch(0x80 | c >> 6 & 0x3F) + ch(0x80 | c & 0x3F);
+    }
+    return ch(0xF0 | c >> 18) + ch(0x80 | c >> 12 & 0x3F) + ch(0x80 | c >> 6 & 0x3F) + ch(0x80 | c & 0x3F);
+  };
+
+  Utils.parseBoolean = function(input, strict) {
+    var lowerInput;
+    if (strict == null) {
+      strict = true;
+    }
+    if (typeof input === 'string') {
+      lowerInput = input.toLowerCase();
+      if (!strict) {
+        if (lowerInput === 'no') {
+          return false;
+        }
+      }
+      if (lowerInput === '0') {
+        return false;
+      }
+      if (lowerInput === 'false') {
+        return false;
+      }
+      if (lowerInput === '') {
+        return false;
+      }
+      return true;
+    }
+    return !!input;
+  };
+
+  Utils.isNumeric = function(input) {
+    this.REGEX_SPACES.lastIndex = 0;
+    return typeof input === 'number' || typeof input === 'string' && !isNaN(input) && input.replace(this.REGEX_SPACES, '') !== '';
+  };
+
+  Utils.stringToDate = function(str) {
+    var date, day, fraction, hour, info, minute, month, second, tz_hour, tz_minute, tz_offset, year;
+    if (!(str != null ? str.length : void 0)) {
+      return null;
+    }
+    info = this.PATTERN_DATE.exec(str);
+    if (!info) {
+      return null;
+    }
+    year = parseInt(info.year, 10);
+    month = parseInt(info.month, 10) - 1;
+    day = parseInt(info.day, 10);
+    if (info.hour == null) {
+      date = new Date(Date.UTC(year, month, day));
+      return date;
+    }
+    hour = parseInt(info.hour, 10);
+    minute = parseInt(info.minute, 10);
+    second = parseInt(info.second, 10);
+    if (info.fraction != null) {
+      fraction = info.fraction.slice(0, 3);
+      while (fraction.length < 3) {
+        fraction += '0';
+      }
+      fraction = parseInt(fraction, 10);
+    } else {
+      fraction = 0;
+    }
+    if (info.tz != null) {
+      tz_hour = parseInt(info.tz_hour, 10);
+      if (info.tz_minute != null) {
+        tz_minute = parseInt(info.tz_minute, 10);
+      } else {
+        tz_minute = 0;
+      }
+      tz_offset = (tz_hour * 60 + tz_minute) * 60000;
+      if ('-' === info.tz_sign) {
+        tz_offset *= -1;
+      }
+    }
+    date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));
+    if (tz_offset) {
+      date.setTime(date.getTime() - tz_offset);
+    }
+    return date;
+  };
+
+  Utils.strRepeat = function(str, number) {
+    var i, res;
+    res = '';
+    i = 0;
+    while (i < number) {
+      res += str;
+      i++;
+    }
+    return res;
+  };
+
+  Utils.getStringFromFile = function(path, callback) {
+    var data, fs, j, len1, name, ref, req, xhr;
+    if (callback == null) {
+      callback = null;
+    }
+    xhr = null;
+    if (typeof window !== "undefined" && window !== null) {
+      if (window.XMLHttpRequest) {
+        xhr = new XMLHttpRequest();
+      } else if (window.ActiveXObject) {
+        ref = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"];
+        for (j = 0, len1 = ref.length; j < len1; j++) {
+          name = ref[j];
+          try {
+            xhr = new ActiveXObject(name);
+          } catch (error) {}
+        }
+      }
+    }
+    if (xhr != null) {
+      if (callback != null) {
+        xhr.onreadystatechange = function() {
+          if (xhr.readyState === 4) {
+            if (xhr.status === 200 || xhr.status === 0) {
+              return callback(xhr.responseText);
+            } else {
+              return callback(null);
+            }
+          }
+        };
+        xhr.open('GET', path, true);
+        return xhr.send(null);
+      } else {
+        xhr.open('GET', path, false);
+        xhr.send(null);
+        if (xhr.status === 200 || xhr.status === 0) {
+          return xhr.responseText;
+        }
+        return null;
+      }
+    } else {
+      req = require;
+      fs = req('fs');
+      if (callback != null) {
+        return fs.readFile(path, function(err, data) {
+          if (err) {
+            return callback(null);
+          } else {
+            return callback(String(data));
+          }
+        });
+      } else {
+        data = fs.readFileSync(path);
+        if (data != null) {
+          return String(data);
+        }
+        return null;
+      }
+    }
+  };
+
+  return Utils;
+
+})();
+
+module.exports = Utils;
diff --git a/lib/Yaml.js b/lib/Yaml.js
new file mode 100644
index 0000000..9505565
--- /dev/null
+++ b/lib/Yaml.js
@@ -0,0 +1,93 @@
+// Generated by CoffeeScript 1.12.4
+var Dumper, Parser, Utils, Yaml;
+
+Parser = require('./Parser');
+
+Dumper = require('./Dumper');
+
+Utils = require('./Utils');
+
+Yaml = (function() {
+  function Yaml() {}
+
+  Yaml.parse = function(input, exceptionOnInvalidType, objectDecoder) {
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    return new Parser().parse(input, exceptionOnInvalidType, objectDecoder);
+  };
+
+  Yaml.parseFile = function(path, callback, exceptionOnInvalidType, objectDecoder) {
+    var input;
+    if (callback == null) {
+      callback = null;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    if (callback != null) {
+      return Utils.getStringFromFile(path, (function(_this) {
+        return function(input) {
+          var result;
+          result = null;
+          if (input != null) {
+            result = _this.parse(input, exceptionOnInvalidType, objectDecoder);
+          }
+          callback(result);
+        };
+      })(this));
+    } else {
+      input = Utils.getStringFromFile(path);
+      if (input != null) {
+        return this.parse(input, exceptionOnInvalidType, objectDecoder);
+      }
+      return null;
+    }
+  };
+
+  Yaml.dump = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    var yaml;
+    if (inline == null) {
+      inline = 2;
+    }
+    if (indent == null) {
+      indent = 4;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    yaml = new Dumper();
+    yaml.indentation = indent;
+    return yaml.dump(input, inline, 0, exceptionOnInvalidType, objectEncoder);
+  };
+
+  Yaml.stringify = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    return this.dump(input, inline, indent, exceptionOnInvalidType, objectEncoder);
+  };
+
+  Yaml.load = function(path, callback, exceptionOnInvalidType, objectDecoder) {
+    return this.parseFile(path, callback, exceptionOnInvalidType, objectDecoder);
+  };
+
+  return Yaml;
+
+})();
+
+if (typeof window !== "undefined" && window !== null) {
+  window.YAML = Yaml;
+}
+
+if (typeof window === "undefined" || window === null) {
+  this.YAML = Yaml;
+}
+
+module.exports = Yaml;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2963d7f
--- /dev/null
+++ b/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "yamljs",
+  "version": "0.3.0",
+  "description": "Standalone JavaScript YAML 1.2 Parser & Encoder. Works under node.js and all major browsers. Also brings command line YAML/JSON conversion tools.",
+  "keywords": [
+    "yaml",
+    "json",
+    "yaml2json",
+    "json2yaml"
+  ],
+  "author": "Jeremy Faivre <contact at jeremyfa.com>",
+  "main": "./lib/Yaml.js",
+  "dependencies": {
+    "argparse": "^1.0.7",
+    "glob": "^7.0.5"
+  },
+  "devDependencies": {
+    "benchmark": "^2.1.0",
+    "coffeeify": "^2.0.1",
+    "jasmine-node": "^1.14.5"
+  },
+  "bin": {
+    "yaml2json": "./bin/yaml2json",
+    "json2yaml": "./bin/json2yaml"
+  },
+  "scripts": {
+    "test": "cake build; cake test"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jeremyfa/yaml.js.git"
+  }
+}
diff --git a/src/Dumper.coffee b/src/Dumper.coffee
new file mode 100644
index 0000000..0338212
--- /dev/null
+++ b/src/Dumper.coffee
@@ -0,0 +1,56 @@
+
+Utils   = require './Utils'
+Inline  = require './Inline'
+
+# Dumper dumps JavaScript variables to YAML strings.
+#
+class Dumper
+
+    # The amount of spaces to use for indentation of nested nodes.
+    @indentation:   4
+
+
+    # Dumps a JavaScript value to YAML.
+    #
+    # @param [Object]   input                   The JavaScript value
+    # @param [Integer]  inline                  The level where you switch to inline YAML
+    # @param [Integer]  indent                  The level of indentation (used internally)
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectEncoder           A function to serialize custom objects, null otherwise
+    #
+    # @return [String]  The YAML representation of the JavaScript value
+    #
+    dump: (input, inline = 0, indent = 0, exceptionOnInvalidType = false, objectEncoder = null) ->
+        output = ''
+        prefix = (if indent then Utils.strRepeat(' ', indent) else '')
+
+        if inline <= 0 or typeof(input) isnt 'object' or input instanceof Date or Utils.isEmpty(input)
+            output += prefix + Inline.dump(input, exceptionOnInvalidType, objectEncoder)
+        
+        else
+            if input instanceof Array
+                for value in input
+                    willBeInlined = (inline - 1 <= 0 or typeof(value) isnt 'object' or Utils.isEmpty(value))
+
+                    output +=
+                        prefix +
+                        '-' +
+                        (if willBeInlined then ' ' else "\n") +
+                        @dump(value, inline - 1, (if willBeInlined then 0 else indent + @indentation), exceptionOnInvalidType, objectEncoder) +
+                        (if willBeInlined then "\n" else '')
+
+            else
+                for key, value of input
+                    willBeInlined = (inline - 1 <= 0 or typeof(value) isnt 'object' or Utils.isEmpty(value))
+
+                    output +=
+                        prefix +
+                        Inline.dump(key, exceptionOnInvalidType, objectEncoder) + ':' +
+                        (if willBeInlined then ' ' else "\n") +
+                        @dump(value, inline - 1, (if willBeInlined then 0 else indent + @indentation), exceptionOnInvalidType, objectEncoder) +
+                        (if willBeInlined then "\n" else '')
+
+        return output
+
+
+module.exports = Dumper
diff --git a/src/Escaper.coffee b/src/Escaper.coffee
new file mode 100644
index 0000000..0eefec7
--- /dev/null
+++ b/src/Escaper.coffee
@@ -0,0 +1,80 @@
+
+Pattern = require './Pattern'
+
+# Escaper encapsulates escaping rules for single
+# and double-quoted YAML strings.
+class Escaper
+
+    # Mapping arrays for escaping a double quoted string. The backslash is
+    # first to ensure proper escaping.
+    @LIST_ESCAPEES:                 ['\\', '\\\\', '\\"', '"',
+                                     "\x00",  "\x01",  "\x02",  "\x03",  "\x04",  "\x05",  "\x06",  "\x07",
+                                     "\x08",  "\x09",  "\x0a",  "\x0b",  "\x0c",  "\x0d",  "\x0e",  "\x0f",
+                                     "\x10",  "\x11",  "\x12",  "\x13",  "\x14",  "\x15",  "\x16",  "\x17",
+                                     "\x18",  "\x19",  "\x1a",  "\x1b",  "\x1c",  "\x1d",  "\x1e",  "\x1f",
+                                     (ch = String.fromCharCode)(0x0085), ch(0x00A0), ch(0x2028), ch(0x2029)]
+    @LIST_ESCAPED:                  ['\\\\', '\\"', '\\"', '\\"',
+                                     "\\0",   "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
+                                     "\\b",   "\\t",   "\\n",   "\\v",   "\\f",   "\\r",   "\\x0e", "\\x0f",
+                                     "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
+                                     "\\x18", "\\x19", "\\x1a", "\\e",   "\\x1c", "\\x1d", "\\x1e", "\\x1f",
+                                     "\\N", "\\_", "\\L", "\\P"]
+
+    @MAPPING_ESCAPEES_TO_ESCAPED:   do =>
+        mapping = {}
+        for i in [0... at LIST_ESCAPEES.length]
+            mapping[@LIST_ESCAPEES[i]] = @LIST_ESCAPED[i]
+        return mapping
+
+    # Characters that would cause a dumped string to require double quoting.
+    @PATTERN_CHARACTERS_TO_ESCAPE:  new Pattern '[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9'
+
+    # Other precompiled patterns
+    @PATTERN_MAPPING_ESCAPEES:      new Pattern @LIST_ESCAPEES.join('|').split('\\').join('\\\\')
+    @PATTERN_SINGLE_QUOTING:        new Pattern '[\\s\'":{}[\\],&*#?]|^[-?|<>=!%@`]'
+
+
+
+    # Determines if a JavaScript value would require double quoting in YAML.
+    #
+    # @param [String]   value   A JavaScript value value
+    #
+    # @return [Boolean] true    if the value would require double quotes.
+    #
+    @requiresDoubleQuoting: (value) ->
+        return @PATTERN_CHARACTERS_TO_ESCAPE.test value
+
+
+    # Escapes and surrounds a JavaScript value with double quotes.
+    #
+    # @param [String]   value   A JavaScript value
+    #
+    # @return [String]  The quoted, escaped string
+    #
+    @escapeWithDoubleQuotes: (value) ->
+        result = @PATTERN_MAPPING_ESCAPEES.replace value, (str) =>
+            return @MAPPING_ESCAPEES_TO_ESCAPED[str]
+        return '"'+result+'"'
+
+
+    # Determines if a JavaScript value would require single quoting in YAML.
+    #
+    # @param [String]   value   A JavaScript value
+    #
+    # @return [Boolean] true if the value would require single quotes.
+    #
+    @requiresSingleQuoting: (value) ->
+        return @PATTERN_SINGLE_QUOTING.test value
+
+
+    # Escapes and surrounds a JavaScript value with single quotes.
+    #
+    # @param [String]   value   A JavaScript value
+    #
+    # @return [String]  The quoted, escaped string
+    #
+    @escapeWithSingleQuotes: (value) ->
+        return "'"+value.replace(/'/g, "''")+"'"
+
+
+module.exports = Escaper
diff --git a/src/Exception/DumpException.coffee b/src/Exception/DumpException.coffee
new file mode 100644
index 0000000..9cc6c27
--- /dev/null
+++ b/src/Exception/DumpException.coffee
@@ -0,0 +1,12 @@
+
+class DumpException extends Error
+
+    constructor: (@message, @parsedLine, @snippet) ->
+
+    toString: ->
+        if @parsedLine? and @snippet?
+            return '<DumpException> ' + @message + ' (line ' + @parsedLine + ': \'' + @snippet + '\')'
+        else
+            return '<DumpException> ' + @message
+
+module.exports = DumpException
diff --git a/src/Exception/ParseException.coffee b/src/Exception/ParseException.coffee
new file mode 100644
index 0000000..a6a0785
--- /dev/null
+++ b/src/Exception/ParseException.coffee
@@ -0,0 +1,12 @@
+
+class ParseException extends Error
+
+    constructor: (@message, @parsedLine, @snippet) ->
+
+    toString: ->
+        if @parsedLine? and @snippet?
+            return '<ParseException> ' + @message + ' (line ' + @parsedLine + ': \'' + @snippet + '\')'
+        else
+            return '<ParseException> ' + @message
+
+module.exports = ParseException
diff --git a/src/Exception/ParseMore.coffee b/src/Exception/ParseMore.coffee
new file mode 100644
index 0000000..faeb946
--- /dev/null
+++ b/src/Exception/ParseMore.coffee
@@ -0,0 +1,12 @@
+
+class ParseMore extends Error
+
+    constructor: (@message, @parsedLine, @snippet) ->
+
+    toString: ->
+        if @parsedLine? and @snippet?
+            return '<ParseMore> ' + @message + ' (line ' + @parsedLine + ': \'' + @snippet + '\')'
+        else
+            return '<ParseMore> ' + @message
+
+module.exports = ParseMore
diff --git a/src/Inline.coffee b/src/Inline.coffee
new file mode 100644
index 0000000..4620e3f
--- /dev/null
+++ b/src/Inline.coffee
@@ -0,0 +1,488 @@
+
+Pattern         = require './Pattern'
+Unescaper       = require './Unescaper'
+Escaper         = require './Escaper'
+Utils           = require './Utils'
+ParseException  = require './Exception/ParseException'
+ParseMore       = require './Exception/ParseMore'
+DumpException   = require './Exception/DumpException'
+
+# Inline YAML parsing and dumping
+class Inline
+
+    # Quoted string regular expression
+    @REGEX_QUOTED_STRING:               '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')'
+
+    # Pre-compiled patterns
+    #
+    @PATTERN_TRAILING_COMMENTS:         new Pattern '^\\s*#.*$'
+    @PATTERN_QUOTED_SCALAR:             new Pattern '^'+ at REGEX_QUOTED_STRING
+    @PATTERN_THOUSAND_NUMERIC_SCALAR:   new Pattern '^(-|\\+)?[0-9,]+(\\.[0-9]+)?$'
+    @PATTERN_SCALAR_BY_DELIMITERS:      {}
+
+    # Settings
+    @settings: {}
+
+
+    # Configure YAML inline.
+    #
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    @configure: (exceptionOnInvalidType = null, objectDecoder = null) ->
+        # Update settings
+        @settings.exceptionOnInvalidType = exceptionOnInvalidType
+        @settings.objectDecoder = objectDecoder
+        return
+
+
+    # Converts a YAML string to a JavaScript object.
+    #
+    # @param [String]   value                   A YAML string
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object]  A JavaScript object representing the YAML string
+    #
+    # @throw [ParseException]
+    #
+    @parse: (value, exceptionOnInvalidType = false, objectDecoder = null) ->
+        # Update settings from last call of Inline.parse()
+        @settings.exceptionOnInvalidType = exceptionOnInvalidType
+        @settings.objectDecoder = objectDecoder
+
+        if not value?
+            return ''
+
+        value = Utils.trim value
+
+        if 0 is value.length
+            return ''
+
+        # Keep a context object to pass through static methods
+        context = {exceptionOnInvalidType, objectDecoder, i: 0}
+
+        switch value.charAt(0)
+            when '['
+                result = @parseSequence value, context
+                ++context.i
+            when '{'
+                result = @parseMapping value, context
+                ++context.i
+            else
+                result = @parseScalar value, null, ['"', "'"], context
+
+        # Some comments are allowed at the end
+        if @PATTERN_TRAILING_COMMENTS.replace(value[context.i..], '') isnt ''
+            throw new ParseException 'Unexpected characters near "'+value[context.i..]+'".'
+
+        return result
+
+
+    # Dumps a given JavaScript variable to a YAML string.
+    #
+    # @param [Object]   value                   The JavaScript variable to convert
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectEncoder           A function to serialize custom objects, null otherwise
+    #
+    # @return [String]  The YAML string representing the JavaScript object
+    #
+    # @throw [DumpException]
+    #
+    @dump: (value, exceptionOnInvalidType = false, objectEncoder = null) ->
+        if not value?
+            return 'null'
+        type = typeof value
+        if type is 'object'
+            if value instanceof Date
+                return value.toISOString()
+            else if objectEncoder?
+                result = objectEncoder value
+                if typeof result is 'string' or result?
+                    return result
+            return @dumpObject value
+        if type is 'boolean'
+            return (if value then 'true' else 'false')
+        if Utils.isDigits(value)
+            return (if type is 'string' then "'"+value+"'" else String(parseInt(value)))
+        if Utils.isNumeric(value)
+            return (if type is 'string' then "'"+value+"'" else String(parseFloat(value)))
+        if type is 'number'
+            return (if value is Infinity then '.Inf' else (if value is -Infinity then '-.Inf' else (if isNaN(value) then '.NaN' else value)))
+        if Escaper.requiresDoubleQuoting value
+            return Escaper.escapeWithDoubleQuotes value
+        if Escaper.requiresSingleQuoting value
+            return Escaper.escapeWithSingleQuotes value
+        if '' is value
+            return '""'
+        if Utils.PATTERN_DATE.test value
+            return "'"+value+"'";
+        if value.toLowerCase() in ['null','~','true','false']
+            return "'"+value+"'"
+        # Default
+        return value;
+
+
+    # Dumps a JavaScript object to a YAML string.
+    #
+    # @param [Object]   value                   The JavaScript object to dump
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectEncoder           A function do serialize custom objects, null otherwise
+    #
+    # @return string The YAML string representing the JavaScript object
+    #
+    @dumpObject: (value, exceptionOnInvalidType, objectSupport = null) ->
+        # Array
+        if value instanceof Array
+            output = []
+            for val in value
+                output.push @dump val
+            return '['+output.join(', ')+']'
+
+        # Mapping
+        else
+            output = []
+            for key, val of value
+                output.push @dump(key)+': '+ at dump(val)
+            return '{'+output.join(', ')+'}'
+
+
+    # Parses a scalar to a YAML string.
+    #
+    # @param [Object]   scalar
+    # @param [Array]    delimiters
+    # @param [Array]    stringDelimiters
+    # @param [Object]   context
+    # @param [Boolean]  evaluate
+    #
+    # @return [String]  A YAML string
+    #
+    # @throw [ParseException] When malformed inline YAML string is parsed
+    #
+    @parseScalar: (scalar, delimiters = null, stringDelimiters = ['"', "'"], context = null, evaluate = true) ->
+        unless context?
+            context = exceptionOnInvalidType: @settings.exceptionOnInvalidType, objectDecoder: @settings.objectDecoder, i: 0
+        {i} = context
+
+        if scalar.charAt(i) in stringDelimiters
+            # Quoted scalar
+            output = @parseQuotedScalar scalar, context
+            {i} = context
+
+            if delimiters?
+                tmp = Utils.ltrim scalar[i..], ' '
+                if not(tmp.charAt(0) in delimiters)
+                    throw new ParseException 'Unexpected characters ('+scalar[i..]+').'
+
+        else
+            # "normal" string
+            if not delimiters
+                output = scalar[i..]
+                i += output.length
+
+                # Remove comments
+                strpos = output.indexOf ' #'
+                if strpos isnt -1
+                    output = Utils.rtrim output[0...strpos]
+
+            else
+                joinedDelimiters = delimiters.join('|')
+                pattern = @PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters]
+                unless pattern?
+                    pattern = new Pattern '^(.+?)('+joinedDelimiters+')'
+                    @PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] = pattern
+                if match = pattern.exec scalar[i..]
+                    output = match[1]
+                    i += output.length
+                else
+                    throw new ParseException 'Malformed inline YAML string ('+scalar+').'
+
+
+            if evaluate
+                output = @evaluateScalar output, context
+
+        context.i = i
+        return output
+
+
+    # Parses a quoted scalar to YAML.
+    #
+    # @param [String]   scalar
+    # @param [Object]   context
+    #
+    # @return [String]  A YAML string
+    #
+    # @throw [ParseMore] When malformed inline YAML string is parsed
+    #
+    @parseQuotedScalar: (scalar, context) ->
+        {i} = context
+
+        unless match = @PATTERN_QUOTED_SCALAR.exec scalar[i..]
+            throw new ParseMore 'Malformed inline YAML string ('+scalar[i..]+').'
+
+        output = match[0].substr(1, match[0].length - 2)
+
+        if '"' is scalar.charAt(i)
+            output = Unescaper.unescapeDoubleQuotedString output
+        else
+            output = Unescaper.unescapeSingleQuotedString output
+
+        i += match[0].length
+
+        context.i = i
+        return output
+
+
+    # Parses a sequence to a YAML string.
+    #
+    # @param [String]   sequence
+    # @param [Object]   context
+    #
+    # @return [String]  A YAML string
+    #
+    # @throw [ParseMore] When malformed inline YAML string is parsed
+    #
+    @parseSequence: (sequence, context) ->
+        output = []
+        len = sequence.length
+        {i} = context
+        i += 1
+
+        # [foo, bar, ...]
+        while i < len
+            context.i = i
+            switch sequence.charAt(i)
+                when '['
+                    # Nested sequence
+                    output.push @parseSequence sequence, context
+                    {i} = context
+                when '{'
+                    # Nested mapping
+                    output.push @parseMapping sequence, context
+                    {i} = context
+                when ']'
+                    return output
+                when ',', ' ', "\n"
+                    # Do nothing
+                else
+                    isQuoted = (sequence.charAt(i) in ['"', "'"])
+                    value = @parseScalar sequence, [',', ']'], ['"', "'"], context
+                    {i} = context
+
+                    if not(isQuoted) and typeof(value) is 'string' and (value.indexOf(': ') isnt -1 or value.indexOf(":\n") isnt -1)
+                        # Embedded mapping?
+                        try
+                            value = @parseMapping '{'+value+'}'
+                        catch e
+                            # No, it's not
+
+
+                    output.push value
+
+                    --i
+
+            ++i
+
+        throw new ParseMore 'Malformed inline YAML string '+sequence
+
+
+    # Parses a mapping to a YAML string.
+    #
+    # @param [String]   mapping
+    # @param [Object]   context
+    #
+    # @return [String]  A YAML string
+    #
+    # @throw [ParseMore] When malformed inline YAML string is parsed
+    #
+    @parseMapping: (mapping, context) ->
+        output = {}
+        len = mapping.length
+        {i} = context
+        i += 1
+
+        # {foo: bar, bar:foo, ...}
+        shouldContinueWhileLoop = false
+        while i < len
+            context.i = i
+            switch mapping.charAt(i)
+                when ' ', ',', "\n"
+                    ++i
+                    context.i = i
+                    shouldContinueWhileLoop = true
+                when '}'
+                    return output
+
+            if shouldContinueWhileLoop
+                shouldContinueWhileLoop = false
+                continue
+
+            # Key
+            key = @parseScalar mapping, [':', ' ', "\n"], ['"', "'"], context, false
+            {i} = context
+
+            # Value
+            done = false
+
+            while i < len
+                context.i = i
+                switch mapping.charAt(i)
+                    when '['
+                        # Nested sequence
+                        value = @parseSequence mapping, context
+                        {i} = context
+                        # Spec: Keys MUST be unique; first one wins.
+                        # Parser cannot abort this mapping earlier, since lines
+                        # are processed sequentially.
+                        if output[key] == undefined
+                            output[key] = value
+                        done = true
+                    when '{'
+                        # Nested mapping
+                        value = @parseMapping mapping, context
+                        {i} = context
+                        # Spec: Keys MUST be unique; first one wins.
+                        # Parser cannot abort this mapping earlier, since lines
+                        # are processed sequentially.
+                        if output[key] == undefined
+                            output[key] = value
+                        done = true
+                    when ':', ' ', "\n"
+                        # Do nothing
+                    else
+                        value = @parseScalar mapping, [',', '}'], ['"', "'"], context
+                        {i} = context
+                        # Spec: Keys MUST be unique; first one wins.
+                        # Parser cannot abort this mapping earlier, since lines
+                        # are processed sequentially.
+                        if output[key] == undefined
+                            output[key] = value
+                        done = true
+                        --i
+
+                ++i
+
+                if done
+                    break
+
+        throw new ParseMore 'Malformed inline YAML string '+mapping
+
+
+    # Evaluates scalars and replaces magic values.
+    #
+    # @param [String]   scalar
+    #
+    # @return [String]  A YAML string
+    #
+    @evaluateScalar: (scalar, context) ->
+        scalar = Utils.trim(scalar)
+        scalarLower = scalar.toLowerCase()
+
+        switch scalarLower
+            when 'null', '', '~'
+                return null
+            when 'true'
+                return true
+            when 'false'
+                return false
+            when '.inf'
+                return Infinity
+            when '.nan'
+                return NaN
+            when '-.inf'
+                return Infinity
+            else
+                firstChar = scalarLower.charAt(0)
+                switch firstChar
+                    when '!'
+                        firstSpace = scalar.indexOf(' ')
+                        if firstSpace is -1
+                            firstWord = scalarLower
+                        else
+                            firstWord = scalarLower[0...firstSpace]
+                        switch firstWord
+                            when '!'
+                                if firstSpace isnt -1
+                                    return parseInt @parseScalar(scalar[2..])
+                                return null
+                            when '!str'
+                                return Utils.ltrim scalar[4..]
+                            when '!!str'
+                                return Utils.ltrim scalar[5..]
+                            when '!!int'
+                                return parseInt(@parseScalar(scalar[5..]))
+                            when '!!bool'
+                                return Utils.parseBoolean(@parseScalar(scalar[6..]), false)
+                            when '!!float'
+                                return parseFloat(@parseScalar(scalar[7..]))
+                            when '!!timestamp'
+                                return Utils.stringToDate(Utils.ltrim(scalar[11..]))
+                            else
+                                unless context?
+                                    context = exceptionOnInvalidType: @settings.exceptionOnInvalidType, objectDecoder: @settings.objectDecoder, i: 0
+                                {objectDecoder, exceptionOnInvalidType} = context
+
+                                if objectDecoder
+                                    # If objectDecoder function is given, we can do custom decoding of custom types
+                                    trimmedScalar = Utils.rtrim scalar
+                                    firstSpace = trimmedScalar.indexOf(' ')
+                                    if firstSpace is -1
+                                        return objectDecoder trimmedScalar, null
+                                    else
+                                        subValue = Utils.ltrim trimmedScalar[firstSpace+1..]
+                                        unless subValue.length > 0
+                                            subValue = null
+                                        return objectDecoder trimmedScalar[0...firstSpace], subValue
+
+                                if exceptionOnInvalidType
+                                    throw new ParseException 'Custom object support when parsing a YAML file has been disabled.'
+
+                                return null
+                    when '0'
+                        if '0x' is scalar[0...2]
+                            return Utils.hexDec scalar
+                        else if Utils.isDigits scalar
+                            return Utils.octDec scalar
+                        else if Utils.isNumeric scalar
+                            return parseFloat scalar
+                        else
+                            return scalar
+                    when '+'
+                        if Utils.isDigits scalar
+                            raw = scalar
+                            cast = parseInt(raw)
+                            if raw is String(cast)
+                                return cast
+                            else
+                                return raw
+                        else if Utils.isNumeric scalar
+                            return parseFloat scalar
+                        else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
+                            return parseFloat(scalar.replace(',', ''))
+                        return scalar
+                    when '-'
+                        if Utils.isDigits(scalar[1..])
+                            if '0' is scalar.charAt(1)
+                                return -Utils.octDec(scalar[1..])
+                            else
+                                raw = scalar[1..]
+                                cast = parseInt(raw)
+                                if raw is String(cast)
+                                    return -cast
+                                else
+                                    return -raw
+                        else if Utils.isNumeric scalar
+                            return parseFloat scalar
+                        else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
+                            return parseFloat(scalar.replace(',', ''))
+                        return scalar
+                    else
+                        if date = Utils.stringToDate(scalar)
+                            return date
+                        else if Utils.isNumeric(scalar)
+                            return parseFloat scalar
+                        else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
+                            return parseFloat(scalar.replace(',', ''))
+                        return scalar
+
+module.exports = Inline
diff --git a/src/Parser.coffee b/src/Parser.coffee
new file mode 100644
index 0000000..812d23f
--- /dev/null
+++ b/src/Parser.coffee
@@ -0,0 +1,651 @@
+
+Inline          = require './Inline'
+Pattern         = require './Pattern'
+Utils           = require './Utils'
+ParseException  = require './Exception/ParseException'
+ParseMore       = require './Exception/ParseMore'
+
+# Parser parses YAML strings to convert them to JavaScript objects.
+#
+class Parser
+
+    # Pre-compiled patterns
+    #
+    PATTERN_FOLDED_SCALAR_ALL:              new Pattern '^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$'
+    PATTERN_FOLDED_SCALAR_END:              new Pattern '(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$'
+    PATTERN_SEQUENCE_ITEM:                  new Pattern '^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$'
+    PATTERN_ANCHOR_VALUE:                   new Pattern '^&(?<ref>[^ ]+) *(?<value>.*)'
+    PATTERN_COMPACT_NOTATION:               new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$'
+    PATTERN_MAPPING_ITEM:                   new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$'
+    PATTERN_DECIMAL:                        new Pattern '\\d+'
+    PATTERN_INDENT_SPACES:                  new Pattern '^ +'
+    PATTERN_TRAILING_LINES:                 new Pattern '(\n*)$'
+    PATTERN_YAML_HEADER:                    new Pattern '^\\%YAML[: ][\\d\\.]+.*\n', 'm'
+    PATTERN_LEADING_COMMENTS:               new Pattern '^(\\#.*?\n)+', 'm'
+    PATTERN_DOCUMENT_MARKER_START:          new Pattern '^\\-\\-\\-.*?\n', 'm'
+    PATTERN_DOCUMENT_MARKER_END:            new Pattern '^\\.\\.\\.\\s*$', 'm'
+    PATTERN_FOLDED_SCALAR_BY_INDENTATION:   {}
+
+    # Context types
+    #
+    CONTEXT_NONE:       0
+    CONTEXT_SEQUENCE:   1
+    CONTEXT_MAPPING:    2
+
+
+    # Constructor
+    #
+    # @param [Integer]  offset  The offset of YAML document (used for line numbers in error messages)
+    #
+    constructor: (@offset = 0) ->
+        @lines          = []
+        @currentLineNb  = -1
+        @currentLine    = ''
+        @refs           = {}
+
+
+    # Parses a YAML string to a JavaScript value.
+    #
+    # @param [String]   value                   A YAML string
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object]  A JavaScript value
+    #
+    # @throw [ParseException] If the YAML is not valid
+    #
+    parse: (value, exceptionOnInvalidType = false, objectDecoder = null) ->
+        @currentLineNb = -1
+        @currentLine = ''
+        @lines = @cleanup(value).split "\n"
+
+        data = null
+        context = @CONTEXT_NONE
+        allowOverwrite = false
+        while @moveToNextLine()
+            if @isCurrentLineEmpty()
+                continue
+
+            # Tab?
+            if "\t" is @currentLine[0]
+                throw new ParseException 'A YAML file cannot contain tabs as indentation.', @getRealCurrentLineNb() + 1, @currentLine
+
+            isRef = mergeNode = false
+            if values = @PATTERN_SEQUENCE_ITEM.exec @currentLine
+                if @CONTEXT_MAPPING is context
+                    throw new ParseException 'You cannot define a sequence item when in a mapping'
+                context = @CONTEXT_SEQUENCE
+                data ?= []
+
+                if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value
+                    isRef = matches.ref
+                    values.value = matches.value
+
+                # Array
+                if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0
+                    if @currentLineNb < @lines.length - 1 and not @isNextLineUnIndentedCollection()
+                        c = @getRealCurrentLineNb() + 1
+                        parser = new Parser c
+                        parser.refs = @refs
+                        data.push parser.parse(@getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder)
+                    else
+                        data.push null
+
+                else
+                    if values.leadspaces?.length and matches = @PATTERN_COMPACT_NOTATION.exec values.value
+
+                        # This is a compact notation element, add to next block and parse
+                        c = @getRealCurrentLineNb()
+                        parser = new Parser c
+                        parser.refs = @refs
+
+                        block = values.value
+                        indent = @getCurrentLineIndentation()
+                        if @isNextLineIndented(false)
+                            block += "\n"+ at getNextEmbedBlock(indent + values.leadspaces.length + 1, true)
+
+                        data.push parser.parse block, exceptionOnInvalidType, objectDecoder
+
+                    else
+                        data.push @parseValue values.value, exceptionOnInvalidType, objectDecoder
+
+            else if (values = @PATTERN_MAPPING_ITEM.exec @currentLine) and values.key.indexOf(' #') is -1
+                if @CONTEXT_SEQUENCE is context
+                    throw new ParseException 'You cannot define a mapping item when in a sequence'
+                context = @CONTEXT_MAPPING
+                data ?= {}
+
+                # Force correct settings
+                Inline.configure exceptionOnInvalidType, objectDecoder
+                try
+                    key = Inline.parseScalar values.key
+                catch e
+                    e.parsedLine = @getRealCurrentLineNb() + 1
+                    e.snippet = @currentLine
+
+                    throw e
+
+                if '<<' is key
+                    mergeNode = true
+                    allowOverwrite = true
+                    if values.value?.indexOf('*') is 0
+                        refName = values.value[1..]
+                        unless @refs[refName]?
+                            throw new ParseException 'Reference "'+refName+'" does not exist.', @getRealCurrentLineNb() + 1, @currentLine
+
+                        refValue = @refs[refName]
+
+                        if typeof refValue isnt 'object'
+                            throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine
+
+                        if refValue instanceof Array
+                            # Merge array with object
+                            for value, i in refValue
+                                data[String(i)] ?= value
+                        else
+                            # Merge objects
+                            for key, value of refValue
+                                data[key] ?= value
+
+                    else
+                        if values.value? and values.value isnt ''
+                            value = values.value
+                        else
+                            value = @getNextEmbedBlock()
+
+                        c = @getRealCurrentLineNb() + 1
+                        parser = new Parser c
+                        parser.refs = @refs
+                        parsed = parser.parse value, exceptionOnInvalidType
+
+                        unless typeof parsed is 'object'
+                            throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine
+
+                        if parsed instanceof Array
+                            # If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
+                            # and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
+                            # in the sequence override keys specified in later mapping nodes.
+                            for parsedItem in parsed
+                                unless typeof parsedItem is 'object'
+                                    throw new ParseException 'Merge items must be objects.', @getRealCurrentLineNb() + 1, parsedItem
+
+                                if parsedItem instanceof Array
+                                    # Merge array with object
+                                    for value, i in parsedItem
+                                        k = String(i)
+                                        unless data.hasOwnProperty(k)
+                                            data[k] = value
+                                else
+                                    # Merge objects
+                                    for key, value of parsedItem
+                                        unless data.hasOwnProperty(key)
+                                            data[key] = value
+
+                        else
+                            # If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
+                            # current mapping, unless the key already exists in it.
+                            for key, value of parsed
+                                unless data.hasOwnProperty(key)
+                                    data[key] = value
+
+                else if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value
+                    isRef = matches.ref
+                    values.value = matches.value
+
+
+                if mergeNode
+                    # Merge keys
+                else if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0
+                    # Hash
+                    # if next line is less indented or equal, then it means that the current value is null
+                    if not(@isNextLineIndented()) and not(@isNextLineUnIndentedCollection())
+                        # Spec: Keys MUST be unique; first one wins.
+                        # But overwriting is allowed when a merge node is used in current block.
+                        if allowOverwrite or data[key] is undefined
+                            data[key] = null
+
+                    else
+                        c = @getRealCurrentLineNb() + 1
+                        parser = new Parser c
+                        parser.refs = @refs
+                        val = parser.parse @getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder
+
+                        # Spec: Keys MUST be unique; first one wins.
+                        # But overwriting is allowed when a merge node is used in current block.
+                        if allowOverwrite or data[key] is undefined
+                            data[key] = val
+
+                else
+                    val = @parseValue values.value, exceptionOnInvalidType, objectDecoder
+
+                    # Spec: Keys MUST be unique; first one wins.
+                    # But overwriting is allowed when a merge node is used in current block.
+                    if allowOverwrite or data[key] is undefined
+                        data[key] = val
+
+            else
+                # 1-liner optionally followed by newline
+                lineCount = @lines.length
+                if 1 is lineCount or (2 is lineCount and Utils.isEmpty(@lines[1]))
+                    try
+                        value = Inline.parse @lines[0], exceptionOnInvalidType, objectDecoder
+                    catch e
+                        e.parsedLine = @getRealCurrentLineNb() + 1
+                        e.snippet = @currentLine
+
+                        throw e
+
+                    if typeof value is 'object'
+                        if value instanceof Array
+                            first = value[0]
+                        else
+                            for key of value
+                                first = value[key]
+                                break
+
+                        if typeof first is 'string' and first.indexOf('*') is 0
+                            data = []
+                            for alias in value
+                                data.push @refs[alias[1..]]
+                            value = data
+
+                    return value
+
+                else if Utils.ltrim(value).charAt(0) in ['[', '{']
+                    try
+                        return Inline.parse value, exceptionOnInvalidType, objectDecoder
+                    catch e
+                        e.parsedLine = @getRealCurrentLineNb() + 1
+                        e.snippet = @currentLine
+
+                        throw e
+
+                throw new ParseException 'Unable to parse.', @getRealCurrentLineNb() + 1, @currentLine
+
+            if isRef
+                if data instanceof Array
+                    @refs[isRef] = data[data.length-1]
+                else
+                    lastKey = null
+                    for key of data
+                        lastKey = key
+                    @refs[isRef] = data[lastKey]
+
+
+        if Utils.isEmpty(data)
+            return null
+        else
+            return data
+
+
+
+    # Returns the current line number (takes the offset into account).
+    #
+    # @return [Integer]     The current line number
+    #
+    getRealCurrentLineNb: ->
+        return @currentLineNb + @offset
+
+
+    # Returns the current line indentation.
+    #
+    # @return [Integer]     The current line indentation
+    #
+    getCurrentLineIndentation: ->
+        return @currentLine.length - Utils.ltrim(@currentLine, ' ').length
+
+
+    # Returns the next embed block of YAML.
+    #
+    # @param [Integer]          indentation The indent level at which the block is to be read, or null for default
+    #
+    # @return [String]          A YAML string
+    #
+    # @throw [ParseException]   When indentation problem are detected
+    #
+    getNextEmbedBlock: (indentation = null, includeUnindentedCollection = false) ->
+        @moveToNextLine()
+
+        if not indentation?
+            newIndent = @getCurrentLineIndentation()
+
+            unindentedEmbedBlock = @isStringUnIndentedCollectionItem @currentLine
+
+            if not(@isCurrentLineEmpty()) and 0 is newIndent and not(unindentedEmbedBlock)
+                throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine
+
+        else
+            newIndent = indentation
+
+
+        data = [@currentLine[newIndent..]]
+
+        unless includeUnindentedCollection
+            isItUnindentedCollection = @isStringUnIndentedCollectionItem @currentLine
+
+        # Comments must not be removed inside a string block (ie. after a line ending with "|")
+        # They must not be removed inside a sub-embedded block as well
+        removeCommentsPattern = @PATTERN_FOLDED_SCALAR_END
+        removeComments = not removeCommentsPattern.test @currentLine
+
+        while @moveToNextLine()
+            indent = @getCurrentLineIndentation()
+
+            if indent is newIndent
+                removeComments = not removeCommentsPattern.test @currentLine
+
+            if removeComments and @isCurrentLineComment()
+                continue
+
+            if @isCurrentLineBlank()
+                data.push @currentLine[newIndent..]
+                continue
+
+            if isItUnindentedCollection and not @isStringUnIndentedCollectionItem(@currentLine) and indent is newIndent
+                @moveToPreviousLine()
+                break
+
+            if indent >= newIndent
+                data.push @currentLine[newIndent..]
+            else if Utils.ltrim(@currentLine).charAt(0) is '#'
+                # Don't add line with comments
+            else if 0 is indent
+                @moveToPreviousLine()
+                break
+            else
+                throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine
+
+
+        return data.join "\n"
+
+
+    # Moves the parser to the next line.
+    #
+    # @return [Boolean]
+    #
+    moveToNextLine: ->
+        if @currentLineNb >= @lines.length - 1
+            return false
+
+        @currentLine = @lines[++ at currentLineNb];
+
+        return true
+
+
+    # Moves the parser to the previous line.
+    #
+    moveToPreviousLine: ->
+        @currentLine = @lines[-- at currentLineNb]
+        return
+
+
+    # Parses a YAML value.
+    #
+    # @param [String]   value                   A YAML value
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object] A JavaScript value
+    #
+    # @throw [ParseException] When reference does not exist
+    #
+    parseValue: (value, exceptionOnInvalidType, objectDecoder) ->
+        if 0 is value.indexOf('*')
+            pos = value.indexOf '#'
+            if pos isnt -1
+                value = value.substr(1, pos-2)
+            else
+                value = value[1..]
+
+            if @refs[value] is undefined
+                throw new ParseException 'Reference "'+value+'" does not exist.', @currentLine
+
+            return @refs[value]
+
+
+        if matches = @PATTERN_FOLDED_SCALAR_ALL.exec value
+            modifiers = matches.modifiers ? ''
+
+            foldedIndent = Math.abs(parseInt(modifiers))
+            if isNaN(foldedIndent) then foldedIndent = 0
+            val = @parseFoldedScalar matches.separator, @PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent
+            if matches.type?
+                # Force correct settings
+                Inline.configure exceptionOnInvalidType, objectDecoder
+                return Inline.parseScalar matches.type+' '+val
+            else
+                return val
+
+        # Value can be multiline compact sequence or mapping or string
+        if value.charAt(0) in ['[', '{', '"', "'"]
+            while true
+                try
+                    return Inline.parse value, exceptionOnInvalidType, objectDecoder
+                catch e
+                    if e instanceof ParseMore and @moveToNextLine()
+                        value += "\n" + Utils.trim(@currentLine, ' ')
+                    else
+                        e.parsedLine = @getRealCurrentLineNb() + 1
+                        e.snippet = @currentLine
+                        throw e
+        else
+            if @isNextLineIndented()
+                value += "\n" + @getNextEmbedBlock()
+            return Inline.parse value, exceptionOnInvalidType, objectDecoder
+
+        return
+
+
+    # Parses a folded scalar.
+    #
+    # @param [String]       separator   The separator that was used to begin this folded scalar (| or >)
+    # @param [String]       indicator   The indicator that was used to begin this folded scalar (+ or -)
+    # @param [Integer]      indentation The indentation that was used to begin this folded scalar
+    #
+    # @return [String]      The text value
+    #
+    parseFoldedScalar: (separator, indicator = '', indentation = 0) ->
+        notEOF = @moveToNextLine()
+        if not notEOF
+            return ''
+
+        isCurrentLineBlank = @isCurrentLineBlank()
+        text = ''
+
+        # Leading blank lines are consumed before determining indentation
+        while notEOF and isCurrentLineBlank
+            # newline only if not EOF
+            if notEOF = @moveToNextLine()
+                text += "\n"
+                isCurrentLineBlank = @isCurrentLineBlank()
+
+
+        # Determine indentation if not specified
+        if 0 is indentation
+            if matches = @PATTERN_INDENT_SPACES.exec @currentLine
+                indentation = matches[0].length
+
+
+        if indentation > 0
+            pattern = @PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation]
+            unless pattern?
+                pattern = new Pattern '^ {'+indentation+'}(.*)$'
+                Parser::PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern
+
+            while notEOF and (isCurrentLineBlank or matches = pattern.exec @currentLine)
+                if isCurrentLineBlank
+                    text += @currentLine[indentation..]
+                else
+                    text += matches[1]
+
+                # newline only if not EOF
+                if notEOF = @moveToNextLine()
+                    text += "\n"
+                    isCurrentLineBlank = @isCurrentLineBlank()
+
+        else if notEOF
+            text += "\n"
+
+
+        if notEOF
+            @moveToPreviousLine()
+
+
+        # Remove line breaks of each lines except the empty and more indented ones
+        if '>' is separator
+            newText = ''
+            for line in text.split "\n"
+                if line.length is 0 or line.charAt(0) is ' '
+                    newText = Utils.rtrim(newText, ' ') + line + "\n"
+                else
+                    newText += line + ' '
+            text = newText
+
+        if '+' isnt indicator
+            # Remove any extra space or new line as we are adding them after
+            text = Utils.rtrim(text)
+
+        # Deal with trailing newlines as indicated
+        if '' is indicator
+            text = @PATTERN_TRAILING_LINES.replace text, "\n"
+        else if '-' is indicator
+            text = @PATTERN_TRAILING_LINES.replace text, ''
+
+        return text
+
+
+    # Returns true if the next line is indented.
+    #
+    # @return [Boolean]     Returns true if the next line is indented, false otherwise
+    #
+    isNextLineIndented: (ignoreComments = true) ->
+        currentIndentation = @getCurrentLineIndentation()
+        EOF = not @moveToNextLine()
+
+        if ignoreComments
+            while not(EOF) and @isCurrentLineEmpty()
+                EOF = not @moveToNextLine()
+        else
+            while not(EOF) and @isCurrentLineBlank()
+                EOF = not @moveToNextLine()
+
+        if EOF
+            return false
+
+        ret = false
+        if @getCurrentLineIndentation() > currentIndentation
+            ret = true
+
+        @moveToPreviousLine()
+
+        return ret
+
+
+    # Returns true if the current line is blank or if it is a comment line.
+    #
+    # @return [Boolean]     Returns true if the current line is empty or if it is a comment line, false otherwise
+    #
+    isCurrentLineEmpty: ->
+        trimmedLine = Utils.trim(@currentLine, ' ')
+        return trimmedLine.length is 0 or trimmedLine.charAt(0) is '#'
+
+
+    # Returns true if the current line is blank.
+    #
+    # @return [Boolean]     Returns true if the current line is blank, false otherwise
+    #
+    isCurrentLineBlank: ->
+        return '' is Utils.trim(@currentLine, ' ')
+
+
+    # Returns true if the current line is a comment line.
+    #
+    # @return [Boolean]     Returns true if the current line is a comment line, false otherwise
+    #
+    isCurrentLineComment: ->
+        # Checking explicitly the first char of the trim is faster than loops or strpos
+        ltrimmedLine = Utils.ltrim(@currentLine, ' ')
+
+        return ltrimmedLine.charAt(0) is '#'
+
+
+    # Cleanups a YAML string to be parsed.
+    #
+    # @param [String]   value The input YAML string
+    #
+    # @return [String]  A cleaned up YAML string
+    #
+    cleanup: (value) ->
+        if value.indexOf("\r") isnt -1
+            value = value.split("\r\n").join("\n").split("\r").join("\n")
+
+        # Strip YAML header
+        count = 0
+        [value, count] = @PATTERN_YAML_HEADER.replaceAll value, ''
+        @offset += count
+
+        # Remove leading comments
+        [trimmedValue, count] = @PATTERN_LEADING_COMMENTS.replaceAll value, '', 1
+        if count is 1
+            # Items have been removed, update the offset
+            @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n")
+            value = trimmedValue
+
+        # Remove start of the document marker (---)
+        [trimmedValue, count] = @PATTERN_DOCUMENT_MARKER_START.replaceAll value, '', 1
+        if count is 1
+            # Items have been removed, update the offset
+            @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n")
+            value = trimmedValue
+
+            # Remove end of the document marker (...)
+            value = @PATTERN_DOCUMENT_MARKER_END.replace value, ''
+
+        # Ensure the block is not indented
+        lines = value.split("\n")
+        smallestIndent = -1
+        for line in lines
+            continue if Utils.trim(line, ' ').length == 0
+            indent = line.length - Utils.ltrim(line).length
+            if smallestIndent is -1 or indent < smallestIndent
+                smallestIndent = indent
+        if smallestIndent > 0
+            for line, i in lines
+                lines[i] = line[smallestIndent..]
+            value = lines.join("\n")
+
+        return value
+
+
+    # Returns true if the next line starts unindented collection
+    #
+    # @return [Boolean]     Returns true if the next line starts unindented collection, false otherwise
+    #
+    isNextLineUnIndentedCollection: (currentIndentation = null) ->
+        currentIndentation ?= @getCurrentLineIndentation()
+        notEOF = @moveToNextLine()
+
+        while notEOF and @isCurrentLineEmpty()
+            notEOF = @moveToNextLine()
+
+        if false is notEOF
+            return false
+
+        ret = false
+        if @getCurrentLineIndentation() is currentIndentation and @isStringUnIndentedCollectionItem(@currentLine)
+            ret = true
+
+        @moveToPreviousLine()
+
+        return ret
+
+
+    # Returns true if the string is un-indented collection item
+    #
+    # @return [Boolean]     Returns true if the string is un-indented collection item, false otherwise
+    #
+    isStringUnIndentedCollectionItem: ->
+        return @currentLine is '-' or @currentLine[0...2] is '- '
+
+
+module.exports = Parser
diff --git a/src/Pattern.coffee b/src/Pattern.coffee
new file mode 100644
index 0000000..82f96e7
--- /dev/null
+++ b/src/Pattern.coffee
@@ -0,0 +1,144 @@
+
+# Pattern is a zero-conflict wrapper extending RegExp features
+# in order to make YAML parsing regex more expressive.
+#
+class Pattern
+
+    # @property [RegExp] The RegExp instance
+    regex:          null
+
+    # @property [String] The raw regex string
+    rawRegex:       null
+
+    # @property [String] The cleaned regex string (used to create the RegExp instance)
+    cleanedRegex:   null
+
+    # @property [Object] The dictionary mapping names to capturing bracket numbers
+    mapping:        null
+
+    # Constructor
+    #
+    # @param [String] rawRegex The raw regex string defining the pattern
+    #
+    constructor: (rawRegex, modifiers = '') ->
+        cleanedRegex = ''
+        len = rawRegex.length
+        mapping = null
+
+        # Cleanup raw regex and compute mapping
+        capturingBracketNumber = 0
+        i = 0
+        while i < len
+            _char = rawRegex.charAt(i)
+            if _char is '\\'
+                # Ignore next character
+                cleanedRegex += rawRegex[i..i+1]
+                i++
+            else if _char is '('
+                # Increase bracket number, only if it is capturing
+                if i < len - 2
+                    part = rawRegex[i..i+2]
+                    if part is '(?:'
+                        # Non-capturing bracket
+                        i += 2
+                        cleanedRegex += part
+                    else if part is '(?<'
+                        # Capturing bracket with possibly a name
+                        capturingBracketNumber++
+                        i += 2
+                        name = ''
+                        while i + 1 < len
+                            subChar = rawRegex.charAt(i + 1)
+                            if subChar is '>'
+                                cleanedRegex += '('
+                                i++
+                                if name.length > 0
+                                    # Associate a name with a capturing bracket number
+                                    mapping ?= {}
+                                    mapping[name] = capturingBracketNumber
+                                break
+                            else
+                                name += subChar
+
+                            i++
+                    else
+                        cleanedRegex += _char
+                        capturingBracketNumber++
+                else
+                    cleanedRegex += _char
+            else
+                cleanedRegex += _char
+
+            i++
+
+        @rawRegex = rawRegex
+        @cleanedRegex = cleanedRegex
+        @regex = new RegExp @cleanedRegex, 'g'+modifiers.replace('g', '')
+        @mapping = mapping
+
+
+    # Executes the pattern's regex and returns the matching values
+    #
+    # @param [String] str The string to use to execute the pattern
+    #
+    # @return [Array] The matching values extracted from capturing brackets or null if nothing matched
+    #
+    exec: (str) ->
+        @regex.lastIndex = 0
+        matches = @regex.exec str
+
+        if not matches?
+            return null
+
+        if @mapping?
+            for name, index of @mapping
+                matches[name] = matches[index]
+
+        return matches
+
+
+    # Tests the pattern's regex
+    #
+    # @param [String] str The string to use to test the pattern
+    #
+    # @return [Boolean] true if the string matched
+    #
+    test: (str) ->
+        @regex.lastIndex = 0
+        return @regex.test str
+
+
+    # Replaces occurences matching with the pattern's regex with replacement
+    #
+    # @param [String] str The source string to perform replacements
+    # @param [String] replacement The string to use in place of each replaced occurence.
+    #
+    # @return [String] The replaced string
+    #
+    replace: (str, replacement) ->
+        @regex.lastIndex = 0
+        return str.replace @regex, replacement
+
+
+    # Replaces occurences matching with the pattern's regex with replacement and
+    # get both the replaced string and the number of replaced occurences in the string.
+    #
+    # @param [String] str The source string to perform replacements
+    # @param [String] replacement The string to use in place of each replaced occurence.
+    # @param [Integer] limit The maximum number of occurences to replace (0 means infinite number of occurences)
+    #
+    # @return [Array] A destructurable array containing the replaced string and the number of replaced occurences. For instance: ["my replaced string", 2]
+    #
+    replaceAll: (str, replacement, limit = 0) ->
+        @regex.lastIndex = 0
+        count = 0
+        while @regex.test(str) and (limit is 0 or count < limit)
+            @regex.lastIndex = 0
+            str = str.replace @regex, replacement
+            count++
+        
+        return [str, count]
+
+
+module.exports = Pattern
+
diff --git a/src/Unescaper.coffee b/src/Unescaper.coffee
new file mode 100644
index 0000000..8e1527a
--- /dev/null
+++ b/src/Unescaper.coffee
@@ -0,0 +1,96 @@
+
+Utils   = require './Utils'
+Pattern = require './Pattern'
+
+# Unescaper encapsulates unescaping rules for single and double-quoted YAML strings.
+#
+class Unescaper
+
+    # Regex fragment that matches an escaped character in
+    # a double quoted string.
+    @PATTERN_ESCAPED_CHARACTER:     new Pattern '\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})';
+
+
+    # Unescapes a single quoted string.
+    #
+    # @param [String]       value A single quoted string.
+    #
+    # @return [String]      The unescaped string.
+    #
+    @unescapeSingleQuotedString: (value) ->
+        return value.replace(/\'\'/g, '\'')
+
+
+    # Unescapes a double quoted string.
+    #
+    # @param [String]       value A double quoted string.
+    #
+    # @return [String]      The unescaped string.
+    #
+    @unescapeDoubleQuotedString: (value) ->
+        @_unescapeCallback ?= (str) =>
+            return @unescapeCharacter(str)
+
+        # Evaluate the string
+        return @PATTERN_ESCAPED_CHARACTER.replace value, @_unescapeCallback
+
+
+    # Unescapes a character that was found in a double-quoted string
+    #
+    # @param [String]       value An escaped character
+    #
+    # @return [String]      The unescaped character
+    #
+    @unescapeCharacter: (value) ->
+        ch = String.fromCharCode
+        switch value.charAt(1)
+            when '0'
+                return ch(0)
+            when 'a'
+                return ch(7)
+            when 'b'
+                return ch(8)
+            when 't'
+                return "\t"
+            when "\t"
+                return "\t"
+            when 'n'
+                return "\n"
+            when 'v'
+                return ch(11)
+            when 'f'
+                return ch(12)
+            when 'r'
+                return ch(13)
+            when 'e'
+                return ch(27)
+            when ' '
+                return ' '
+            when '"'
+                return '"'
+            when '/'
+                return '/'
+            when '\\'
+                return '\\'
+            when 'N'
+                # U+0085 NEXT LINE
+                return ch(0x0085)
+            when '_'
+                # U+00A0 NO-BREAK SPACE
+                return ch(0x00A0)
+            when 'L'
+                # U+2028 LINE SEPARATOR
+                return ch(0x2028)
+            when 'P'
+                # U+2029 PARAGRAPH SEPARATOR
+                return ch(0x2029)
+            when 'x'
+                return Utils.utf8chr(Utils.hexDec(value.substr(2, 2)))
+            when 'u'
+                return Utils.utf8chr(Utils.hexDec(value.substr(2, 4)))
+            when 'U'
+                return Utils.utf8chr(Utils.hexDec(value.substr(2, 8)))
+            else
+                return ''
+
+module.exports = Unescaper
diff --git a/src/Utils.coffee b/src/Utils.coffee
new file mode 100644
index 0000000..aade0de
--- /dev/null
+++ b/src/Utils.coffee
@@ -0,0 +1,349 @@
+
+Pattern = require './Pattern'
+
+# A bunch of utility methods
+#
+class Utils
+
+    @REGEX_LEFT_TRIM_BY_CHAR:   {}
+    @REGEX_RIGHT_TRIM_BY_CHAR:  {}
+    @REGEX_SPACES:              /\s+/g
+    @REGEX_DIGITS:              /^\d+$/
+    @REGEX_OCTAL:               /[^0-7]/gi
+    @REGEX_HEXADECIMAL:         /[^a-f0-9]/gi
+
+    # Precompiled date pattern
+    @PATTERN_DATE:              new Pattern '^'+
+            '(?<year>[0-9][0-9][0-9][0-9])'+
+            '-(?<month>[0-9][0-9]?)'+
+            '-(?<day>[0-9][0-9]?)'+
+            '(?:(?:[Tt]|[ \t]+)'+
+            '(?<hour>[0-9][0-9]?)'+
+            ':(?<minute>[0-9][0-9])'+
+            ':(?<second>[0-9][0-9])'+
+            '(?:\.(?<fraction>[0-9]*))?'+
+            '(?:[ \t]*(?<tz>Z|(?<tz_sign>[-+])(?<tz_hour>[0-9][0-9]?)'+
+            '(?::(?<tz_minute>[0-9][0-9]))?))?)?'+
+            '$', 'i'
+
+    # Local timezone offset in ms
+    @LOCAL_TIMEZONE_OFFSET:     new Date().getTimezoneOffset() * 60 * 1000
+
+    # Trims the given string on both sides
+    #
+    # @param [String] str The string to trim
+    # @param [String] _char The character to use for trimming (default: '\\s')
+    #
+    # @return [String] A trimmed string
+    #
+    @trim: (str, _char = '\\s') ->
+        regexLeft = @REGEX_LEFT_TRIM_BY_CHAR[_char]
+        unless regexLeft?
+            @REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp '^'+_char+''+_char+'*'
+        regexLeft.lastIndex = 0
+        regexRight = @REGEX_RIGHT_TRIM_BY_CHAR[_char]
+        unless regexRight?
+            @REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp _char+''+_char+'*$'
+        regexRight.lastIndex = 0
+        return str.replace(regexLeft, '').replace(regexRight, '')
+
+
+    # Trims the given string on the left side
+    #
+    # @param [String] str The string to trim
+    # @param [String] _char The character to use for trimming (default: '\\s')
+    #
+    # @return [String] A trimmed string
+    #
+    @ltrim: (str, _char = '\\s') ->
+        regexLeft = @REGEX_LEFT_TRIM_BY_CHAR[_char]
+        unless regexLeft?
+            @REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp '^'+_char+''+_char+'*'
+        regexLeft.lastIndex = 0
+        return str.replace(regexLeft, '')
+
+
+    # Trims the given string on the right side
+    #
+    # @param [String] str The string to trim
+    # @param [String] _char The character to use for trimming (default: '\\s')
+    #
+    # @return [String] A trimmed string
+    #
+    @rtrim: (str, _char = '\\s') ->
+        regexRight = @REGEX_RIGHT_TRIM_BY_CHAR[_char]
+        unless regexRight?
+            @REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp _char+''+_char+'*$'
+        regexRight.lastIndex = 0
+        return str.replace(regexRight, '')
+
+
+    # Checks if the given value is empty (null, undefined, empty string, string '0', empty Array, empty Object)
+    #
+    # @param [Object] value The value to check
+    #
+    # @return [Boolean] true if the value is empty
+    #
+    @isEmpty: (value) ->
+        return not(value) or value is '' or value is '0' or (value instanceof Array and value.length is 0) or @isEmptyObject(value)
+
+    # Checks if the given value is an empty object
+    #
+    # @param [Object] value The value to check
+    #
+    # @return [Boolean] true if the value is empty and is an object
+    #
+    @isEmptyObject: (value) ->
+        return value instanceof Object and (k for own k of value).length is 0
+
+    # Counts the number of occurences of subString inside string
+    #
+    # @param [String] string The string where to count occurences
+    # @param [String] subString The subString to count
+    # @param [Integer] start The start index
+    # @param [Integer] length The string length until where to count
+    #
+    # @return [Integer] The number of occurences
+    #
+    @subStrCount: (string, subString, start, length) ->
+        c = 0
+
+        string = '' + string
+        subString = '' + subString
+
+        if start?
+            string = string[start..]
+        if length?
+            string = string[0...length]
+
+        len = string.length
+        sublen = subString.length
+        for i in [0...len]
+            if subString is string[i...sublen]
+                c++
+                i += sublen - 1
+
+        return c
+
+
+    # Returns true if input is only composed of digits
+    #
+    # @param [Object] input The value to test
+    #
+    # @return [Boolean] true if input is only composed of digits
+    #
+    @isDigits: (input) ->
+        @REGEX_DIGITS.lastIndex = 0
+        return @REGEX_DIGITS.test input
+
+
+    # Decode octal value
+    #
+    # @param [String] input The value to decode
+    #
+    # @return [Integer] The decoded value
+    #
+    @octDec: (input) ->
+        @REGEX_OCTAL.lastIndex = 0
+        return parseInt((input+'').replace(@REGEX_OCTAL, ''), 8)
+
+
+    # Decode hexadecimal value
+    #
+    # @param [String] input The value to decode
+    #
+    # @return [Integer] The decoded value
+    #
+    @hexDec: (input) ->
+        @REGEX_HEXADECIMAL.lastIndex = 0
+        input = @trim(input)
+        if (input+'')[0...2] is '0x' then input = (input+'')[2..]
+        return parseInt((input+'').replace(@REGEX_HEXADECIMAL, ''), 16)
+
+
+    # Get the UTF-8 character for the given code point.
+    #
+    # @param [Integer] c The unicode code point
+    #
+    # @return [String] The corresponding UTF-8 character
+    #
+    @utf8chr: (c) ->
+        ch = String.fromCharCode
+        if 0x80 > (c %= 0x200000)
+            return ch(c)
+        if 0x800 > c
+            return ch(0xC0 | c>>6) + ch(0x80 | c & 0x3F)
+        if 0x10000 > c
+            return ch(0xE0 | c>>12) + ch(0x80 | c>>6 & 0x3F) + ch(0x80 | c & 0x3F)
+
+        return ch(0xF0 | c>>18) + ch(0x80 | c>>12 & 0x3F) + ch(0x80 | c>>6 & 0x3F) + ch(0x80 | c & 0x3F)
+
+
+    # Returns the boolean value equivalent to the given input
+    #
+    # @param [String|Object]    input       The input value
+    # @param [Boolean]          strict      If set to false, accept 'yes' and 'no' as boolean values
+    #
+    # @return [Boolean]         the boolean value
+    #
+    @parseBoolean: (input, strict = true) ->
+        if typeof(input) is 'string'
+            lowerInput = input.toLowerCase()
+            if not strict
+                if lowerInput is 'no' then return false
+            if lowerInput is '0' then return false
+            if lowerInput is 'false' then return false
+            if lowerInput is '' then return false
+            return true
+        return !!input
+
+
+
+    # Returns true if input is numeric
+    #
+    # @param [Object] input The value to test
+    #
+    # @return [Boolean] true if input is numeric
+    #
+    @isNumeric: (input) ->
+        @REGEX_SPACES.lastIndex = 0
+        return typeof(input) is 'number' or typeof(input) is 'string' and !isNaN(input) and input.replace(@REGEX_SPACES, '') isnt ''
+
+
+    # Returns a parsed date from the given string
+    #
+    # @param [String] str The date string to parse
+    #
+    # @return [Date] The parsed date or null if parsing failed
+    #
+    @stringToDate: (str) ->
+        unless str?.length
+            return null
+
+        # Perform regular expression pattern
+        info = @PATTERN_DATE.exec str
+        unless info
+            return null
+
+        # Extract year, month, day
+        year = parseInt info.year, 10
+        month = parseInt(info.month, 10) - 1 # In javascript, january is 0, february 1, etc...
+        day = parseInt info.day, 10
+
+        # If no hour is given, return a date with day precision
+        unless info.hour?
+            date = new Date Date.UTC(year, month, day)
+            return date
+
+        # Extract hour, minute, second
+        hour = parseInt info.hour, 10
+        minute = parseInt info.minute, 10
+        second = parseInt info.second, 10
+
+        # Extract fraction, if given
+        if info.fraction?
+            fraction = info.fraction[0...3]
+            while fraction.length < 3
+                fraction += '0'
+            fraction = parseInt fraction, 10
+        else
+            fraction = 0
+
+        # Compute timezone offset if given
+        if info.tz?
+            tz_hour = parseInt info.tz_hour, 10
+            if info.tz_minute?
+                tz_minute = parseInt info.tz_minute, 10
+            else
+                tz_minute = 0
+
+            # Compute timezone delta in ms
+            tz_offset = (tz_hour * 60 + tz_minute) * 60000
+            if '-' is info.tz_sign
+                tz_offset *= -1
+
+        # Compute date
+        date = new Date Date.UTC(year, month, day, hour, minute, second, fraction)
+        if tz_offset
+            date.setTime date.getTime() - tz_offset
+
+        return date
+
+
+    # Repeats the given string a number of times
+    #
+    # @param [String]   str     The string to repeat
+    # @param [Integer]  number  The number of times to repeat the string
+    #
+    # @return [String]  The repeated string
+    #
+    @strRepeat: (str, number) ->
+        res = ''
+        i = 0
+        while i < number
+            res += str
+            i++
+        return res
+
+
+    # Reads the data from the given file path and returns the result as string
+    #
+    # @param [String]   path        The path to the file
+    # @param [Function] callback    A callback to read file asynchronously (optional)
+    #
+    # @return [String]  The resulting data as string
+    #
+    @getStringFromFile: (path, callback = null) ->
+        xhr = null
+        if window?
+            if window.XMLHttpRequest
+                xhr = new XMLHttpRequest()
+            else if window.ActiveXObject
+                for name in ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"]
+                    try
+                        xhr = new ActiveXObject(name)
+
+        if xhr?
+            # Browser
+            if callback?
+                # Async
+                xhr.onreadystatechange = ->
+                    if xhr.readyState is 4
+                        if xhr.status is 200 or xhr.status is 0
+                            callback(xhr.responseText)
+                        else
+                            callback(null)
+                xhr.open 'GET', path, true
+                xhr.send null
+
+            else
+                # Sync
+                xhr.open 'GET', path, false
+                xhr.send null
+
+                if xhr.status is 200 or xhr.status == 0
+                    return xhr.responseText
+
+                return null
+        else
+            # Node.js-like
+            req = require
+            fs = req('fs') # Prevent browserify from trying to load 'fs' module
+            if callback?
+                # Async
+                fs.readFile path, (err, data) ->
+                    if err
+                        callback null
+                    else
+                        callback String(data)
+
+            else
+                # Sync
+                data = fs.readFileSync path
+                if data?
+                    return String(data)
+                return null
+
+
+
+module.exports = Utils
diff --git a/src/Yaml.coffee b/src/Yaml.coffee
new file mode 100644
index 0000000..83951b5
--- /dev/null
+++ b/src/Yaml.coffee
@@ -0,0 +1,104 @@
+
+Parser = require './Parser'
+Dumper = require './Dumper'
+Utils  = require './Utils'
+
+# Yaml offers convenience methods to load and dump YAML.
+#
+class Yaml
+
+    # Parses YAML into a JavaScript object.
+    #
+    # The parse method, when supplied with a YAML string,
+    # will do its best to convert YAML in a file into a JavaScript object.
+    #
+    #  Usage:
+    #     myObject = Yaml.parse('some: yaml');
+    #     console.log(myObject);
+    #
+    # @param [String]   input                   A string containing YAML
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types, false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object]  The YAML converted to a JavaScript object
+    #
+    # @throw [ParseException] If the YAML is not valid
+    #
+    @parse: (input, exceptionOnInvalidType = false, objectDecoder = null) ->
+        return new Parser().parse(input, exceptionOnInvalidType, objectDecoder)
+
+
+    # Parses YAML from file path into a JavaScript object.
+    #
+    # The parseFile method, when supplied with a YAML file,
+    # will do its best to convert YAML in a file into a JavaScript object.
+    #
+    #  Usage:
+    #     myObject = Yaml.parseFile('config.yml');
+    #     console.log(myObject);
+    #
+    # @param [String]   path                    A file path pointing to a valid YAML file
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types, false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object]  The YAML converted to a JavaScript object or null if the file doesn't exist.
+    #
+    # @throw [ParseException] If the YAML is not valid
+    #
+    @parseFile: (path, callback = null, exceptionOnInvalidType = false, objectDecoder = null) ->
+        if callback?
+            # Async
+            Utils.getStringFromFile path, (input) =>
+                result = null
+                if input?
+                    result = @parse input, exceptionOnInvalidType, objectDecoder
+                callback result
+                return
+        else
+            # Sync
+            input = Utils.getStringFromFile path
+            if input?
+                return @parse input, exceptionOnInvalidType, objectDecoder
+            return null
+
+
+    # Dumps a JavaScript object to a YAML string.
+    #
+    # The dump method, when supplied with an object, will do its best
+    # to convert the object into friendly YAML.
+    #
+    # @param [Object]   input                   JavaScript object
+    # @param [Integer]  inline                  The level where you switch to inline YAML
+    # @param [Integer]  indent                  The amount of spaces to use for indentation of nested nodes.
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectEncoder           A function to serialize custom objects, null otherwise
+    #
+    # @return [String]  A YAML string representing the original JavaScript object
+    #
+    @dump: (input, inline = 2, indent = 4, exceptionOnInvalidType = false, objectEncoder = null) ->
+        yaml = new Dumper()
+        yaml.indentation = indent
+
+        return yaml.dump(input, inline, 0, exceptionOnInvalidType, objectEncoder)
+
+
+    # Alias of dump() method for compatibility reasons.
+    #
+    @stringify: (input, inline, indent, exceptionOnInvalidType, objectEncoder) ->
+        return @dump input, inline, indent, exceptionOnInvalidType, objectEncoder
+
+
+    # Alias of parseFile() method for compatibility reasons.
+    #
+    @load: (path, callback, exceptionOnInvalidType, objectDecoder) ->
+        return @parseFile path, callback, exceptionOnInvalidType, objectDecoder
+
+
+# Expose YAML namespace to browser
+window?.YAML = Yaml
+
+# Not in the browser?
+unless window?
+    @YAML = Yaml
+
+module.exports = Yaml

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



More information about the Pkg-javascript-commits mailing list