├── .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 | [](https://travis-ci.org/matej21/neon-js)
5 | [](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 |
--------------------------------------------------------------------------------