links-notation 0.13.0 → 0.16.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,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,8 +1,9 @@
1
1
  {
2
2
  "name": "links-notation",
3
- "version": "0.13.0",
3
+ "version": "0.16.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",
@@ -10,7 +11,9 @@
10
11
  "test": "bun test",
11
12
  "test:watch": "bun test --watch",
12
13
  "lint": "eslint src/ tests/",
13
- "lint:fix": "eslint src/ tests/ --fix"
14
+ "lint:fix": "eslint src/ tests/ --fix",
15
+ "format": "prettier --write .",
16
+ "format:check": "prettier --check ."
14
17
  },
15
18
  "keywords": [
16
19
  "lino",
@@ -20,10 +23,19 @@
20
23
  ],
21
24
  "author": "",
22
25
  "license": "Unlicense",
23
- "devDependencies": {
24
- "bun-types": "^1.2.19",
25
- "eslint": "^8.56.0",
26
- "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"
27
32
  },
28
- "dependencies": {}
33
+ "homepage": "https://github.com/link-foundation/links-notation#readme",
34
+ "devDependencies": {
35
+ "bun-types": "^1.4.0",
36
+ "eslint": "^10.9.1",
37
+ "peggy": "^5.1.0",
38
+ "prettier": "^3.9.6",
39
+ "typescript": "^7.0.2"
40
+ }
29
41
  }
package/src/Link.js CHANGED
@@ -82,10 +82,16 @@ export class Link {
82
82
  * @returns {string} Escaped reference
83
83
  */
84
84
  static escapeReference(reference) {
85
- if (!reference || reference.trim() === '') {
85
+ if (reference === null || reference === undefined) {
86
86
  return '';
87
87
  }
88
88
 
89
+ // The empty reference is written as a bare delimiter pair, so that it reads
90
+ // back as itself instead of disappearing from the document.
91
+ if (reference === '') {
92
+ return '""';
93
+ }
94
+
89
95
  const hasSingleQuote = reference.includes("'");
90
96
  const hasDoubleQuote = reference.includes('"');
91
97
 
package/src/Parser.js CHANGED
@@ -64,7 +64,11 @@ export class Parser {
64
64
  if (item.children && item.children.length > 0) {
65
65
  // Special case: If this is an ID with empty values but has children,
66
66
  // the children should become the values of the link (indented ID syntax)
67
- if (item.id && (!item.values || item.values.length === 0)) {
67
+ if (
68
+ item.id !== undefined &&
69
+ item.id !== null &&
70
+ (!item.values || item.values.length === 0)
71
+ ) {
68
72
  const childValues = item.children.map((child) => {
69
73
  // For indented children, extract the actual reference from the child's values
70
74
  if (child.values && child.values.length === 1) {
@@ -135,6 +139,30 @@ export class Parser {
135
139
  return combined;
136
140
  }
137
141
 
142
+ /**
143
+ * Transform the links of a nested (parenthesised) context into a single Link.
144
+ * The nested context is parsed with the same rules as the root, so it yields
145
+ * a list of links; a single link is used as is, several links become the
146
+ * values of one anonymous link. An already parenthesised single link keeps
147
+ * its own group so that `((a b))` stays distinct from `(a b)`.
148
+ * @param {Array} nested - Raw items parsed inside the parentheses
149
+ * @returns {Link} The link representing the parenthesised group
150
+ */
151
+ transformNested(nested) {
152
+ const nestedLinks = [];
153
+ for (const item of nested) {
154
+ if (item !== null && item !== undefined) {
155
+ this.collectLinks(item, [], nestedLinks);
156
+ }
157
+ }
158
+ const wrapsSingleGroup =
159
+ nested.length === 1 && nested[0] && nested[0].nested !== undefined;
160
+ if (nestedLinks.length === 1 && !wrapsSingleGroup) {
161
+ return nestedLinks[0];
162
+ }
163
+ return new Link(null, nestedLinks);
164
+ }
165
+
138
166
  /**
139
167
  * Transform a parsed item into a Link object
140
168
  * @param {*} item - The item to transform
@@ -148,6 +176,11 @@ export class Parser {
148
176
  return item;
149
177
  }
150
178
 
179
+ // Parenthesised group parsed as a nested context
180
+ if (item.nested !== undefined) {
181
+ return this.transformNested(item.nested);
182
+ }
183
+
151
184
  // Handle simple reference objects like {id: 'a'}
152
185
  if (item.id !== undefined && !item.values && !item.children) {
153
186
  return new Link(item.id);
@@ -156,12 +189,12 @@ export class Parser {
156
189
  // For items with values, create a link with those values
157
190
  if (item.values && Array.isArray(item.values)) {
158
191
  // Create a link with id (if present) and transformed values
159
- const link = new Link(item.id || null, []);
192
+ const link = new Link(item.id ?? null, []);
160
193
  link.values = item.values.map((v) => this.transformLink(v));
161
194
  return link;
162
195
  }
163
196
 
164
197
  // Default case
165
- return new Link(item.id || null, []);
198
+ return new Link(item.id ?? null, []);
166
199
  }
167
200
  }