├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src ├── assert.js ├── decoder.js ├── dumper.js ├── encoder.js ├── entity.js ├── error.js ├── map.js └── neon.js └── test ├── Decoder.errors.js ├── Decoder.inline.array.js ├── Decoder.scalar.js ├── Decored.array.js ├── Encoder.js └── Map.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test/ 2 | /.* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: npm install -g mocha 5 | script: mocha --ui tdd 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, David Matějka 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NEON-JS - [NEON](http://ne-on.org/) decoder and encoder for JavaScript 2 | ===================================================================== 3 | 4 | [![Build Status](https://travis-ci.org/matej21/neon-js.svg?branch=master)](https://travis-ci.org/matej21/neon-js) 5 | [![NPM version](https://img.shields.io/npm/v/neon-js.svg)](https://www.npmjs.org/package/neon-js) 6 | 7 | NEON is very similar to YAML.The main difference is that the NEON supports "entities" 8 | (so can be used e.g. to parse phpDoc annotations) and tab characters for indentation. 9 | NEON syntax is a little simpler and the parsing is faster. 10 | 11 | Example of Neon code: 12 | 13 | ``` 14 | # my web application config 15 | 16 | php: 17 | date.timezone: Europe/Prague 18 | zlib.output_compression: yes # use gzip 19 | 20 | database: 21 | driver: mysql 22 | username: root 23 | password: beruska92 24 | 25 | users: 26 | - Dave 27 | - Kryten 28 | - Rimmer 29 | ``` 30 | 31 | Installation: 32 | ------------ 33 | ### NEON module for node.js 34 | ``` 35 | npm install neon-js 36 | ``` 37 | 38 | CLI executable is not available yet. 39 | 40 | ### NEON for browser 41 | 42 | You can create browser compatible library using browserify. 43 | ``` 44 | npm install browserify 45 | browserify -r neon-js -s neon 46 | ``` 47 | 48 | You can found precompiled browserifed version in repository [matej21/neon-js-dist](https://github.com/matej21/neon-js-dist). 49 | 50 | ```html 51 | 52 | 55 | ``` 56 | 57 | API 58 | --- 59 | 60 | ```javascript 61 | var neon = require('neon-js'); 62 | 63 | try { 64 | var result = neon.decode("foo: {bar: 123}") 65 | } catch (e) { 66 | if (e instanceof neon.Error) { 67 | console.log(e.message); 68 | } 69 | throw e; 70 | } 71 | ``` 72 | 73 | ### `decode(input)` 74 | 75 | Decodes input NEON string. 76 | 77 | ```javascript 78 | var result = neon.decode(input); 79 | ``` 80 | 81 | ### `encode(var[, options])` 82 | 83 | Encodes javascript to NEON. 84 | 85 | ```javascript 86 | var data = {foo: "bar"} 87 | var result = neon.encode(data); 88 | //or you can use block mode 89 | var result = neon.encode(data, neon.BLOCK); 90 | ``` 91 | 92 | ### Entity 93 | 94 | Class representing NEON entity. 95 | 96 | ```javascript 97 | var entity = neon.decode("Foo(bar)"); 98 | entity.value; "Foo"; 99 | entity.attributes// neon.Map instance, see bellow 100 | ``` 101 | 102 | ### Map 103 | 104 | NEON was originally written for PHP where all NEON array-like structures (lists, objects or mixed) were converted to the PHP array. And arrays in PHP are very powerful - they can be treated as a list, hash table and more. `neon.Map` is trying to keep the very basic of this behaviour - items are ordered (list) and can have string key (hash table). Therefore all NEON array-like structures are mapped to the `neon.Map`. 105 | 106 | ``` 107 | #example neon 108 | foo: bar 109 | - lorem 110 | ipsum: dolor 111 | - sit 112 | ``` 113 | 114 | ```javascript 115 | var map = neon.decode(input); 116 | //map instanceof neon.Map 117 | ``` 118 | 119 | #### `Map.get(key)` 120 | 121 | Returns value by the key. 122 | 123 | ```javascript 124 | map.get("foo"); // bar 125 | map.get(0); // lorem 126 | map.get("xxx"); //throws an Error 127 | ``` 128 | 129 | #### `Map.has(key)` 130 | 131 | Checks if the given key exists in the map. 132 | 133 | ```javascript 134 | map.has("foo"); // true 135 | map.has("xxx"); // false 136 | ``` 137 | 138 | #### `Map.forEach(callable)` 139 | 140 | Calls provided function for every item in the map. Key is passed as a first argument, value as a second argument. 141 | 142 | ```javascript 143 | map.forEach(function (key, value) { 144 | 145 | }); 146 | ``` 147 | 148 | #### `Map.isList()` 149 | 150 | Checks if Map is a list. That means the map doesn't contain any string keys and numeric keys are in range `0..(length-1)`. 151 | 152 | ```javascript 153 | map.isList(); //false 154 | ``` 155 | 156 | #### `Map.values()` 157 | 158 | Returns values as an array. 159 | 160 | ```javascript 161 | var values = map.values(); 162 | // ["bar", "lorem", "dolor", "sit"] 163 | ``` 164 | 165 | #### `Map.keys()` 166 | 167 | Returns keys as an array. 168 | 169 | ```javascript 170 | var keys = map.keys(); 171 | // ["foo", 0, "ipsum", 1] 172 | ``` 173 | 174 | #### `Map.items()` 175 | 176 | Returns items structure. This structure is an array where every item is object with properties `key` and `value`. 177 | 178 | ```javascript 179 | var items = map.items(); 180 | 181 | // [{key: "foo", value: "bar"}, ...] 182 | ``` 183 | 184 | #### `Map.toObject([deep])` 185 | 186 | Converts Map to javascript object. Items order may be lost. 187 | ```javascript 188 | var obj = map.toObject(); 189 | // {foo: "bar", 0: "lorem", ipsum: "dolor", 1: sit} 190 | ``` 191 | 192 | ### `Dumper.toText(data)` 193 | 194 | Dumps decoded structure into simple text output as you can see in the [demo](http://matej21.github.io/neon-js-dist/). 195 | 196 | ```javascript 197 | var text = neon.Dumper.toText(data)`; 198 | ``` 199 | 200 | 201 | Links: 202 | ------ 203 | - [Official Neon homepage](http://ne-on.org) 204 | - [Neon-js sandbox](http://matej21.github.io/neon-js-dist/) 205 | - [Neon for PHP](https://github.com/nette/neon) 206 | - [Neon-js for browser](http://www.github.com/matej21/neon-js-dist/) 207 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neon-js", 3 | "version": "1.1.2", 4 | "description": "NEON decoder and encoder for JavaScript", 5 | "main": "src/neon.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "mocha": "~2.1" 12 | }, 13 | "scripts": { 14 | "test": "mocha --ui tdd" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/matej21/neon-js.git" 19 | }, 20 | "keywords": [ 21 | "neon", 22 | "nette" 23 | ], 24 | "author": "David Matějka ", 25 | "license": "BSD-3-Clause", 26 | "bugs": { 27 | "url": "https://github.com/matej21/neon-js/issues" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/assert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mainAssert = require('assert'); 4 | var Map = require('./map'); 5 | var Entity = require('./entity'); 6 | 7 | module.exports.equal = function (actual, expected) { 8 | var mapToObject = function (value) { 9 | if (value instanceof Map) { 10 | var obj = {}; 11 | value.forEach(function (key, value) { 12 | obj[key] = mapToObject(value); 13 | }); 14 | return obj; 15 | } else if (value instanceof Entity) { 16 | value.attributes = mapToObject(value.attributes); 17 | value.value = mapToObject(value.value); 18 | } 19 | 20 | return value; 21 | }; 22 | return mainAssert.deepEqual(mapToObject(actual), expected); 23 | }; 24 | -------------------------------------------------------------------------------- /src/decoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Map = require("./map"); 4 | var Entity = require("./entity"); 5 | var NeonError = require('./error'); 6 | 7 | 8 | function Result() { 9 | this.key = 0; 10 | this.value = null; 11 | this.add = function (key, value) { 12 | if (this.value === null) { 13 | this.value = new Map(); 14 | } 15 | return this.value.add(key, value); 16 | }; 17 | } 18 | 19 | 20 | function decoder(output) { 21 | if (typeof output === "undefined") { 22 | output = decoder.MAP; 23 | } 24 | 25 | /** @var array */ 26 | this.tokens = []; 27 | 28 | /** @var int */ 29 | this.pos = 0; 30 | 31 | this.input = ""; 32 | 33 | 34 | /** 35 | * Decodes a NEON string. 36 | * @param input string 37 | * @return mixed 38 | */ 39 | this.decode = function (input) { 40 | 41 | if (typeof (input) != "string") { 42 | throw 'Argument must be a string, ' + typeof input + ' given.'; 43 | 44 | } else if (input.substr(0, 3) == "\xEF\xBB\xBF") { // BOM 45 | input = input.substr(3); 46 | } 47 | this.input = "\n" + "" + input.replace(/\r\n/g, "\n"); // \n forces indent detection 48 | 49 | var regexp = new RegExp('(' + "" + decoder.patterns.join(')|(') + "" + ')', 'mig'); 50 | this.tokens = this.split(regexp, this.input); 51 | 52 | var last = this.tokens[this.tokens.length - 1]; 53 | if (this.tokens && !regexp.test(last[0])) { 54 | this.pos = this.tokens.length - 1; 55 | this.error(); 56 | } 57 | 58 | this.pos = 0; 59 | var res = this.parse(null); 60 | 61 | while ((this.tokens[this.pos])) { 62 | if (this.tokens[this.pos][0][0] === "\n") { 63 | this.pos++; 64 | } else { 65 | this.error(); 66 | } 67 | } 68 | var flatten = function (res) { 69 | if (res instanceof Result) { 70 | return flatten(res.value); 71 | } else if (res instanceof Entity) { 72 | res.attributes = flatten(res.attributes); 73 | res.value = flatten(res.value); 74 | } else if (res instanceof Map) { 75 | if (output === decoder.FORCE_OBJECT) { 76 | var obj = {}; 77 | res.forEach(function (key, value) { 78 | obj[key] = flatten(value); 79 | }); 80 | return obj; 81 | } else { 82 | var result = new Map; 83 | var isList = true; 84 | var cmp = 0; 85 | res.forEach(function (key, value) { 86 | result.set(key, flatten(value)); 87 | if (key !== cmp++) { 88 | isList = false; 89 | } 90 | }); 91 | if (output === decoder.MAP) { 92 | return result; 93 | } else { 94 | return isList ? result.values() : result.toObject(); 95 | } 96 | } 97 | } 98 | return res; 99 | 100 | }; 101 | return flatten(res); 102 | 103 | }; 104 | 105 | 106 | /** 107 | * @param indent string indentation (for block-parser) 108 | * @param key mixed 109 | * @param hasKey bool 110 | * @return array 111 | */ 112 | this.parse = function (indent, defaultValue, key, hasKey) { 113 | if (typeof key === "undefined") { 114 | key = null; 115 | } 116 | if (typeof defaultValue === "undefined") { 117 | defaultValue = null; 118 | } 119 | 120 | if (typeof hasKey === "undefined") { 121 | hasKey = false; 122 | } 123 | var result = new Result(); 124 | result.value = defaultValue; 125 | var inlineParser = indent === false; 126 | var value = null; 127 | var hasValue = false; 128 | var tokens = this.tokens; 129 | var count = tokens.length; 130 | var mainResult = result; 131 | 132 | for (; this.pos < count; this.pos++) { 133 | var t = tokens[this.pos][0]; 134 | 135 | if (t === ',') { // ArrayEntry separator 136 | if ((!hasKey && !hasValue) || !inlineParser) { 137 | this.error(); 138 | } 139 | this.addValue(result, hasKey ? key : null, hasValue ? value : null); 140 | hasKey = hasValue = false; 141 | 142 | } else if (t === ':' || t === '=') { // KeyValuePair separator 143 | if (hasValue && (typeof value == "object")) { 144 | this.error('Unacceptable key'); 145 | 146 | } else if (hasKey && key == null && hasValue && !inlineParser) { 147 | this.pos++; 148 | this.addValue(result, null, this.parse(indent + "" + ' ', new Map(), value, true)); 149 | var newIndent = (typeof tokens[this.pos] !== "undefined" && typeof tokens[this.pos + 1] !== "undefined") ? tokens[this.pos][0].substr(1) : ''; // not last 150 | if (newIndent.length > indent.length) { 151 | this.pos++; 152 | this.error('Bad indentation'); 153 | } else if (newIndent.length < indent.length) { 154 | return mainResult; // block parser exit point 155 | } 156 | hasKey = hasValue = false; 157 | 158 | } else if (hasKey || !hasValue) { 159 | this.error(); 160 | 161 | } else { 162 | key = value; 163 | hasKey = true; 164 | hasValue = false; 165 | result = mainResult; 166 | } 167 | 168 | } else if (t === '-') { // BlockArray bullet 169 | if (hasKey || hasValue || inlineParser) { 170 | this.error(); 171 | } 172 | key = null; 173 | hasKey = true; 174 | 175 | } else if ((decoder.brackets[t])) { // Opening bracket [ ( { 176 | if (hasValue) { 177 | if (t !== '(') { 178 | this.error(); 179 | } 180 | this.pos++; 181 | if (value instanceof Entity && value.value === decoder.CHAIN) { 182 | value.attributes.value.last().value.attributes = this.parse(false, new Map()); 183 | } else { 184 | value = new Entity(value, this.parse(false, new Map())); 185 | } 186 | } else { 187 | this.pos++; 188 | value = this.parse(false, new Map()); 189 | } 190 | hasValue = true; 191 | if (tokens[this.pos] === undefined || tokens[this.pos][0] !== decoder.brackets[t]) { // unexpected type of bracket or block-parser 192 | this.error(); 193 | } 194 | 195 | } else if (t === ']' || t === '}' || t === ')') { // Closing bracket ] ) } 196 | if (!inlineParser) { 197 | this.error(); 198 | } 199 | break; 200 | 201 | } else if (t[0] === "\n") { // Indent 202 | if (inlineParser) { 203 | if (hasKey || hasValue) { 204 | this.addValue(result, hasKey ? key : null, hasValue ? value : null); 205 | hasKey = hasValue = false; 206 | } 207 | 208 | } else { 209 | while (tokens[this.pos + 1] !== undefined && tokens[this.pos + 1][0][0] === "\n") { 210 | this.pos++; // skip to last indent 211 | } 212 | if (tokens[this.pos + 1] === undefined) { 213 | break; 214 | } 215 | 216 | newIndent = tokens[this.pos][0].substr(1); 217 | if (indent === null) { // first iteration 218 | indent = newIndent; 219 | } 220 | var minlen = Math.min(newIndent.length, indent.length); 221 | if (minlen && newIndent.substr(0, minlen) !== indent.substr(0, minlen)) { 222 | this.pos++; 223 | this.error('Invalid combination of tabs and spaces'); 224 | } 225 | 226 | if (newIndent.length > indent.length) { // open new block-array or hash 227 | if (hasValue || !hasKey) { 228 | this.pos++; 229 | this.error('Bad indentation'); 230 | } 231 | this.addValue(result, key, this.parse(newIndent, new Map)); 232 | newIndent = (tokens[this.pos] !== undefined && tokens[this.pos + 1] !== undefined) ? tokens[this.pos][0].substr(1) : ''; // not last 233 | if (newIndent.length > indent.length) { 234 | this.pos++; 235 | this.error('Bad indentation'); 236 | } 237 | hasKey = false; 238 | 239 | } else { 240 | if (hasValue && !hasKey) { // block items must have "key"; NULL key means list item 241 | break; 242 | 243 | } else if (hasKey) { 244 | this.addValue(result, key, hasValue ? value : null); 245 | if (key !== null && !hasValue && newIndent === indent && typeof tokens[this.pos + 1] !== "undefined" && tokens[this.pos + 1][0] === "-") { 246 | result.value.set(key, new Result); 247 | result = result.value.get(key); 248 | } 249 | 250 | hasKey = hasValue = false; 251 | } 252 | } 253 | 254 | if (newIndent.length < indent.length) { // close block 255 | return mainResult; // block parser exit point 256 | } 257 | } 258 | 259 | } else if (hasValue) { // Value 260 | if (value instanceof Entity) { // Entity chaining 261 | if (value.value !== decoder.CHAIN) { 262 | var attributes = new Result(); 263 | attributes.add(null, value); 264 | value = new Entity(decoder.CHAIN, attributes); 265 | } 266 | value.attributes.add(null, new Entity(t)); 267 | } else { 268 | this.error(); 269 | } 270 | } else { // Value 271 | if (typeof this.parse.consts == 'undefined') 272 | this.parse.consts = { 273 | 'true': true, 'True': true, 'TRUE': true, 'yes': true, 'Yes': true, 'YES': true, 'on': true, 'On': true, 'ON': true, 274 | 'false': false, 'False': false, 'FALSE': false, 'no': false, 'No': false, 'NO': false, 'off': false, 'Off': false, 'OFF': false, 275 | 'null': 0, 'Null': 0, 'NULL': 0 276 | }; 277 | if (t[0] === '"') { 278 | var self = this; 279 | value = t.substr(1, t.length - 2).replace(/\\(?:u[0-9a-f]{4}|x[0-9a-f]{2}|.)/gi, function (match) { 280 | var mapping = {'t': "\t", 'n': "\n", 'r': "\r", 'f': "\x0C", 'b': "\x08", '"': '"', '\\': '\\', '/': '/', '_': "\xc2\xa0"}; 281 | if (mapping[match[1]] !== undefined) { 282 | return mapping[match[1]]; 283 | } else if (match[1] === 'u' && match.length === 6) { 284 | return String.fromCharCode(parseInt(match.substr(2), 16)); 285 | } else if (match[1] === 'x' && match.length === 4) { 286 | return String.fromCharCode(parseInt(match.substr(2), 16)); 287 | } else { 288 | self.error("Invalid escaping sequence " + match + ""); 289 | } 290 | }); 291 | } else if (t[0] === "'") { 292 | value = t.substr(1, t.length - 2); 293 | } else if (typeof this.parse.consts[t] !== "undefined" 294 | && (typeof tokens[this.pos + 1] === "undefined" || (typeof tokens[this.pos + 1] !== "undefined" && tokens[this.pos + 1][0] !== ':' && tokens[this.pos + 1][0] !== '='))) { 295 | value = this.parse.consts[t] === 0 ? null : this.parse.consts[t]; 296 | } else if (!isNaN(t)) { 297 | value = t * 1; 298 | } else if (t.match(/^\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| +)\d\d?:\d\d:\d\d(?:\.\d*)? *(?:Z|[-+]\d\d?(?::\d\d)?)?)?$/)) { 299 | value = new Date(t); 300 | } else { // literal 301 | value = t; 302 | } 303 | hasValue = true; 304 | } 305 | } 306 | 307 | if (inlineParser) { 308 | if (hasKey || hasValue) { 309 | this.addValue(result, hasKey ? key : null, hasValue ? value : null); 310 | } 311 | } else { 312 | if (hasValue && !hasKey) { // block items must have "key" 313 | if (result.value === null || (result.value instanceof Map && result.value.length == 0)) { //if empty 314 | return value; // simple value parser 315 | } else { 316 | this.error(); 317 | } 318 | } else if (hasKey) { 319 | this.addValue(result, key, hasValue ? value : null); 320 | } 321 | } 322 | return mainResult; 323 | }; 324 | 325 | 326 | this.addValue = function (result, key, value) { 327 | if (result.add(key, value) === false) { 328 | this.error("Duplicated key '" + key + "'"); 329 | } 330 | }; 331 | 332 | 333 | this.error = function (message) { 334 | if (typeof message === "undefined") { 335 | message = "Unexpected '%s'"; 336 | } 337 | 338 | var last = this.tokens[this.pos] !== undefined ? this.tokens[this.pos] : null; 339 | var offset = last ? last[1] : this.input.length; 340 | var text = this.input.substr(0, offset); 341 | var line = text.split("\n").length - 1; 342 | var col = offset - ("\n" + "" + text).lastIndexOf("\n") + 1; 343 | var token = last ? last[0].substr(0, 40).replace("\n", '') : 'end'; 344 | throw new NeonError(message.replace("%s", token), line, col); 345 | }; 346 | 347 | 348 | this.split = function (pattern, subject) { 349 | /* 350 | Copyright (c) 2013 Kevin van Zonneveld (http://kvz.io) 351 | and Contributors (http://phpjs.org/authors) 352 | LICENSE: https://github.com/kvz/phpjs/blob/master/LICENSE.txt 353 | */ 354 | var result, ret = [], index = 0, i = 0; 355 | 356 | var _filter = function (str, strindex) { 357 | if (!str.length) { 358 | return; 359 | } 360 | str = [str, strindex]; 361 | ret.push(str); 362 | }; 363 | // Exec the pattern and get the result 364 | while (result = pattern.exec(subject)) { 365 | // Take the correct portion of the string and filter the match 366 | _filter(subject.slice(index, result.index), index); 367 | index = result.index + result[0].length; 368 | // Convert the regexp result into a normal array 369 | var resarr = Array.prototype.slice.call(result); 370 | for (i = 1; i < resarr.length; i++) { 371 | if (result[i] !== undefined) { 372 | _filter(result[i], result.index + result[0].indexOf(result[i])); 373 | } 374 | } 375 | } 376 | // Filter last match 377 | _filter(subject.slice(index, subject.length), index); 378 | return ret; 379 | } 380 | 381 | } 382 | 383 | decoder.patterns = [ 384 | "'[^'\\n]*'|\"(?:\\\\.|[^\"\\\\\\n])*\"", 385 | "(?:[^\\x00-\\x20#\"',:=[\\]{}()!`-]|[:-][^\"',\\]})\\s])(?:[^\\x00-\\x20,:=\\]})(]+|:(?![\\s,\\]})]|$)|[\\ \\t]+[^\\x00-\\x20#,:=\\]})(])*", 386 | "[,:=[\\]{}()-]", 387 | "?:\\#.*", 388 | "\\n[\\t\\ ]*", 389 | "?:[\\t\\ ]+"]; 390 | 391 | decoder.brackets = { 392 | '[': ']', 393 | '{': '}', 394 | '(': ')' 395 | }; 396 | decoder.CHAIN = '!!chain'; 397 | 398 | decoder.MAP = 'map'; 399 | decoder.AUTO = 'auto'; 400 | decoder.FORCE_OBJECT = 'object'; 401 | 402 | 403 | module.exports = decoder; 404 | -------------------------------------------------------------------------------- /src/dumper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Map = require('./map'); 4 | var Entity = require('./entity'); 5 | var Decoder = require('./decoder'); 6 | 7 | function toText(xvar) { 8 | if (xvar instanceof Date) { 9 | return "(date) " + xvar.toJSON(); 10 | 11 | } else if (xvar instanceof Entity) { 12 | var attrs = ''; 13 | if (xvar.attributes !== null && typeof xvar.attributes === "object") { 14 | attrs = toText(xvar.attributes); 15 | } 16 | return "(entity)\n\tname: " + toText(xvar.value) 17 | + "\n\tattributes: " + (attrs.indexOf("\n") === -1 ? ("" + attrs) : attrs.replace(/\n/g, "\n\t")); 18 | } 19 | 20 | if (xvar instanceof Map) { 21 | var s = "array (" + xvar.length + ")\n"; 22 | xvar.forEach(function (key, val) { 23 | var value = toText(val); 24 | s += "\t" + (toText(key) + " => ") 25 | + "" + (value.indexOf("\n") === -1 ? ("" + value) : value.replace(/\n/g, "\n\t")) 26 | + "" + "\n"; 27 | }); 28 | return s.trim() + "\n"; 29 | 30 | } else if (typeof xvar == "string" && isNaN(xvar) 31 | && !xvar.match(/[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)$/i) 32 | && (new RegExp("^" + Decoder.patterns[1] + "$")).exec(xvar) // 1 = literals 33 | ) { 34 | return '"' + xvar + '" (' + xvar.length + ")"; 35 | } else { 36 | return JSON.stringify(xvar); 37 | } 38 | } 39 | 40 | module.exports.toText = toText; 41 | -------------------------------------------------------------------------------- /src/encoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Entity = require('./entity'); 4 | var Decoder = require('./decoder'); 5 | var Map = require('./map'); 6 | 7 | function encoder() { 8 | /** 9 | * Returns the NEON representation of a value. 10 | * @param xvar mixed 11 | * @param options int 12 | * @return string 13 | */ 14 | this.encode = function (xvar, options) { 15 | if (typeof options === "undefined") { 16 | options = null; 17 | } 18 | 19 | if (xvar instanceof Date) { 20 | return xvar.format('Y-m-d H:i:s O'); 21 | 22 | } else if (xvar instanceof Entity) { 23 | var attrs = ''; 24 | if (xvar.attributes !== null && typeof xvar.attributes === "object") { 25 | attrs = this.encode(xvar.attributes); 26 | attrs = attrs.substr(1, attrs.length - 2); 27 | } 28 | return this.encode(xvar.value) + "" + '(' + "" + attrs + "" + ')'; 29 | } 30 | 31 | if (xvar !== null && typeof xvar == 'object') { 32 | var isMap = xvar instanceof Map; 33 | var isList = (xvar instanceof Array && (function (arr) { 34 | var length = 0; 35 | for (var i in arr) { 36 | length++; 37 | } 38 | return length === arr.length; 39 | })(xvar)) || (isMap && xvar.isList()); 40 | var s = ''; 41 | xvar = isMap ? xvar.items() : xvar; 42 | if ((function (arr) { 43 | for (var key in arr) { 44 | return false; 45 | } 46 | return true; 47 | })(xvar)) { 48 | return '[]'; 49 | } 50 | for (var i in xvar) { 51 | if (isMap) { 52 | var k = xvar[i].key; 53 | var v = xvar[i].value; 54 | } else { 55 | var k = xvar instanceof Array ? parseInt(i) : i; 56 | var v = xvar[i]; 57 | } 58 | if (options & encoder.BLOCK) { 59 | v = this.encode(v, encoder.BLOCK); 60 | s += (isList ? '-' : this.encode(k) + "" + ':') 61 | + "" + (v.indexOf("\n") === -1 ? (' ' + "" + v) : "\n\t" + "" + v.replace(/\n/g, "\n\t")) 62 | + "" + "\n"; 63 | } else { 64 | s += (isList ? '' : this.encode(k) + "" + ': ') + "" + this.encode(v) + "" + ', '; 65 | } 66 | } 67 | if (options & encoder.BLOCK) { 68 | return s.trim().replace(/^\s*\n/gm, '') + "\n"; 69 | } else { 70 | return (isList ? '[' : '{') + "" + s.substr(0, s.length - 2) + "" + (isList ? ']' : '}'); 71 | } 72 | 73 | } else if (this.isLiteral(xvar)) { 74 | return xvar; 75 | } else { 76 | return JSON.stringify(xvar); 77 | } 78 | }; 79 | 80 | this.isLiteral = function (value) { 81 | if (typeof value !== "string" || !isNaN(value)) { 82 | return false; 83 | } 84 | if (value.match(/[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)$/i)) { 85 | return false; 86 | } 87 | var result = (new RegExp("^" + Decoder.patterns[1])).exec(value); 88 | 89 | return result && result[0].length === value.length; 90 | 91 | } 92 | 93 | } 94 | encoder.BLOCK = 1; 95 | 96 | module.exports = encoder; 97 | -------------------------------------------------------------------------------- /src/entity.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (value, attrs) { 4 | 5 | this.value = null; 6 | this.attributes = null; 7 | 8 | if (typeof value === "undefined") { 9 | value = null; 10 | } 11 | 12 | if (typeof attrs === "undefined") { 13 | attrs = null; 14 | } 15 | 16 | this.value = value; 17 | this.attributes = attrs; 18 | }; 19 | -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function NeonError(message, line, column) { 4 | this.name = this.constructor.name; 5 | this.message = message + " on line " + line + ", column " + column + "."; 6 | this.line = line; 7 | this.column = column; 8 | this.constructor.prototype.__proto__ = Error.prototype; 9 | if (typeof Error.captureStackTrace !== "undefined") { 10 | Error.captureStackTrace(this, this.constructor); 11 | } 12 | } 13 | module.exports = NeonError; 14 | -------------------------------------------------------------------------------- /src/map.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Map() { 4 | this._items = []; 5 | this._indexes = {}; 6 | this._key = 0; 7 | this.length = 0; 8 | 9 | this.set = function (key, value, replace) { 10 | if (typeof replace === "undefined") { 11 | replace = true; 12 | } 13 | if (key === null) { 14 | key = this._key++; 15 | } else { 16 | var number = parseInt(key * 1); 17 | if (!isNaN(number) && number >= this._key) { 18 | this._key = number + 1; 19 | } 20 | } 21 | if (typeof this._indexes[key] === "undefined") { 22 | this._items[this.length] = {key: key, value: value}; 23 | this._indexes[key] = this.length; 24 | this.length++; 25 | return 1; 26 | } else if (replace) { 27 | this._items[this._indexes[key]].value = value; 28 | return 0; 29 | } else { 30 | return -1; 31 | } 32 | }; 33 | 34 | this.add = function (key, value) { 35 | return this.set(key, value, false) === 1; 36 | }; 37 | 38 | this.get = function (key) { 39 | if (!this.has(key)) { 40 | throw new Error("Item with key " + key + " not exists"); 41 | } 42 | return this._items[this._indexes[key]].value; 43 | }; 44 | 45 | this.last = function () { 46 | if (this.length === 0) { 47 | throw new Error("Map has no items"); 48 | } 49 | return this._items[this.length - 1]; 50 | }; 51 | 52 | this.has = function (key) { 53 | return typeof this._indexes[key] !== "undefined"; 54 | }; 55 | 56 | this.remove = function (key) { 57 | if (!this.has(key)) { 58 | throw new Error("Item with key " + key + " not exists"); 59 | } 60 | delete this._items[this._indexes[key]]; 61 | delete this._indexes[key]; 62 | this.length--; 63 | }; 64 | 65 | this.items = function () { 66 | return this._items; 67 | }; 68 | 69 | this.keys = function () { 70 | var keys = []; 71 | for (var i in this._items) { 72 | keys.push(this._items[i].key); 73 | } 74 | return keys; 75 | }; 76 | 77 | this.values = function () { 78 | var values = []; 79 | for (var i in this._items) { 80 | values.push(this._items[i].value); 81 | } 82 | return values; 83 | }; 84 | 85 | this.toObject = function (deep) { 86 | if (typeof deep === "undefined") { 87 | deep = false; 88 | } 89 | var result = {}; 90 | for (var i in this._items) { 91 | var value = this._items[i].value; 92 | result[this._items[i].key] = value instanceof Map && deep ? value.toObject(true) : value; 93 | } 94 | 95 | return result; 96 | }; 97 | 98 | this.forEach = function (callable) { 99 | for (var i in this._items) { 100 | callable(this._items[i].key, this._items[i].value); 101 | } 102 | }; 103 | 104 | this.isList = function () { 105 | return (function(items) { 106 | var cmp = 0; 107 | for(var i in items) { 108 | if(cmp !== items[i].key) { 109 | return false; 110 | } 111 | cmp = items[i].key + 1; 112 | } 113 | return true; 114 | })(this.items()); 115 | }; 116 | 117 | } 118 | Map.fromObject = function (obj) { 119 | var mapInst = new Map; 120 | for (var key in obj) { 121 | mapInst.set(key, obj[key]); 122 | } 123 | return mapInst; 124 | }; 125 | 126 | Map.fromArray = function (obj) { 127 | var mapInst = new Map; 128 | for (var i in obj) { 129 | mapInst.set(null, obj[i]); 130 | } 131 | return mapInst; 132 | }; 133 | 134 | module.exports = Map; 135 | -------------------------------------------------------------------------------- /src/neon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EncoderClass = require("./encoder"); 4 | var DecoderClass = require("./decoder"); 5 | var encode = function (xvar, options) { 6 | if (typeof options === "undefined") { 7 | options = null; 8 | } 9 | 10 | var encoder = new EncoderClass(); 11 | return encoder.encode(xvar, options); 12 | }; 13 | 14 | 15 | module.exports.encode = encode; 16 | module.exports.decode = function (input, outputFormat) { 17 | if (typeof outputFormat ==="undefined") { 18 | outputFormat = DecoderClass.MAP; 19 | } 20 | var decoder = new DecoderClass(outputFormat); 21 | 22 | return decoder.decode(input); 23 | }; 24 | module.exports.Entity = require('./entity'); 25 | module.exports.Map = require('./map'); 26 | module.exports.CHAIN = DecoderClass.CHAIN; 27 | module.exports.OUTPUT_MAP = DecoderClass.MAP; 28 | module.exports.OUTPUT_OBJECT = DecoderClass.FORCE_OBJECT; 29 | module.exports.OUTPUT_AUTO = DecoderClass.AUTO; 30 | module.exports.BLOCK = EncoderClass.BLOCK; 31 | module.exports.Dumper = require('./dumper'); 32 | module.exports.Error = require('./error'); 33 | -------------------------------------------------------------------------------- /test/Decoder.errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var neon = require('../src/neon'); 5 | suite('Decoder.errors', function () { 6 | test('multiline string', function () { 7 | assert.throws(function () { 8 | neon.decode('Hello\nWorld') 9 | }, /Unexpected \'World\' on line 2, column 1./); 10 | }); 11 | test('bad comma', function () { 12 | assert.throws(function () { 13 | neon.decode('- Dave,\n- Rimmer,\n- Kryten,\n') 14 | }, /Unexpected ',' on line 1, column 7./); 15 | }); 16 | test('bad comma 2', function () { 17 | assert.throws(function () { 18 | neon.decode('item [a, b]') 19 | }, /Unexpected ',' on line 1, column 8./); 20 | }); 21 | test('bad comma 3', function () { 22 | assert.throws(function () { 23 | neon.decode('{, }') 24 | }, /Unexpected ',' on line 1, column 2./); 25 | }); 26 | test('bad comma 4', function () { 27 | assert.throws(function () { 28 | neon.decode('{a, ,}') 29 | }, /Unexpected ',' on line 1, column 5./); 30 | }); 31 | test('"', function () { 32 | assert.throws(function () { 33 | neon.decode('"') 34 | }, /Unexpected '\"' on line 1, column 1./); 35 | }); 36 | test('mix of tabs and spaces', function () { 37 | assert.throws(function () { 38 | neon.decode('\ta:\n b"') 39 | }, /Invalid combination of tabs and spaces on line 2, column 2./); 40 | }); 41 | test('bad indentation 1', function () { 42 | assert.throws(function () { 43 | neon.decode( 44 | '\n' + 45 | 'a: \n' + 46 | ' b:\n' + 47 | ' c: x\n') 48 | }, /Bad indentation on line 4, column 2./); 49 | }); 50 | test('bad indentation 2', function () { 51 | assert.throws(function () { 52 | neon.decode( 53 | '\n' + 54 | 'a: 1\n' + 55 | ' b:\n' 56 | ) 57 | }, /Bad indentation on line 3, column 3./); 58 | }); 59 | test('bad indentation 3', function () { 60 | assert.throws(function () { 61 | neon.decode( 62 | '\n' + 63 | '- x:\n' + 64 | ' a: 10\n') 65 | }, /Bad indentation on line 3, column 2./); 66 | }); 67 | test('bad indentation 4', function () { 68 | assert.throws(function () { 69 | neon.decode( 70 | '\n' + 71 | '- x: 20\n' + 72 | ' a: 10\n') 73 | }, /Bad indentation on line 3, column 4./); 74 | }); 75 | test('bad indentation 5', function () { 76 | assert.throws(function () { 77 | neon.decode( 78 | '\n' + 79 | '- x: 20\n' + 80 | ' a: 10\n') 81 | }, /Bad indentation on line 3, column 2./); 82 | }); 83 | test('bad indentation 4', function () { 84 | assert.throws(function () { 85 | neon.decode('- x: y:') 86 | }, /Unexpected ':' on line 1, column 7./); 87 | }); 88 | 89 | test('array-after-key scalar', function() { 90 | assert.throws(function () { 91 | neon.decode('\n' + 92 | 'foo:\n' + 93 | 'bar\n') 94 | }, /Unexpected '' on line 3, column 4./); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test/Decoder.inline.array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var assertNeon = require('../src/assert'); 5 | var neon = require('../src/neon'); 6 | suite('Decoder.inline.array', function () { 7 | test('{"foo": "bar"}', function () { 8 | assertNeon.equal(neon.decode('{"foo":"bar"}'), { 9 | foo: "bar" 10 | }); 11 | }); 12 | test('true, false, null constants', function () { 13 | assertNeon.equal(neon.decode('[true, tRuE, TRUE, false, FALSE, yes, YES, no, NO, null, NULL,]'), { 14 | 0: true, 1: 'tRuE', 2: true, 3: false, 4: false, 5: true, 6: true, 7: false, 8: false, 9: null, 10: null 15 | }) 16 | }); 17 | test('on, off, false, numbers', function () { 18 | assertNeon.equal(neon.decode('{false: off, "on": true, -5: 1, 5.3: 1}'), { 19 | "false": false, 20 | "on": true, 21 | "-5": 1, 22 | "5.3": 1 23 | }); 24 | }); 25 | 26 | test('long inline', function () { 27 | assertNeon.equal(neon.decode('{a, b, {c: d}, e: f, g:,h:}'), { 28 | 0: "a", 29 | 1: "b", 30 | 2: {c: "d"}, 31 | e: "f", 32 | g: null, 33 | h: null 34 | }) 35 | }); 36 | 37 | test('5', function () { 38 | assertNeon.equal(neon.decode("{a,\nb\nc: 1,\nd: 1,\n\ne: 1\nf:\n}"), { 39 | 0: "a", 40 | 1: "b", 41 | c: 1, 42 | d: 1, 43 | e: 1, 44 | f: null 45 | }); 46 | }); 47 | 48 | test('5 - crlf', function () { 49 | assertNeon.equal(neon.decode("{a,\r\nb\r\nc: 1,\r\nd: 1,\r\n\r\ne: 1\r\nf:\r\n}"), { 50 | 0: "a", 51 | 1: "b", 52 | c: 1, 53 | d: 1, 54 | e: 1, 55 | f: null 56 | }); 57 | }); 58 | 59 | test('entity 1', function () { 60 | assert.ok(neon.decode("@item(a, b)") instanceof neon.Entity); 61 | }); 62 | test('entity 2', function () { 63 | assertNeon.equal(neon.decode("@item(a, b)"), new neon.Entity("@item", {0: "a", 1: "b"})); 64 | }); 65 | test('entity 3', function () { 66 | assertNeon.equal(neon.decode("@item(a, b)"), new neon.Entity("@item", {0: "a", 1: "b"})); 67 | }); 68 | test('entity 4', function () { 69 | assertNeon.equal(neon.decode("@item (a, b)"), new neon.Entity("@item", {0: "a", 1: "b"})); 70 | }); 71 | test('entity 5', function () { 72 | assertNeon.equal(neon.decode("[]()"), new neon.Entity({}, {})); 73 | }); 74 | test('entity 6', function () { 75 | assertNeon.equal(neon.decode("first(a, b)second"), new neon.Entity(neon.CHAIN, { 76 | 0: new neon.Entity("first", {0: "a", 1: "b"}), 77 | 1: new neon.Entity("second") 78 | })); 79 | }); 80 | test('entity 7', function () { 81 | assertNeon.equal(neon.decode("first(a, b)second(1,2)"), new neon.Entity(neon.CHAIN, { 82 | 0: new neon.Entity("first", {0: "a", 1: "b"}), 83 | 1: new neon.Entity("second", {0: 1, 1: 2}) 84 | })); 85 | }); 86 | test('entity 8', function () { 87 | assertNeon.equal(neon.decode("first(a, b)second(1,2)third(x: foo, y=bar)"), new neon.Entity(neon.CHAIN, { 88 | 0: new neon.Entity("first", {0: "a", 1: "b"}), 89 | 1: new neon.Entity("second", {0: 1, 1: 2}), 90 | 2: new neon.Entity("third", {x: "foo", y: "bar"}) 91 | })); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/Decoder.scalar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var neon = require('../src/neon'); 5 | 6 | suite('Decoder.scalar', function () { 7 | test('null', function () { 8 | assert.strictEqual(neon.decode(''), null); 9 | }); 10 | test('null 2', function () { 11 | assert.strictEqual(neon.decode(' '), null); 12 | }); 13 | test('int 0', function () { 14 | assert.strictEqual(neon.decode('0'), 0); 15 | }); 16 | test('float 0.0 (as int)', function () { 17 | assert.strictEqual(neon.decode('0.0'), 0); 18 | }); 19 | test('int 1', function () { 20 | assert.strictEqual(neon.decode('1'), 1); 21 | }); 22 | test('float -1.2', function () { 23 | assert.strictEqual(neon.decode('-1.2'), -1.2); 24 | }); 25 | test('float -1.2e2', function () { 26 | assert.strictEqual(neon.decode('-1.2e2'), -120); 27 | }); 28 | test('true', function () { 29 | assert.ok(neon.decode('true') === true); 30 | }); 31 | test('false', function () { 32 | assert.ok(neon.decode('false') === false); 33 | }); 34 | test('null string', function () { 35 | assert.ok(neon.decode('null') === null); 36 | }); 37 | test('literal 1', function () { 38 | assert.strictEqual(neon.decode('the"string#literal'), 'the"string#literal'); 39 | }); 40 | test('literal 2', function () { 41 | assert.strictEqual(neon.decode('the"string #literal'), 'the"string'); 42 | }); 43 | test('literal 3', function () { 44 | assert.strictEqual(neon.decode('"the\'string #literal"'), "the'string #literal"); 45 | }); 46 | test('literal 4', function () { 47 | assert.strictEqual(neon.decode("'the\"string #literal'"), 'the"string #literal'); 48 | }); 49 | test('literal 5', function () { 50 | assert.strictEqual(neon.decode('"the\\"string #literal"'), 'the"string #literal'); 51 | }); 52 | test('literal 5', function () { 53 | assert.strictEqual(neon.decode(" "), " "); 54 | }); 55 | test('empty string 1', function () { 56 | assert.strictEqual(neon.decode("''"), ""); 57 | }); 58 | test('empty string 2', function () { 59 | assert.strictEqual(neon.decode('""'), ""); 60 | }); 61 | test('string :a', function () { 62 | assert.strictEqual(neon.decode(':a'), ":a"); 63 | }); 64 | test('char x', function () { 65 | assert.strictEqual(neon.decode('x'), "x"); 66 | }); 67 | test('char x 2', function () { 68 | assert.strictEqual(neon.decode('\nx\n'), "x"); 69 | }); 70 | test('char x 3', function () { 71 | assert.strictEqual(neon.decode(' x'), "x"); 72 | }); 73 | test('@x', function () { 74 | assert.strictEqual(neon.decode('@x'), "@x"); 75 | }); 76 | test('@true', function () { 77 | assert.strictEqual(neon.decode('@true'), "@true"); 78 | }); 79 | test('date', function () { 80 | assert.deepEqual(neon.decode('2014-05-20'), new Date("2014-05-20")); 81 | }); 82 | test('spaaace', function () { 83 | assert.strictEqual(neon.decode('a '), "a"); 84 | }); 85 | test('BOM!', function () { 86 | assert.strictEqual(neon.decode('\xEF\xBB\xBFa'), "a"); 87 | }); 88 | test('unicode', function () { 89 | assert.strictEqual(neon.decode('"\\u0040"'), '@'); 90 | assert.strictEqual(neon.decode('"\\u011B"'), "\u011B"); 91 | assert.strictEqual(neon.decode('"\\uD834\\uDF06"'), '\uD834\uDF06'); 92 | 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /test/Decored.array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assertNeon = require('../src/assert'); 4 | var assert = require('assert'); 5 | var neon = require('../src/neon'); 6 | 7 | suite('Decoder.array', function () { 8 | 9 | test('empty list', function () { 10 | assertNeon.equal(neon.decode( 11 | "a: []\n" 12 | ), { 13 | a: {} 14 | }) 15 | }); 16 | test('empty value', function () { 17 | assertNeon.equal(neon.decode( 18 | "a: \n" + 19 | "b: 1\n" 20 | ), { 21 | a: null, 22 | b: 1 23 | }) 24 | }); 25 | test('1', function () { 26 | assertNeon.equal(neon.decode( 27 | "\n" + 28 | "a: {1, 2, }\n" + 29 | "b: 1" 30 | ), { 31 | a: {0: 1, 1: 2}, 32 | b: 1 33 | }); 34 | }); 35 | test('2', function () { 36 | assertNeon.equal(neon.decode( 37 | " a: 1\n" + 38 | " b: 2" 39 | ), { 40 | a: 1, 41 | b: 2 42 | }); 43 | }); 44 | test('3', function () { 45 | assertNeon.equal(neon.decode( 46 | "\n" + 47 | "a: x\n" + 48 | "- x" 49 | ), { 50 | a: "x", 51 | 0: "x" 52 | }); 53 | }); 54 | test('4', function () { 55 | assertNeon.equal(neon.decode( 56 | "\n" + 57 | "- x\n" + 58 | "a: x" 59 | ), { 60 | 0: "x", 61 | a: "x" 62 | }); 63 | }); 64 | test('5', function () { 65 | assertNeon.equal(neon.decode( 66 | "\n" + 67 | "a:\n" + 68 | "- 1\n" + 69 | "-\n" + 70 | " - 2\n" + 71 | "b:\n" + 72 | "- 3\n" + 73 | "c: null\n" + 74 | "- 4" 75 | ), { 76 | a: {0: 1, 1: {0: 2}}, 77 | b: {0: 3}, 78 | c: null, 79 | 0: 4 80 | }); 81 | }); 82 | test('5', function () { 83 | assertNeon.equal(neon.decode("\n" + 84 | "x:\n" + 85 | " - x\n" + 86 | " a: x\n" 87 | ), { 88 | x: {0: "x", a: "x"} 89 | }); 90 | }); 91 | test('6', function () { 92 | assertNeon.equal(neon.decode( 93 | "x:\n" + 94 | " y:\n" + 95 | " -\n" + 96 | "a: x" 97 | ), { 98 | x: {y: {0: null}}, 99 | a: "x" 100 | }); 101 | }); 102 | test('7', function () { 103 | assertNeon.equal(neon.decode( 104 | "\n" + 105 | "x: {\n" + 106 | " a: 1\n" + 107 | "b: 2\n" + 108 | "}\n" 109 | ), { 110 | x: {a: 1, b: 2} 111 | }); 112 | }); 113 | test('8', function () { 114 | assertNeon.equal(neon.decode( 115 | "\n" + 116 | "{\n" + 117 | " one\n" + 118 | "two\n" + 119 | "}\n" 120 | ), { 121 | 0: "one", 122 | 1: "two" 123 | }); 124 | }); 125 | test('9', function () { 126 | assertNeon.equal(neon.decode( 127 | "\n" + 128 | "- x: 20\n" + 129 | " - a: 10\n" + 130 | " b: 10\n" + 131 | "- arr:\n" + 132 | " - 10\n" + 133 | " - 20\n" + 134 | "- y\n" 135 | ), { 136 | 0: {x: 20, 0: {a: 10, b: 10}}, 137 | 1: {arr: {0: 10, 1: 20}}, 138 | 2: "y" 139 | }); 140 | }); 141 | test('10', function () { 142 | assertNeon.equal(neon.decode( 143 | "\n" + 144 | "root:\n" + 145 | "\t- key1:\n" + 146 | "\t key3: 123\n" + 147 | "\t" 148 | ), { 149 | root: {0: {key1: null, key3: 123}} 150 | }) 151 | }); 152 | test('11', function () { 153 | assertNeon.equal(neon.decode( 154 | "\n" + 155 | "- x:\n" + 156 | " a: 10\n" 157 | ), { 158 | 0: {x: {a: 10}} 159 | }); 160 | }); 161 | test('12', function () { 162 | assertNeon.equal(neon.decode( 163 | "\n" + 164 | "x:\n" + 165 | "\t a: 10\n" + 166 | "y:\n" + 167 | " \tb: 20\n" 168 | ), { 169 | x: {a: 10}, 170 | y: {b: 20} 171 | }) 172 | }); 173 | test('13', function() { 174 | assertNeon.equal(neon.decode( 175 | "\n" + 176 | "- {null= 42}\n" + 177 | "null : 42\n" 178 | ), { 179 | 0: {"null": 42}, 180 | "null": 42 181 | }) 182 | }); 183 | 184 | test('empty-line-before-value: 1', function() { 185 | assertNeon.equal(neon.decode( 186 | "\n" + 187 | "x:\n" + 188 | "\ty\n" 189 | ), { 190 | x: "y" 191 | }) 192 | }); 193 | 194 | test('empty-line-before-value: 2', function () { 195 | assertNeon.equal(neon.decode( 196 | "\n" + 197 | "-\n" + 198 | "\tx:\n" + 199 | "\t y\n" 200 | ), { 201 | 0: {x: "y"} 202 | }) 203 | }); 204 | test('empty-line-before-value: 3', function () { 205 | assertNeon.equal(neon.decode( 206 | "\n" + 207 | "x:\n" + 208 | " [1, 2, 3]\n" 209 | ), { 210 | x: {0: 1, 1: 2, 2: 3} 211 | }) 212 | }); 213 | test('empty-line-before-value: 4', function () { 214 | assertNeon.equal(neon.decode( 215 | "\n" + 216 | "-\n" + 217 | " a\n" 218 | ), { 219 | 0: "a" 220 | }) 221 | }); 222 | 223 | test('output-map', function() { 224 | assert.ok(neon.decode("foo: bar") instanceof neon.Map); 225 | }); 226 | 227 | test('output-object', function() { 228 | assert.deepEqual(neon.decode( 229 | "\n" + 230 | "foo: {bar: lorem, ipsum}\n" + 231 | "- [dolor]", neon.OUTPUT_OBJECT 232 | ), { 233 | foo: {bar: "lorem", 0: "ipsum"}, 234 | 0: {0: "dolor"} 235 | }); 236 | }); 237 | 238 | test('output-auto', function() { 239 | assert.deepEqual(neon.decode( 240 | "\n" + 241 | "- {foo: bar}\n" + 242 | "- [lorem, 2: ipsum]\n" + 243 | "- dolor\n", neon.OUTPUT_AUTO 244 | ), [{foo: "bar"}, {0: "lorem", 2: "ipsum"}, "dolor"]); 245 | }); 246 | 247 | test('empty-arrays', function () { 248 | assertNeon.equal(neon.decode( 249 | "\n" + 250 | "one:\n" + 251 | "two:\n" 252 | ), { 253 | one: null, 254 | two: null 255 | }); 256 | }); 257 | }); 258 | -------------------------------------------------------------------------------- /test/Encoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var neon = require('../src/neon'); 5 | 6 | suite('Encoder', function () { 7 | 8 | test('true, false, null constants and strings', function () { 9 | assert.strictEqual(neon.encode([ 10 | true, 'TRUE', 'tRuE', 'true', 11 | false, 'FALSE', 'fAlSe', 'false', 12 | null, 'NULL', 'nUlL', 'null', 13 | 'yes', 'no', 'on', 'off' 14 | ]), '[true, "TRUE", "tRuE", "true", false, "FALSE", "fAlSe", "false", null, "NULL", "nUlL", "null", "yes", "no", "on", "off"]'); 15 | }); 16 | test('numbers', function () { 17 | assert.strictEqual(neon.encode([1, 1.0, 0, 0.0, -1, -1.2, '1', '1.0', '-1']), '[1, 1, 0, 0, -1, -1.2, "1", "1.0", "-1"]'); 18 | }); 19 | test('symbols', function () { 20 | assert.strictEqual(neon.encode(['[', ']', '{', '}', ':', ': ', '=', '#']), '["[", "]", "{", "}", ":", ": ", "=", "#"]'); 21 | }); 22 | test('list', function () { 23 | assert.strictEqual(neon.encode([1, 2, 3]), '[1, 2, 3]'); 24 | }); 25 | test('object', function () { 26 | assert.strictEqual(neon.encode({1: 1, 2: 2, 3: 3}), '{"1": 1, "2": 2, "3": 3}'); 27 | }); 28 | 29 | test('list with missing key', function () { 30 | var arr = []; 31 | arr[1] = 1; 32 | arr[2] = 2; 33 | arr[3] = 3; 34 | assert.strictEqual(neon.encode(arr), '{1: 1, 2: 2, 3: 3}'); 35 | }); 36 | 37 | test('object and array', function () { 38 | assert.strictEqual(neon.encode({foo: 1, bar: [2, 3]}), '{foo: 1, bar: [2, 3]}'); 39 | }); 40 | 41 | test('map with list', function () { 42 | assert.strictEqual(neon.encode(neon.Map.fromArray([1, 2, 3])), '[1, 2, 3]'); 43 | }); 44 | test('map with assoc array', function () { 45 | assert.strictEqual(neon.encode(neon.Map.fromObject({1: 1, "foo": 2})), '{"1": 1, foo: 2}'); 46 | }); 47 | test('map', function () { 48 | var map = new neon.Map; 49 | map.set(1, 1); 50 | map.set("foo", 2); 51 | assert.strictEqual(neon.encode(map), '{1: 1, foo: 2}'); 52 | }); 53 | 54 | test('entity 1', function () { 55 | assert.strictEqual(neon.encode(neon.decode('item(a, b)')), 'item(a, b)'); 56 | }); 57 | test('entity 2', function () { 58 | assert.strictEqual(neon.encode(neon.decode('item(a, b)')), 'item(a, b)'); 59 | }); 60 | test('entity 3', function () { 61 | assert.strictEqual(neon.encode(neon.decode('item(foo: a, bar: b)')), 'item(foo: a, bar: b)'); 62 | }); 63 | 64 | test('entity 4', function () { 65 | assert.strictEqual(neon.encode(neon.decode('[]()')), '[]()'); 66 | }); 67 | test('entity 5', function () { 68 | assert.strictEqual(neon.encode(neon.decode('item(a, foo: b)')), 'item(0: a, foo: b)'); 69 | }); 70 | 71 | test('entity 6', function () { 72 | var entity = new neon.Entity("ent"); 73 | entity.attributes = null; 74 | assert.strictEqual(neon.encode(entity), 'ent()'); 75 | }); 76 | 77 | test('block', function () { 78 | assert.strictEqual(neon.encode(["foo", "bar"], neon.BLOCK), "- foo\n" + 79 | "- bar\n"); 80 | }); 81 | test('block 2', function () { 82 | assert.strictEqual(neon.encode({x: "foo", y: "bar"}, neon.BLOCK), "x: foo\n" + 83 | "y: bar\n"); 84 | }); 85 | test('block 3', function () { 86 | assert.strictEqual(neon.encode({x: "foo", y: [1, 2]}, neon.BLOCK), "x: foo\n" + 87 | "y:\n" + 88 | " - 1\n" + 89 | " - 2\n"); 90 | }); 91 | test('block 5', function () { 92 | assert.strictEqual(neon.encode({x: "foo", y: [1, 2]}, neon.BLOCK), "x: foo\n" + 93 | "y:\n" + 94 | " - 1\n" + 95 | " - 2\n"); 96 | }); 97 | test('block 6', function () { 98 | assert.strictEqual(neon.encode({a: {foo1: {lorem: 1}, foo2: {lorem: 2}}}, neon.BLOCK), "a:\n" + 99 | " foo1:\n" + 100 | " lorem: 1\n" + 101 | " foo2:\n" + 102 | " lorem: 2\n"); 103 | }); 104 | 105 | test('sentence', function () { 106 | assert.strictEqual(neon.encode("Sed tempus eu tortor sagittis commodo. Phasellus luctus pharetra lectus, at vulputate ex."), 107 | "\"Sed tempus eu tortor sagittis commodo. Phasellus luctus pharetra lectus, at vulputate ex.\"") 108 | }); 109 | 110 | test('sentence no comma', function () { 111 | assert.strictEqual(neon.encode("Sed tempus eu tortor sagittis commodo. Phasellus luctus pharetra lectus at vulputate ex."), 112 | "Sed tempus eu tortor sagittis commodo. Phasellus luctus pharetra lectus at vulputate ex.") 113 | }); 114 | 115 | }); 116 | -------------------------------------------------------------------------------- /test/Map.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var Map = require('../src/map'); 5 | 6 | suite('Map', function () { 7 | 8 | var testItems = [[0, "a"], ["xx", "b"], [5, "c"], [1, "d"]]; 9 | 10 | test('set', function () { 11 | var mapInst = new Map(); 12 | mapInst.set("foo", "bar"); 13 | assert.strictEqual(mapInst.get("foo"), "bar"); 14 | assert.strictEqual(mapInst.length, 1); 15 | }); 16 | test('add', function () { 17 | var mapInst = new Map(); 18 | assert.ok(mapInst.add("foo", "bar")); 19 | assert.ok(!mapInst.add("foo", "bar")); 20 | }); 21 | 22 | test('remove', function () { 23 | var mapInst = new Map(); 24 | mapInst.set("foo", "bar"); 25 | assert.ok(mapInst.has("foo")); 26 | mapInst.remove("foo"); 27 | assert.ok(!mapInst.has("foo")); 28 | assert.strictEqual(mapInst.length, 0); 29 | }); 30 | test('values, keys, items', function () { 31 | var mapInst = new Map(); 32 | for (var i in testItems) { 33 | mapInst.set(testItems[i][0], testItems[i][1]); 34 | } 35 | assert.strictEqual(mapInst.values().length, 4); 36 | assert.strictEqual(mapInst.keys().length, 4); 37 | assert.strictEqual(mapInst.items().length, 4); 38 | }); 39 | 40 | test('order', function () { 41 | var mapInst = new Map(); 42 | for (var i in testItems) { 43 | mapInst.set(testItems[i][0], testItems[i][1]); 44 | } 45 | var values = mapInst.values(); 46 | for (var i in values) { 47 | assert.strictEqual(values[i], testItems[i][1]); 48 | } 49 | var keys = mapInst.keys(); 50 | for (var i in keys) { 51 | assert.strictEqual(keys[i], testItems[i][0]); 52 | } 53 | }); 54 | 55 | test('foreach', function () { 56 | var mapInst = new Map(); 57 | for (var i in testItems) { 58 | mapInst.set(testItems[i][0], testItems[i][1]); 59 | } 60 | var i = 0; 61 | mapInst.forEach(function (key, value) { 62 | assert.strictEqual(key, testItems[i][0]); 63 | assert.strictEqual(value, testItems[i][1]); 64 | i++; 65 | }); 66 | }); 67 | 68 | test('toObject', function () { 69 | var mapInst = new Map(); 70 | for (var i in testItems) { 71 | mapInst.set(testItems[i][0], testItems[i][1]); 72 | } 73 | assert.deepEqual(mapInst.toObject(), {0: "a", xx: "b", 5: "c", 1: "d"}); 74 | }); 75 | test('deep toObject', function () { 76 | var mapInst = new Map(); 77 | var nested = new Map(); 78 | nested.set("bar", 1); 79 | mapInst.set("foo", nested); 80 | assert.deepEqual(mapInst.toObject(true), {foo: {bar: 1}}); 81 | }); 82 | 83 | test('number index', function () { 84 | var mapInst = new Map(); 85 | mapInst.set(0, "a"); 86 | mapInst.set(null, "b"); 87 | assert.deepEqual(mapInst.toObject(), {0: "a", 1: "b"}); 88 | }); 89 | }); 90 | --------------------------------------------------------------------------------