links-notation 0.12.0 → 0.15.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.
@@ -0,0 +1,34 @@
1
+ export default [
2
+ {
3
+ ignores: ['dist/', 'node_modules/', 'src/parser-generated.js'],
4
+ },
5
+ {
6
+ languageOptions: {
7
+ ecmaVersion: 'latest',
8
+ sourceType: 'module',
9
+ globals: {
10
+ // Node.js globals
11
+ process: 'readonly',
12
+ Buffer: 'readonly',
13
+ __dirname: 'readonly',
14
+ __filename: 'readonly',
15
+ global: 'readonly',
16
+ // Test framework globals
17
+ describe: 'readonly',
18
+ test: 'readonly',
19
+ it: 'readonly',
20
+ expect: 'readonly',
21
+ beforeEach: 'readonly',
22
+ afterEach: 'readonly',
23
+ beforeAll: 'readonly',
24
+ afterAll: 'readonly',
25
+ },
26
+ },
27
+ rules: {
28
+ 'no-unused-vars': 'warn',
29
+ 'no-undef': 'error',
30
+ semi: ['error', 'always'],
31
+ quotes: ['warn', 'single', { avoidEscape: true }],
32
+ },
33
+ },
34
+ ];
@@ -0,0 +1,43 @@
1
+ // A document written for an object with empty keys, the case that motivated
2
+ // https://github.com/link-foundation/links-notation/issues/288
3
+ //
4
+ // `{"": {"": 1}}` is written as `(o: ("" (o: ("" 1))))`. Every reference in it
5
+ // reads back as itself, so encoders that produce empty keys round trip.
6
+ //
7
+ // Usage: node js/examples/empty_reference.js
8
+
9
+ import { Parser, formatLinks } from '../src/index.js';
10
+
11
+ const parser = new Parser();
12
+
13
+ const source = '(o: ("" (o: ("" 1))))';
14
+
15
+ console.log('Source: ', source);
16
+
17
+ const links = parser.parse(source);
18
+
19
+ // Show every reference between angle brackets, so an empty one stays visible.
20
+ function render(node) {
21
+ if (!node.values || node.values.length === 0) {
22
+ return `<${node.id ?? ''}>`;
23
+ }
24
+ const head =
25
+ node.id === null || node.id === undefined ? '' : `<${node.id}>: `;
26
+ return `(${head}${node.values.map(render).join(' ')})`;
27
+ }
28
+
29
+ console.log('Parsed as: ', links.map(render).join('\n'));
30
+
31
+ const formatted = formatLinks(links);
32
+ console.log('Formatted: ', formatted);
33
+ console.log(
34
+ 'Round trips: ',
35
+ formatLinks(parser.parse(formatted)) === formatted
36
+ );
37
+
38
+ // A bare delimiter pair is one empty reference, and two in a row stay separate.
39
+ for (const example of ['(a "" b)', '(a "" "" b)', '(a " " b)', '(a ""x"" b)']) {
40
+ console.log(
41
+ `${example.padEnd(14)} => ${parser.parse(example).map(render).join(' ')}`
42
+ );
43
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * TypeScript example demonstrating usage of links-notation with type checking
3
+ */
4
+
5
+ import {
6
+ Parser,
7
+ Link,
8
+ FormatOptions,
9
+ FormatConfig,
10
+ formatLinks,
11
+ } from '../index.js';
12
+
13
+ // Example 1: Basic parsing
14
+ const parser = new Parser();
15
+ const input = '(index: source target)';
16
+ const links: Link[] = parser.parse(input);
17
+
18
+ // Example 2: Creating links programmatically
19
+ const link1 = new Link('parent', [new Link('child1'), new Link('child2')]);
20
+
21
+ console.log(link1.toString());
22
+
23
+ // Example 3: Working with link properties
24
+ const link2 = new Link('id', [new Link('value1'), new Link('value2')]);
25
+ console.log('ID:', link2.id);
26
+ console.log('Values:', link2.values);
27
+
28
+ // Example 4: Link methods
29
+ const link3 = new Link('source');
30
+ const link4 = new Link('target');
31
+ const combined = link3.combine(link4);
32
+ console.log('Combined:', combined.toString());
33
+
34
+ // Example 5: FormatOptions
35
+ const formatOptions = new FormatOptions({
36
+ lessParentheses: true,
37
+ maxLineLength: 80,
38
+ indentLongLines: true,
39
+ maxInlineRefs: 3,
40
+ groupConsecutive: false,
41
+ indentString: ' ',
42
+ preferInline: true,
43
+ });
44
+
45
+ // Example 6: Formatting with options
46
+ const formattedLink = link2.format(formatOptions);
47
+ console.log('Formatted:', formattedLink);
48
+
49
+ // Example 7: FormatConfig (alias for FormatOptions)
50
+ const formatConfig = new FormatConfig({
51
+ lessParentheses: false,
52
+ maxInlineRefs: 2,
53
+ });
54
+
55
+ // Example 8: Format multiple links
56
+ const multipleLinks = [
57
+ new Link('link1', [new Link('a'), new Link('b')]),
58
+ new Link('link2', [new Link('c'), new Link('d')]),
59
+ ];
60
+
61
+ const formatted = formatLinks(multipleLinks, formatConfig);
62
+ console.log('Formatted links:\n', formatted);
63
+
64
+ // Example 9: Parser with options
65
+ const customParser = new Parser({
66
+ maxInputSize: 5 * 1024 * 1024, // 5MB
67
+ maxDepth: 500,
68
+ });
69
+
70
+ // Example 10: Complex parsing
71
+ const complexInput = `
72
+ parent
73
+ child1
74
+ child2
75
+ grandchild
76
+ `;
77
+
78
+ const complexLinks = customParser.parse(complexInput);
79
+ complexLinks.forEach((link) => {
80
+ console.log(link.format(true));
81
+ });
82
+
83
+ // Example 11: Link equality
84
+ const linkA = new Link('id', [new Link('value')]);
85
+ const linkB = new Link('id', [new Link('value')]);
86
+ console.log('Equal:', linkA.equals(linkB));
87
+
88
+ // Example 12: Link simplification
89
+ const complexLink = new Link(null, [new Link('single')]);
90
+ const simplified = complexLink.simplify();
91
+ console.log('Simplified:', simplified.toString());
92
+
93
+ // Example 13: Working with quoted references
94
+ const quotedLink = new Link('quoted id', [new Link('value with spaces')]);
95
+ console.log('Escaped:', Link.escapeReference('needs:quoting'));
package/index.d.ts ADDED
@@ -0,0 +1,312 @@
1
+ /**
2
+ * TypeScript type definitions for links-notation
3
+ *
4
+ * This module provides TypeScript type definitions for the Links Notation parser.
5
+ * Links Notation is a format for representing relationships between entities.
6
+ */
7
+
8
+ /**
9
+ * Represents a link with an optional identifier and values.
10
+ *
11
+ * A Link can represent:
12
+ * - A simple reference: `new Link('id')`
13
+ * - A link with values: `new Link('id', [new Link('child1'), new Link('child2')])`
14
+ * - A link without an id: `new Link(null, [...])`
15
+ */
16
+ export class Link {
17
+ /**
18
+ * Optional identifier for the link
19
+ */
20
+ id: string | null;
21
+
22
+ /**
23
+ * Array of child links/values
24
+ */
25
+ values: Link[];
26
+
27
+ /**
28
+ * Create a new Link
29
+ * @param id - Optional identifier for the link
30
+ * @param values - Optional array of nested links
31
+ * @throws {TypeError} If values is not an array or null
32
+ */
33
+ constructor(id?: string | null, values?: Link[] | null);
34
+
35
+ /**
36
+ * Convert link to string representation
37
+ * @returns String representation of the link
38
+ */
39
+ toString(): string;
40
+
41
+ /**
42
+ * Get formatted string of all values
43
+ * @returns Space-separated string of values
44
+ */
45
+ getValuesString(): string;
46
+
47
+ /**
48
+ * Simplify the link structure by unwrapping single-value containers
49
+ * @returns Simplified link
50
+ */
51
+ simplify(): Link;
52
+
53
+ /**
54
+ * Combine this link with another link
55
+ * @param other - The link to combine with
56
+ * @returns Combined link
57
+ */
58
+ combine(other: Link): Link;
59
+
60
+ /**
61
+ * Convert to string using either just ID or full format
62
+ * @returns String representation
63
+ */
64
+ toLinkOrIdString(): string;
65
+
66
+ /**
67
+ * Check equality with another Link
68
+ * @param other - Object to compare with
69
+ * @returns True if links are equal
70
+ */
71
+ equals(other: any): boolean;
72
+
73
+ /**
74
+ * Format the link as a string
75
+ * @param lessParentheses - If true, omit parentheses where safe; or a FormatOptions/FormatConfig object
76
+ * @param isCompoundValue - If true, this is a value in a compound link
77
+ * @returns Formatted string
78
+ */
79
+ format(
80
+ lessParentheses?: boolean | FormatOptions | FormatConfig,
81
+ isCompoundValue?: boolean
82
+ ): string;
83
+
84
+ /**
85
+ * Check if a string needs to be wrapped in parentheses
86
+ * @param str - The string to check
87
+ * @returns True if parentheses are needed
88
+ */
89
+ needsParentheses(str: string): boolean;
90
+
91
+ /**
92
+ * Get string representation of a value
93
+ * @param value - The value to stringify
94
+ * @returns String representation
95
+ */
96
+ static getValueString(value: Link): string;
97
+
98
+ /**
99
+ * Escape a reference string by adding quotes if necessary
100
+ * @param reference - The reference to escape
101
+ * @returns Escaped reference
102
+ */
103
+ static escapeReference(reference: string): string;
104
+ }
105
+
106
+ /**
107
+ * Parser options for configuring the parser behavior
108
+ */
109
+ export interface ParserOptions {
110
+ /**
111
+ * Maximum input size in bytes (default: 10MB)
112
+ */
113
+ maxInputSize?: number;
114
+
115
+ /**
116
+ * Maximum nesting depth (default: 1000)
117
+ */
118
+ maxDepth?: number;
119
+ }
120
+
121
+ /**
122
+ * Parser for Links Notation format
123
+ *
124
+ * The Parser class converts Links Notation strings into Link objects.
125
+ */
126
+ export class Parser {
127
+ /**
128
+ * Maximum input size in bytes
129
+ */
130
+ maxInputSize: number;
131
+
132
+ /**
133
+ * Maximum nesting depth
134
+ */
135
+ maxDepth: number;
136
+
137
+ /**
138
+ * Create a new Parser instance
139
+ * @param options - Parser options
140
+ */
141
+ constructor(options?: ParserOptions);
142
+
143
+ /**
144
+ * Parse Lino notation text into Link objects
145
+ * @param input - The Lino notation text to parse
146
+ * @returns Array of parsed Link objects
147
+ * @throws {Error} If parsing fails
148
+ */
149
+ parse(input: string): Link[];
150
+ }
151
+
152
+ /**
153
+ * Options for formatting links
154
+ */
155
+ export interface FormatOptionsConfig {
156
+ /**
157
+ * If true, omit parentheses where safe (default: false)
158
+ */
159
+ lessParentheses?: boolean;
160
+
161
+ /**
162
+ * Maximum line length before auto-indenting (default: 80)
163
+ */
164
+ maxLineLength?: number;
165
+
166
+ /**
167
+ * If true, indent lines exceeding maxLineLength (default: false)
168
+ */
169
+ indentLongLines?: boolean;
170
+
171
+ /**
172
+ * Maximum number of references before auto-indenting (default: null)
173
+ */
174
+ maxInlineRefs?: number | null;
175
+
176
+ /**
177
+ * If true, group consecutive links with same ID (default: false)
178
+ */
179
+ groupConsecutive?: boolean;
180
+
181
+ /**
182
+ * String to use for indentation (default: " ")
183
+ */
184
+ indentString?: string;
185
+
186
+ /**
187
+ * If true, prefer inline format when under thresholds (default: true)
188
+ */
189
+ preferInline?: boolean;
190
+ }
191
+
192
+ /**
193
+ * FormatOptions for Lino notation formatting.
194
+ *
195
+ * Provides configuration options for controlling how Link objects are formatted.
196
+ */
197
+ export class FormatOptions {
198
+ /**
199
+ * If true, omit parentheses where safe
200
+ */
201
+ lessParentheses: boolean;
202
+
203
+ /**
204
+ * Maximum line length before auto-indenting
205
+ */
206
+ maxLineLength: number;
207
+
208
+ /**
209
+ * If true, indent lines exceeding maxLineLength
210
+ */
211
+ indentLongLines: boolean;
212
+
213
+ /**
214
+ * Maximum number of references before auto-indenting
215
+ */
216
+ maxInlineRefs: number | null;
217
+
218
+ /**
219
+ * If true, group consecutive links with same ID
220
+ */
221
+ groupConsecutive: boolean;
222
+
223
+ /**
224
+ * String to use for indentation
225
+ */
226
+ indentString: string;
227
+
228
+ /**
229
+ * If true, prefer inline format when under thresholds
230
+ */
231
+ preferInline: boolean;
232
+
233
+ /**
234
+ * Create a new FormatOptions instance
235
+ * @param options - Configuration options
236
+ */
237
+ constructor(options?: FormatOptionsConfig);
238
+
239
+ /**
240
+ * Check if line should be indented based on length
241
+ * @param line - The line to check
242
+ * @returns True if line should be indented based on length threshold
243
+ */
244
+ shouldIndentByLength(line: string): boolean;
245
+
246
+ /**
247
+ * Check if link should be indented based on reference count
248
+ * @param refCount - Number of references in the link
249
+ * @returns True if link should be indented based on reference count threshold
250
+ */
251
+ shouldIndentByRefCount(refCount: number): boolean;
252
+ }
253
+
254
+ /**
255
+ * FormatConfig is an alias for FormatOptions to maintain API consistency
256
+ * with the Python implementation.
257
+ *
258
+ * This is an alias for FormatOptions to maintain API consistency across languages.
259
+ * Python uses 'FormatConfig', so this export provides the same name for JavaScript.
260
+ */
261
+ export class FormatConfig extends FormatOptions {
262
+ /**
263
+ * Create a new FormatConfig instance
264
+ * @param options - Configuration options
265
+ */
266
+ constructor(options?: FormatOptionsConfig);
267
+ }
268
+
269
+ /**
270
+ * Container for grouping related links
271
+ */
272
+ export class LinksGroup {
273
+ /**
274
+ * The element associated with this group
275
+ */
276
+ element: any;
277
+
278
+ /**
279
+ * Child groups or links
280
+ */
281
+ children: (LinksGroup | Link)[];
282
+
283
+ /**
284
+ * Create a new LinksGroup
285
+ * @param element - The element for this group
286
+ * @param children - Optional array of child groups or links
287
+ */
288
+ constructor(element: any, children?: (LinksGroup | Link)[]);
289
+
290
+ /**
291
+ * Convert the group to a flat list
292
+ * @returns Flattened list of elements
293
+ */
294
+ toList(): any[];
295
+
296
+ /**
297
+ * Convert the group to a string representation
298
+ * @returns String representation of the group
299
+ */
300
+ toString(): string;
301
+ }
302
+
303
+ /**
304
+ * Format an array of links as a string
305
+ * @param links - Array of links to format
306
+ * @param lessParentheses - If true, omit parentheses where safe; or a FormatOptions/FormatConfig object
307
+ * @returns Formatted string with each link on a new line
308
+ */
309
+ export function formatLinks(
310
+ links: Link[],
311
+ lessParentheses?: boolean | FormatOptions | FormatConfig
312
+ ): string;
package/package.json CHANGED
@@ -1,14 +1,19 @@
1
1
  {
2
2
  "name": "links-notation",
3
- "version": "0.12.0",
3
+ "version": "0.15.0",
4
4
  "description": "Links Notation parser for JavaScript",
5
5
  "main": "dist/index.js",
6
+ "types": "index.d.ts",
6
7
  "type": "module",
7
8
  "scripts": {
8
9
  "build": "bun run build:grammar && bun build src/index.js --outdir=dist --target=node",
9
10
  "build:grammar": "peggy --format es -o src/parser-generated.js src/grammar.pegjs",
10
11
  "test": "bun test",
11
- "test:watch": "bun test --watch"
12
+ "test:watch": "bun test --watch",
13
+ "lint": "eslint src/ tests/",
14
+ "lint:fix": "eslint src/ tests/ --fix",
15
+ "format": "prettier --write .",
16
+ "format:check": "prettier --check ."
12
17
  },
13
18
  "keywords": [
14
19
  "lino",
@@ -18,9 +23,19 @@
18
23
  ],
19
24
  "author": "",
20
25
  "license": "Unlicense",
21
- "devDependencies": {
22
- "bun-types": "^1.2.19",
23
- "peggy": "^5.0.6"
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/link-foundation/links-notation.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/link-foundation/links-notation/issues"
24
32
  },
25
- "dependencies": {}
26
- }
33
+ "homepage": "https://github.com/link-foundation/links-notation#readme",
34
+ "devDependencies": {
35
+ "bun-types": "^1.3.14",
36
+ "eslint": "^10.8.1",
37
+ "peggy": "^5.1.0",
38
+ "prettier": "^3.9.6",
39
+ "typescript": "^7.0.2"
40
+ }
41
+ }
@@ -21,7 +21,7 @@ export class FormatOptions {
21
21
  this.indentLongLines = options.indentLongLines ?? false;
22
22
  this.maxInlineRefs = options.maxInlineRefs ?? null;
23
23
  this.groupConsecutive = options.groupConsecutive ?? false;
24
- this.indentString = options.indentString ?? " ";
24
+ this.indentString = options.indentString ?? ' ';
25
25
  this.preferInline = options.preferInline ?? true;
26
26
  }
27
27