links-notation 0.8.0
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/README.md +219 -0
- package/README.ru.md +193 -0
- package/bun.lock +33 -0
- package/dist/index.js +1462 -0
- package/package.json +26 -0
- package/src/Link.js +155 -0
- package/src/LinksGroup.js +30 -0
- package/src/Parser.js +132 -0
- package/src/grammar.pegjs +86 -0
- package/src/index.js +3 -0
- package/src/parser-generated.js +1322 -0
- package/tests/ApiTests.test.js +74 -0
- package/tests/EdgeCaseParser.test.js +162 -0
- package/tests/IndentedIdSyntax.test.js +171 -0
- package/tests/Link.test.js +69 -0
- package/tests/LinksGroup.test.js +40 -0
- package/tests/MixedIndentationModes.test.js +188 -0
- package/tests/MultilineParser.test.js +118 -0
- package/tests/MultilineQuotedString.test.js +78 -0
- package/tests/NestedParser.test.js +166 -0
- package/tests/SingleLineParser.test.js +229 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { Parser } from '../src/Parser.js';
|
|
3
|
+
import { Link } from '../src/Link.js';
|
|
4
|
+
|
|
5
|
+
const parser = new Parser();
|
|
6
|
+
|
|
7
|
+
test('test_is_ref equivalent', () => {
|
|
8
|
+
// JS doesn't have separate Ref/Link types, but we can test simple link behavior
|
|
9
|
+
const simpleLink = new Link('some_value');
|
|
10
|
+
expect(simpleLink.id).toBe('some_value');
|
|
11
|
+
expect(simpleLink.values).toEqual([]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('test_is_link equivalent', () => {
|
|
15
|
+
// Test link with values
|
|
16
|
+
const link = new Link('id', [new Link('child')]);
|
|
17
|
+
expect(link.id).toBe('id');
|
|
18
|
+
expect(link.values.length).toBe(1);
|
|
19
|
+
expect(link.values[0].id).toBe('child');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('test_empty_link', () => {
|
|
23
|
+
const link = new Link(null, []);
|
|
24
|
+
const output = link.toString();
|
|
25
|
+
expect(output).toBe('()');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('test_simple_link', () => {
|
|
29
|
+
const input = '(1: 1 1)';
|
|
30
|
+
const parsed = parser.parse(input);
|
|
31
|
+
|
|
32
|
+
// Validate regular formatting
|
|
33
|
+
const output = parsed[0].format();
|
|
34
|
+
const expected = '(1: 1 1)'; // JS format matches input
|
|
35
|
+
expect(output).toBe(expected);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('test_link_with_source_target', () => {
|
|
39
|
+
const input = '(index: source target)';
|
|
40
|
+
const parsed = parser.parse(input);
|
|
41
|
+
|
|
42
|
+
// Validate regular formatting
|
|
43
|
+
const output = parsed[0].format();
|
|
44
|
+
expect(output).toBe(input);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('test_link_with_source_type_target', () => {
|
|
48
|
+
const input = '(index: source type target)';
|
|
49
|
+
const parsed = parser.parse(input);
|
|
50
|
+
|
|
51
|
+
// Validate regular formatting
|
|
52
|
+
const output = parsed[0].format();
|
|
53
|
+
expect(output).toBe(input);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('test_single_line_format', () => {
|
|
57
|
+
const input = 'id: value1 value2';
|
|
58
|
+
const parsed = parser.parse(input);
|
|
59
|
+
|
|
60
|
+
// The parser should handle single-line format
|
|
61
|
+
const output = parsed[0].format(true); // lessParentheses mode
|
|
62
|
+
expect(output).toContain('id');
|
|
63
|
+
expect(output).toContain('value1');
|
|
64
|
+
expect(output).toContain('value2');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('test_quoted_references', () => {
|
|
68
|
+
const input = '("quoted id": "value with spaces")';
|
|
69
|
+
const parsed = parser.parse(input);
|
|
70
|
+
|
|
71
|
+
const output = parsed[0].format();
|
|
72
|
+
expect(output).toContain('quoted id');
|
|
73
|
+
expect(output).toContain('value with spaces');
|
|
74
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { Parser } from '../src/Parser.js';
|
|
3
|
+
import { formatLinks } from '../src/Link.js';
|
|
4
|
+
|
|
5
|
+
const parser = new Parser();
|
|
6
|
+
|
|
7
|
+
test('EmptyLinkTest', () => {
|
|
8
|
+
const source = ':';
|
|
9
|
+
// Standalone ':' is now forbidden and should throw an error
|
|
10
|
+
expect(() => parser.parse(source)).toThrow();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('EmptyLinkWithParenthesesTest', () => {
|
|
14
|
+
const source = '()';
|
|
15
|
+
const target = '()';
|
|
16
|
+
const links = parser.parse(source);
|
|
17
|
+
const formattedLinks = formatLinks(links);
|
|
18
|
+
expect(formattedLinks).toBe(target);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('EmptyLinkWithEmptySelfReferenceTest', () => {
|
|
22
|
+
const source = '(:)';
|
|
23
|
+
// '(:)' is now forbidden and should throw an error
|
|
24
|
+
expect(() => parser.parse(source)).toThrow();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('TestAllFeaturesTest', () => {
|
|
28
|
+
// Test single-line link with id
|
|
29
|
+
let input = 'id: value1 value2';
|
|
30
|
+
let result = parser.parse(input);
|
|
31
|
+
expect(result.length).toBeGreaterThan(0);
|
|
32
|
+
|
|
33
|
+
// Test multi-line link with id
|
|
34
|
+
input = '(id: value1 value2)';
|
|
35
|
+
result = parser.parse(input);
|
|
36
|
+
expect(result.length).toBeGreaterThan(0);
|
|
37
|
+
|
|
38
|
+
// Test link without id (single-line) - now forbidden
|
|
39
|
+
input = ': value1 value2';
|
|
40
|
+
expect(() => parser.parse(input)).toThrow();
|
|
41
|
+
|
|
42
|
+
// Test link without id (multi-line) - now forbidden
|
|
43
|
+
input = '(: value1 value2)';
|
|
44
|
+
expect(() => parser.parse(input)).toThrow();
|
|
45
|
+
|
|
46
|
+
// Test singlet link
|
|
47
|
+
input = '(singlet)';
|
|
48
|
+
result = parser.parse(input);
|
|
49
|
+
expect(result.length).toBe(1);
|
|
50
|
+
expect(result[0].id).toBe(null);
|
|
51
|
+
expect(result[0].values.length).toBe(1);
|
|
52
|
+
expect(result[0].values[0].id).toBe('singlet');
|
|
53
|
+
expect(result[0].values[0].values).toEqual([]);
|
|
54
|
+
|
|
55
|
+
// Test value link
|
|
56
|
+
input = '(value1 value2 value3)';
|
|
57
|
+
result = parser.parse(input);
|
|
58
|
+
expect(result.length).toBeGreaterThan(0);
|
|
59
|
+
|
|
60
|
+
// Test quoted references
|
|
61
|
+
input = '("id with spaces": "value with spaces")';
|
|
62
|
+
result = parser.parse(input);
|
|
63
|
+
expect(result.length).toBeGreaterThan(0);
|
|
64
|
+
|
|
65
|
+
// Test single-quoted references
|
|
66
|
+
input = "('id': 'value')";
|
|
67
|
+
result = parser.parse(input);
|
|
68
|
+
expect(result.length).toBeGreaterThan(0);
|
|
69
|
+
|
|
70
|
+
// Test nested links
|
|
71
|
+
input = '(outer: (inner: value))';
|
|
72
|
+
result = parser.parse(input);
|
|
73
|
+
expect(result.length).toBeGreaterThan(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('TestEmptyDocumentTest', () => {
|
|
77
|
+
const input = '';
|
|
78
|
+
// Empty document should return empty array
|
|
79
|
+
const result = parser.parse(input);
|
|
80
|
+
expect(result).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('TestWhitespaceOnlyTest', () => {
|
|
84
|
+
const input = ' \n \n ';
|
|
85
|
+
// Whitespace-only document should return empty array
|
|
86
|
+
const result = parser.parse(input);
|
|
87
|
+
expect(result).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('TestEmptyLinksTest', () => {
|
|
91
|
+
let input = '()';
|
|
92
|
+
let result = parser.parse(input);
|
|
93
|
+
expect(result.length).toBe(1);
|
|
94
|
+
expect(result[0].id).toBe(null);
|
|
95
|
+
expect(result[0].values).toEqual([]);
|
|
96
|
+
|
|
97
|
+
// '(:)' is now forbidden
|
|
98
|
+
input = '(:)';
|
|
99
|
+
expect(() => parser.parse(input)).toThrow();
|
|
100
|
+
|
|
101
|
+
input = '(id:)';
|
|
102
|
+
result = parser.parse(input);
|
|
103
|
+
expect(result.length).toBe(1);
|
|
104
|
+
expect(result[0].id).toBe('id');
|
|
105
|
+
expect(result[0].values).toEqual([]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('TestSingletLinksTest', () => {
|
|
109
|
+
// Test singlet (1)
|
|
110
|
+
let input = '(1)';
|
|
111
|
+
let result = parser.parse(input);
|
|
112
|
+
expect(result.length).toBe(1);
|
|
113
|
+
expect(result[0].id).toBe(null);
|
|
114
|
+
expect(result[0].values.length).toBe(1);
|
|
115
|
+
expect(result[0].values[0].id).toBe('1');
|
|
116
|
+
expect(result[0].values[0].values).toEqual([]);
|
|
117
|
+
|
|
118
|
+
// Test (1 2)
|
|
119
|
+
input = '(1 2)';
|
|
120
|
+
result = parser.parse(input);
|
|
121
|
+
expect(result.length).toBe(1);
|
|
122
|
+
expect(result[0].id).toBe(null);
|
|
123
|
+
expect(result[0].values.length).toBe(2);
|
|
124
|
+
expect(result[0].values[0].id).toBe('1');
|
|
125
|
+
expect(result[0].values[0].values).toEqual([]);
|
|
126
|
+
expect(result[0].values[1].id).toBe('2');
|
|
127
|
+
expect(result[0].values[1].values).toEqual([]);
|
|
128
|
+
|
|
129
|
+
// Test (1 2 3)
|
|
130
|
+
input = '(1 2 3)';
|
|
131
|
+
result = parser.parse(input);
|
|
132
|
+
expect(result.length).toBe(1);
|
|
133
|
+
expect(result[0].id).toBe(null);
|
|
134
|
+
expect(result[0].values.length).toBe(3);
|
|
135
|
+
expect(result[0].values[0].id).toBe('1');
|
|
136
|
+
expect(result[0].values[0].values).toEqual([]);
|
|
137
|
+
expect(result[0].values[1].id).toBe('2');
|
|
138
|
+
expect(result[0].values[1].values).toEqual([]);
|
|
139
|
+
expect(result[0].values[2].id).toBe('3');
|
|
140
|
+
expect(result[0].values[2].values).toEqual([]);
|
|
141
|
+
|
|
142
|
+
// Test (1 2 3 4)
|
|
143
|
+
input = '(1 2 3 4)';
|
|
144
|
+
result = parser.parse(input);
|
|
145
|
+
expect(result.length).toBe(1);
|
|
146
|
+
expect(result[0].id).toBe(null);
|
|
147
|
+
expect(result[0].values.length).toBe(4);
|
|
148
|
+
expect(result[0].values[0].id).toBe('1');
|
|
149
|
+
expect(result[0].values[0].values).toEqual([]);
|
|
150
|
+
expect(result[0].values[1].id).toBe('2');
|
|
151
|
+
expect(result[0].values[1].values).toEqual([]);
|
|
152
|
+
expect(result[0].values[2].id).toBe('3');
|
|
153
|
+
expect(result[0].values[2].values).toEqual([]);
|
|
154
|
+
expect(result[0].values[3].id).toBe('4');
|
|
155
|
+
expect(result[0].values[3].values).toEqual([]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('TestInvalidInputTest', () => {
|
|
159
|
+
const input = '(invalid';
|
|
160
|
+
// Unclosed parentheses should throw an error
|
|
161
|
+
expect(() => parser.parse(input)).toThrow();
|
|
162
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
import { Parser } from "../src/Parser.js";
|
|
3
|
+
import { formatLinks } from "../src/Link.js";
|
|
4
|
+
|
|
5
|
+
const parser = new Parser();
|
|
6
|
+
|
|
7
|
+
test("Basic indented ID syntax - issue #21", () => {
|
|
8
|
+
const indentedSyntax = `3:
|
|
9
|
+
papa
|
|
10
|
+
loves
|
|
11
|
+
mama`;
|
|
12
|
+
|
|
13
|
+
const inlineSyntax = "(3: papa loves mama)";
|
|
14
|
+
|
|
15
|
+
const indentedResult = parser.parse(indentedSyntax);
|
|
16
|
+
const inlineResult = parser.parse(inlineSyntax);
|
|
17
|
+
|
|
18
|
+
// Both should produce identical structures
|
|
19
|
+
expect(indentedResult).toEqual(inlineResult);
|
|
20
|
+
|
|
21
|
+
// Both should format to the same inline syntax
|
|
22
|
+
expect(formatLinks(indentedResult)).toBe("(3: papa loves mama)");
|
|
23
|
+
expect(formatLinks(inlineResult)).toBe("(3: papa loves mama)");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("Indented ID syntax with single value", () => {
|
|
27
|
+
const input = `greeting:
|
|
28
|
+
hello`;
|
|
29
|
+
|
|
30
|
+
const result = parser.parse(input);
|
|
31
|
+
const formatted = formatLinks(result);
|
|
32
|
+
|
|
33
|
+
expect(formatted).toBe("(greeting: hello)");
|
|
34
|
+
expect(result.length).toBe(1);
|
|
35
|
+
expect(result[0].id).toBe("greeting");
|
|
36
|
+
expect(result[0].values.length).toBe(1);
|
|
37
|
+
expect(result[0].values[0].id).toBe("hello");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("Indented ID syntax with multiple values", () => {
|
|
41
|
+
const input = `action:
|
|
42
|
+
run
|
|
43
|
+
fast
|
|
44
|
+
now`;
|
|
45
|
+
|
|
46
|
+
const result = parser.parse(input);
|
|
47
|
+
const formatted = formatLinks(result);
|
|
48
|
+
|
|
49
|
+
expect(formatted).toBe("(action: run fast now)");
|
|
50
|
+
expect(result.length).toBe(1);
|
|
51
|
+
expect(result[0].id).toBe("action");
|
|
52
|
+
expect(result[0].values.length).toBe(3);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("Indented ID syntax with numeric ID", () => {
|
|
56
|
+
const input = `42:
|
|
57
|
+
answer
|
|
58
|
+
to
|
|
59
|
+
everything`;
|
|
60
|
+
|
|
61
|
+
const result = parser.parse(input);
|
|
62
|
+
const formatted = formatLinks(result);
|
|
63
|
+
|
|
64
|
+
expect(formatted).toBe("(42: answer to everything)");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("Indented ID syntax with quoted ID", () => {
|
|
68
|
+
const input = `"complex id":
|
|
69
|
+
value1
|
|
70
|
+
value2`;
|
|
71
|
+
|
|
72
|
+
const result = parser.parse(input);
|
|
73
|
+
const formatted = formatLinks(result);
|
|
74
|
+
|
|
75
|
+
expect(formatted).toBe("('complex id': value1 value2)");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("Multiple indented ID links", () => {
|
|
79
|
+
const input = `first:
|
|
80
|
+
a
|
|
81
|
+
b
|
|
82
|
+
second:
|
|
83
|
+
c
|
|
84
|
+
d`;
|
|
85
|
+
|
|
86
|
+
const result = parser.parse(input);
|
|
87
|
+
const formatted = formatLinks(result);
|
|
88
|
+
|
|
89
|
+
expect(result.length).toBe(2);
|
|
90
|
+
expect(formatted).toBe("(first: a b)\n(second: c d)");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("Mixed indented and regular syntax", () => {
|
|
94
|
+
const input = `first:
|
|
95
|
+
a
|
|
96
|
+
b
|
|
97
|
+
(second: c d)
|
|
98
|
+
third value`;
|
|
99
|
+
|
|
100
|
+
const result = parser.parse(input);
|
|
101
|
+
expect(result.length).toBe(3);
|
|
102
|
+
|
|
103
|
+
const formatted = formatLinks(result);
|
|
104
|
+
expect(formatted).toContain("(first: a b)");
|
|
105
|
+
expect(formatted).toContain("(second: c d)");
|
|
106
|
+
expect(formatted).toContain("third value");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("Unsupported colon-only syntax should fail", () => {
|
|
110
|
+
const input = `:
|
|
111
|
+
papa
|
|
112
|
+
loves
|
|
113
|
+
mama`;
|
|
114
|
+
|
|
115
|
+
expect(() => {
|
|
116
|
+
parser.parse(input);
|
|
117
|
+
}).toThrow();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("Indented ID with deeper nesting", () => {
|
|
121
|
+
const input = `root:
|
|
122
|
+
child1
|
|
123
|
+
child2
|
|
124
|
+
grandchild`;
|
|
125
|
+
|
|
126
|
+
// This should work but the grandchild will be processed as a separate nested structure
|
|
127
|
+
const result = parser.parse(input);
|
|
128
|
+
expect(result.length).toBeGreaterThan(0);
|
|
129
|
+
|
|
130
|
+
// The root should have child1 and child2 as values
|
|
131
|
+
const rootLink = result[0];
|
|
132
|
+
expect(rootLink.id).toBe("root");
|
|
133
|
+
expect(rootLink.values.length).toBe(2);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("Empty indented ID should work", () => {
|
|
137
|
+
const input = "empty:";
|
|
138
|
+
|
|
139
|
+
const result = parser.parse(input);
|
|
140
|
+
expect(result.length).toBe(1);
|
|
141
|
+
expect(result[0].id).toBe("empty");
|
|
142
|
+
expect(result[0].values.length).toBe(0);
|
|
143
|
+
|
|
144
|
+
const formatted = formatLinks(result);
|
|
145
|
+
expect(formatted).toBe("(empty)");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("Equivalence test - comprehensive", () => {
|
|
149
|
+
const testCases = [
|
|
150
|
+
{
|
|
151
|
+
indented: "test:\n one",
|
|
152
|
+
inline: "(test: one)"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
indented: "x:\n a\n b\n c",
|
|
156
|
+
inline: "(x: a b c)"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
indented: "\"quoted\":\n value",
|
|
160
|
+
inline: "(\"quoted\": value)"
|
|
161
|
+
}
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
for (const testCase of testCases) {
|
|
165
|
+
const indentedResult = parser.parse(testCase.indented);
|
|
166
|
+
const inlineResult = parser.parse(testCase.inline);
|
|
167
|
+
|
|
168
|
+
expect(indentedResult).toEqual(inlineResult);
|
|
169
|
+
expect(formatLinks(indentedResult)).toBe(formatLinks(inlineResult));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { Link } from '../src/Link.js';
|
|
3
|
+
|
|
4
|
+
test('Link constructor with id only', () => {
|
|
5
|
+
const link = new Link('test');
|
|
6
|
+
expect(link.id).toBe('test');
|
|
7
|
+
expect(link.values).toEqual([]);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('Link constructor with id and values', () => {
|
|
11
|
+
const link = new Link('parent', [new Link('child1'), new Link('child2')]);
|
|
12
|
+
expect(link.id).toBe('parent');
|
|
13
|
+
expect(link.values.length).toBe(2);
|
|
14
|
+
expect(link.values[0].id).toBe('child1');
|
|
15
|
+
expect(link.values[1].id).toBe('child2');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('Link toString with id only', () => {
|
|
19
|
+
const link = new Link('test');
|
|
20
|
+
expect(link.toString()).toBe('(test)');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('Link toString with values only', () => {
|
|
24
|
+
const link = new Link(null, [new Link('value1'), new Link('value2')]);
|
|
25
|
+
expect(link.toString()).toBe('(value1 value2)');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Link toString with id and values', () => {
|
|
29
|
+
const link = new Link('parent', [new Link('child1'), new Link('child2')]);
|
|
30
|
+
expect(link.toString()).toBe('(parent: child1 child2)');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('Link escapeReference for simple reference', () => {
|
|
34
|
+
expect(Link.escapeReference('simple')).toBe('simple');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('Link escapeReference with special characters', () => {
|
|
38
|
+
expect(Link.escapeReference('has:colon')).toBe("'has:colon'");
|
|
39
|
+
expect(Link.escapeReference('has space')).toBe("'has space'");
|
|
40
|
+
expect(Link.escapeReference('has(paren)')).toBe("'has(paren)'");
|
|
41
|
+
expect(Link.escapeReference('has"quote')).toBe(`'has"quote'`);
|
|
42
|
+
expect(Link.escapeReference("has'quote")).toBe(`"has'quote"`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('Link simplify', () => {
|
|
46
|
+
const link = new Link(null, [new Link('single')]);
|
|
47
|
+
const simplified = link.simplify();
|
|
48
|
+
expect(simplified.id).toBe('single');
|
|
49
|
+
expect(simplified.values).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('Link combine', () => {
|
|
53
|
+
const link1 = new Link('first');
|
|
54
|
+
const link2 = new Link('second');
|
|
55
|
+
const combined = link1.combine(link2);
|
|
56
|
+
expect(combined.id).toBe(null);
|
|
57
|
+
expect(combined.values.length).toBe(2);
|
|
58
|
+
expect(combined.values[0].id).toBe('first');
|
|
59
|
+
expect(combined.values[1].id).toBe('second');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('Link equals', () => {
|
|
63
|
+
const link1 = new Link('test', [new Link('child')]);
|
|
64
|
+
const link2 = new Link('test', [new Link('child')]);
|
|
65
|
+
const link3 = new Link('different', [new Link('child')]);
|
|
66
|
+
|
|
67
|
+
expect(link1.equals(link2)).toBe(true);
|
|
68
|
+
expect(link1.equals(link3)).toBe(false);
|
|
69
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { LinksGroup } from '../src/LinksGroup.js';
|
|
3
|
+
import { Link } from '../src/Link.js';
|
|
4
|
+
|
|
5
|
+
test('LinksGroup constructor', () => {
|
|
6
|
+
const element = new Link('root');
|
|
7
|
+
const children = [new Link('child1'), new Link('child2')];
|
|
8
|
+
const group = new LinksGroup(element, children);
|
|
9
|
+
|
|
10
|
+
expect(group.element).toBe(element);
|
|
11
|
+
expect(group.children).toBe(children);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('LinksGroup toList flattens structure', () => {
|
|
15
|
+
const root = new Link('root');
|
|
16
|
+
const child1 = new Link('child1');
|
|
17
|
+
const child2 = new Link('child2');
|
|
18
|
+
const grandchild = new Link('grandchild');
|
|
19
|
+
|
|
20
|
+
const childGroup = new LinksGroup(child2, [grandchild]);
|
|
21
|
+
const group = new LinksGroup(root, [child1, childGroup]);
|
|
22
|
+
|
|
23
|
+
const list = group.toList();
|
|
24
|
+
expect(list.length).toBe(4);
|
|
25
|
+
expect(list[0]).toBe(root);
|
|
26
|
+
expect(list[1]).toBe(child1);
|
|
27
|
+
expect(list[2]).toBe(child2);
|
|
28
|
+
expect(list[3]).toBe(grandchild);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('LinksGroup toString', () => {
|
|
32
|
+
const element = new Link('root');
|
|
33
|
+
const children = [new Link('child1'), new Link('child2')];
|
|
34
|
+
const group = new LinksGroup(element, children);
|
|
35
|
+
|
|
36
|
+
const str = group.toString();
|
|
37
|
+
expect(str).toContain('(root)');
|
|
38
|
+
expect(str).toContain('(child1)');
|
|
39
|
+
expect(str).toContain('(child2)');
|
|
40
|
+
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
import { Parser } from "../src/Parser.js";
|
|
3
|
+
import { formatLinks } from "../src/Link.js";
|
|
4
|
+
|
|
5
|
+
const parser = new Parser();
|
|
6
|
+
|
|
7
|
+
test("Hero example - mixed modes - issue #105", () => {
|
|
8
|
+
const input = `empInfo
|
|
9
|
+
employees:
|
|
10
|
+
(
|
|
11
|
+
name (James Kirk)
|
|
12
|
+
age 40
|
|
13
|
+
)
|
|
14
|
+
(
|
|
15
|
+
name (Jean-Luc Picard)
|
|
16
|
+
age 45
|
|
17
|
+
)
|
|
18
|
+
(
|
|
19
|
+
name (Wesley Crusher)
|
|
20
|
+
age 27
|
|
21
|
+
)`;
|
|
22
|
+
|
|
23
|
+
const result = parser.parse(input);
|
|
24
|
+
|
|
25
|
+
expect(result.length).toBeGreaterThan(0);
|
|
26
|
+
const formatted = formatLinks(result);
|
|
27
|
+
expect(formatted).toContain("empInfo");
|
|
28
|
+
expect(formatted).toContain("employees:");
|
|
29
|
+
expect(formatted).toContain("James Kirk");
|
|
30
|
+
expect(formatted).toContain("Jean-Luc Picard");
|
|
31
|
+
expect(formatted).toContain("Wesley Crusher");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("Hero example - alternative format - issue #105", () => {
|
|
35
|
+
const input = `empInfo
|
|
36
|
+
(
|
|
37
|
+
employees:
|
|
38
|
+
(
|
|
39
|
+
name (James Kirk)
|
|
40
|
+
age 40
|
|
41
|
+
)
|
|
42
|
+
(
|
|
43
|
+
name (Jean-Luc Picard)
|
|
44
|
+
age 45
|
|
45
|
+
)
|
|
46
|
+
(
|
|
47
|
+
name (Wesley Crusher)
|
|
48
|
+
age 27
|
|
49
|
+
)
|
|
50
|
+
)`;
|
|
51
|
+
|
|
52
|
+
const result = parser.parse(input);
|
|
53
|
+
|
|
54
|
+
expect(result.length).toBeGreaterThan(0);
|
|
55
|
+
const formatted = formatLinks(result);
|
|
56
|
+
expect(formatted).toContain("empInfo");
|
|
57
|
+
expect(formatted).toContain("employees:");
|
|
58
|
+
expect(formatted).toContain("James Kirk");
|
|
59
|
+
expect(formatted).toContain("Jean-Luc Picard");
|
|
60
|
+
expect(formatted).toContain("Wesley Crusher");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("Hero example - equivalence test - issue #105", () => {
|
|
64
|
+
const version1 = `empInfo
|
|
65
|
+
employees:
|
|
66
|
+
(
|
|
67
|
+
name (James Kirk)
|
|
68
|
+
age 40
|
|
69
|
+
)
|
|
70
|
+
(
|
|
71
|
+
name (Jean-Luc Picard)
|
|
72
|
+
age 45
|
|
73
|
+
)
|
|
74
|
+
(
|
|
75
|
+
name (Wesley Crusher)
|
|
76
|
+
age 27
|
|
77
|
+
)`;
|
|
78
|
+
|
|
79
|
+
const version2 = `empInfo
|
|
80
|
+
(
|
|
81
|
+
employees:
|
|
82
|
+
(
|
|
83
|
+
name (James Kirk)
|
|
84
|
+
age 40
|
|
85
|
+
)
|
|
86
|
+
(
|
|
87
|
+
name (Jean-Luc Picard)
|
|
88
|
+
age 45
|
|
89
|
+
)
|
|
90
|
+
(
|
|
91
|
+
name (Wesley Crusher)
|
|
92
|
+
age 27
|
|
93
|
+
)
|
|
94
|
+
)`;
|
|
95
|
+
|
|
96
|
+
const result1 = parser.parse(version1);
|
|
97
|
+
const result2 = parser.parse(version2);
|
|
98
|
+
|
|
99
|
+
const formatted1 = formatLinks(result1);
|
|
100
|
+
const formatted2 = formatLinks(result2);
|
|
101
|
+
|
|
102
|
+
expect(formatted1).toBe(formatted2);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("Set/object context without colon", () => {
|
|
106
|
+
const input = `empInfo
|
|
107
|
+
employees`;
|
|
108
|
+
|
|
109
|
+
const result = parser.parse(input);
|
|
110
|
+
|
|
111
|
+
expect(result.length).toBeGreaterThan(0);
|
|
112
|
+
const formatted = formatLinks(result);
|
|
113
|
+
expect(formatted).toContain("empInfo");
|
|
114
|
+
expect(formatted).toContain("employees");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("Sequence/list context with colon", () => {
|
|
118
|
+
const input = `employees:
|
|
119
|
+
James Kirk
|
|
120
|
+
Jean-Luc Picard
|
|
121
|
+
Wesley Crusher`;
|
|
122
|
+
|
|
123
|
+
const result = parser.parse(input);
|
|
124
|
+
|
|
125
|
+
expect(result.length).toBeGreaterThan(0);
|
|
126
|
+
expect(result.length).toBe(1);
|
|
127
|
+
const formatted = formatLinks(result);
|
|
128
|
+
expect(formatted).toContain("employees:");
|
|
129
|
+
expect(formatted).toContain("James Kirk");
|
|
130
|
+
expect(formatted).toContain("Jean-Luc Picard");
|
|
131
|
+
expect(formatted).toContain("Wesley Crusher");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("Sequence context with complex values", () => {
|
|
135
|
+
const input = `employees:
|
|
136
|
+
(
|
|
137
|
+
name (James Kirk)
|
|
138
|
+
age 40
|
|
139
|
+
)
|
|
140
|
+
(
|
|
141
|
+
name (Jean-Luc Picard)
|
|
142
|
+
age 45
|
|
143
|
+
)`;
|
|
144
|
+
|
|
145
|
+
const result = parser.parse(input);
|
|
146
|
+
|
|
147
|
+
expect(result.length).toBeGreaterThan(0);
|
|
148
|
+
expect(result.length).toBe(1);
|
|
149
|
+
const formatted = formatLinks(result);
|
|
150
|
+
expect(formatted).toContain("employees:");
|
|
151
|
+
expect(formatted).toContain("James Kirk");
|
|
152
|
+
expect(formatted).toContain("Jean-Luc Picard");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("Nested set and sequence contexts", () => {
|
|
156
|
+
const input = `company
|
|
157
|
+
departments:
|
|
158
|
+
engineering
|
|
159
|
+
sales
|
|
160
|
+
employees:
|
|
161
|
+
(name John)
|
|
162
|
+
(name Jane)`;
|
|
163
|
+
|
|
164
|
+
const result = parser.parse(input);
|
|
165
|
+
|
|
166
|
+
expect(result.length).toBeGreaterThan(0);
|
|
167
|
+
const formatted = formatLinks(result);
|
|
168
|
+
expect(formatted).toContain("company");
|
|
169
|
+
expect(formatted).toContain("departments:");
|
|
170
|
+
expect(formatted).toContain("employees:");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("Deeply nested mixed modes", () => {
|
|
174
|
+
const input = `root
|
|
175
|
+
level1
|
|
176
|
+
level2:
|
|
177
|
+
value1
|
|
178
|
+
value2
|
|
179
|
+
level2b
|
|
180
|
+
level3`;
|
|
181
|
+
|
|
182
|
+
const result = parser.parse(input);
|
|
183
|
+
|
|
184
|
+
expect(result.length).toBeGreaterThan(0);
|
|
185
|
+
const formatted = formatLinks(result);
|
|
186
|
+
expect(formatted).toContain("root");
|
|
187
|
+
expect(formatted).toContain("level2:");
|
|
188
|
+
});
|