@nette/neon 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/editing.js CHANGED
@@ -34,12 +34,22 @@ export function setItemValue(item, value) {
34
34
  else {
35
35
  replaceValue(item, styledScalar(old, value) ?? Builder.value(value, placeOf(item, old, value)));
36
36
  }
37
- guardDashSubblocks(item.parent);
37
+ guardGrammar(item.parent);
38
38
  }
39
39
  /** Puts the node into the value slot or empties it, without the comparison and styling of setItemValue(). */
40
40
  export function setItemNode(item, node) {
41
41
  replaceValue(item, node);
42
- guardDashSubblocks(item.parent);
42
+ guardGrammar(item.parent);
43
+ }
44
+ /** Repairs what the grammar would read differently in the array after an edit. */
45
+ function guardGrammar(array) {
46
+ if (array instanceof BlockArrayNode) {
47
+ guardEnd(array);
48
+ guardDashSubblocks(array);
49
+ }
50
+ else if (array instanceof InlineArrayNode) {
51
+ guardKeysWithoutValue(array);
52
+ }
43
53
  }
44
54
  /**
45
55
  * A dash right after a key at the same indentation reads as a dash subblock of that key.
@@ -47,10 +57,6 @@ export function setItemNode(item, node) {
47
57
  * subblock gets its key written out, which ends the subblock.
48
58
  */
49
59
  function guardDashSubblocks(block) {
50
- if (!(block instanceof BlockArrayNode)) {
51
- return;
52
- }
53
- guardEnd(block);
54
60
  block.items.forEach((item, i) => {
55
61
  const next = block.items[i + 1];
56
62
  if (!item.key || !next?.bullet) {
@@ -64,6 +70,15 @@ function guardDashSubblocks(block) {
64
70
  }
65
71
  });
66
72
  }
73
+ /** Keeps an item without a key from reading as the value of a key without a value on the line above. */
74
+ function guardKeysWithoutValue(array) {
75
+ array.items.forEach((item, i) => {
76
+ const next = array.items[i + 1];
77
+ if (item.key && item.value === null && !item.separator && next && !next.key) {
78
+ replaceValue(item, literal('null'));
79
+ }
80
+ });
81
+ }
67
82
  /** Spaces on the last line of the input would be read as the indentation of a missing value at the end. */
68
83
  function guardEnd(at) {
69
84
  let root = at;
@@ -292,7 +307,7 @@ export function addItem(array, key, value, position) {
292
307
  ? addToBlock(array, key, value, at)
293
308
  : addToInline(array, key, value, at);
294
309
  keepKeys(array, before, key === null);
295
- guardDashSubblocks(array);
310
+ guardGrammar(array);
296
311
  return item;
297
312
  }
298
313
  /** The keys of the items before an edit, and whether they made a list. */
@@ -464,7 +479,7 @@ export function insertItem(array, item, at) {
464
479
  }
465
480
  }
466
481
  keepKeys(array, before, key === null);
467
- guardDashSubblocks(array);
482
+ guardGrammar(array);
468
483
  }
469
484
  /**
470
485
  * The comments an item owns at its edges, which an inline array cannot keep in place: the comment lines
@@ -643,7 +658,7 @@ export function removeItem(item, comments, nullable, mergeBlankLines = false) {
643
658
  }
644
659
  array[setItems](remaining);
645
660
  keepKeys(array, before, true); // an item removed from a list renumbers the rest, whatever its key
646
- guardDashSubblocks(array);
661
+ guardGrammar(array);
647
662
  }
648
663
  function removeFromBlock(item, comments, mergeBlankLines) {
649
664
  const { preamble, owned } = splitLeading(item.getFirstToken().leadingTrivia);
@@ -704,7 +719,7 @@ function emptyBlock(block, last, leaveNull) {
704
719
  if (!leaveNull) {
705
720
  replaceValue(owner, emptyArray(last.key ? '{}' : '[]'));
706
721
  }
707
- guardDashSubblocks(owner.parent);
722
+ guardGrammar(owner.parent);
708
723
  }
709
724
  /** The document lost its last item: the empty array, or nothing, keeps what stood above the item. */
710
725
  function emptyDocument(document, last, comments, leaveNull) {
@@ -806,7 +821,7 @@ export function setItemKey(item, key) {
806
821
  if (array && before) {
807
822
  before.keys.set(item, value);
808
823
  keepKeys(array, before, false);
809
- guardDashSubblocks(array);
824
+ guardGrammar(array);
810
825
  }
811
826
  }
812
827
  // ---- nodes in slots
@@ -993,8 +1008,9 @@ export function setByPath(document, path, value, create) {
993
1008
  setItemValue(existing, value);
994
1009
  return;
995
1010
  }
996
- else if (!create || typeof last === 'object') {
997
- throw new NeonError(`No item '${segmentText(last)}' in the path ${pathText(path)}, and create is false`);
1011
+ else if (!create || typeof last === 'object') { // an item by its order is never made
1012
+ const missing = `No item '${segmentText(last)}' in the path ${pathText(path)}`;
1013
+ throw new NeonError(create ? missing : missing + ', and create is false');
998
1014
  }
999
1015
  const key = String(last);
1000
1016
  if (array) {
package/dist/parser.js CHANGED
@@ -228,26 +228,47 @@ class Parser {
228
228
  const end = Pairs[open.kind];
229
229
  const items = [];
230
230
  const keys = new KeyCheck(this);
231
+ let next = null; // the key and the colon of the next item, read on the line after a key without a value
231
232
  while (true) {
232
- while (this.consume('Newline')) {
233
- ;
233
+ let head;
234
+ let colon;
235
+ if (next) {
236
+ [head, colon] = next;
237
+ next = null;
234
238
  }
235
- const close = this.consumeToken(end);
236
- if (close) {
237
- return new InlineArrayNode(open, items, close);
239
+ else {
240
+ while (this.consume('Newline')) {
241
+ ;
242
+ }
243
+ const close = this.consumeToken(end);
244
+ if (close) {
245
+ return new InlineArrayNode(open, items, close);
246
+ }
247
+ head = this.parseValue();
248
+ colon = this.consumeColon();
238
249
  }
239
- const head = this.parseValue();
240
250
  let item;
241
- const colon = this.consumeToken('Colon', 'Equals');
242
251
  if (colon) {
243
252
  const key = keys.add(head);
244
- const value = this.isNext('Newline', 'Comma', end) ? null : this.parseValue();
253
+ let value = null;
254
+ const lineEnd = this.consume('Newline');
255
+ if (!this.isNext('Comma', end)) {
256
+ value = this.parseValue();
257
+ const nextColon = lineEnd ? this.consumeColon() : null;
258
+ if (nextColon) {
259
+ next = [value, nextColon];
260
+ value = null;
261
+ }
262
+ }
245
263
  item = keys.item(null, key, colon, value);
246
264
  }
247
265
  else {
248
266
  item = keys.item(null, null, null, head);
249
267
  }
250
268
  items.push(item);
269
+ if (next) {
270
+ continue;
271
+ }
251
272
  const before = this.mark();
252
273
  while (this.consume('Newline')) {
253
274
  ;
@@ -264,6 +285,19 @@ class Parser {
264
285
  }
265
286
  }
266
287
  }
288
+ /** The colon or the equals sign after a key in an inline array, which may stand on a later line. */
289
+ consumeColon() {
290
+ const mark = this.mark();
291
+ while (this.consume('Newline')) {
292
+ ;
293
+ }
294
+ const colon = this.consumeToken('Colon', 'Equals');
295
+ if (!colon) {
296
+ this.#i = mark.i;
297
+ this.#newlineTaken = mark.newlineTaken;
298
+ }
299
+ return colon;
300
+ }
267
301
  /** One level deeper into arrays. */
268
302
  enter(token) {
269
303
  if (++this.#depth > MaxDepth) {
package/dist/values.d.ts CHANGED
@@ -37,7 +37,7 @@ export declare class NeonDate {
37
37
  static parse(text: string): NeonDate | null;
38
38
  /** Converts the date to an instant; a local date needs the time zone offset in minutes or 'UTC'. */
39
39
  toDate(zone?: 'UTC' | number): Date;
40
- /** The date as the encoder writes it. */
40
+ /** The date as the encoder writes it, without the fraction of a second. */
41
41
  toString(): string;
42
42
  /** Equality of components, including the presence of an offset and the written fraction. */
43
43
  equals(other: NeonDate): boolean;
package/dist/values.js CHANGED
@@ -93,15 +93,12 @@ export class NeonDate {
93
93
  res.setUTCHours(this.hour ?? 0, (this.minute ?? 0) - offset, this.second ?? 0, ms);
94
94
  return res;
95
95
  }
96
- /** The date as the encoder writes it. */
96
+ /** The date as the encoder writes it, without the fraction of a second. */
97
97
  toString() {
98
98
  const pad = (n, width = 2) => String(n).padStart(width, '0');
99
99
  let res = `${pad(this.year, 4)}-${pad(this.month)}-${pad(this.day)}`;
100
100
  if (this.hour !== null) {
101
101
  res += ` ${pad(this.hour)}:${pad(this.minute)}:${pad(this.second)}`;
102
- if (this.fraction) {
103
- res += '.' + this.fraction;
104
- }
105
102
  }
106
103
  if (this.offset) {
107
104
  res += ' ' + this.offset.replace(':', '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nette/neon",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "NEON parser, encoder and lossless editor: change a value in a config file and keep its comments and formatting.",
5
5
  "keywords": [
6
6
  "neon",
@@ -28,7 +28,7 @@
28
28
  }
29
29
  },
30
30
  "bin": {
31
- "neon-lint": "./bin/neon-lint.js"
31
+ "neon-lint": "bin/neon-lint.js"
32
32
  },
33
33
  "files": [
34
34
  "dist",
package/readme.md CHANGED
@@ -71,8 +71,9 @@ note: '''
71
71
  '''
72
72
  ```
73
73
 
74
- JSON is a subset of NEON. The format is described at [ne-on.org](https://ne-on.org) and in the
75
- [manual](https://doc.nette.org/en/neon/format).
74
+ JSON is a subset of NEON, except for a colon at the start of a line glued to its value
75
+ (`"a"` + new line + `:1`), which NEON reads as a literal. The format is described at
76
+ [ne-on.org](https://ne-on.org) and in the [manual](https://doc.nette.org/en/neon/format).
76
77
 
77
78
  ▶ Runnable examples: [examples/edge-cases](examples/edge-cases/)
78
79