@decimalturn/toml-patch 0.3.7 → 0.4.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/LICENSE CHANGED
@@ -1,6 +1,7 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2019 Tim Hall
3
+ Copyright (c) 2019–2025 Tim Hall
4
+ Copyright (c) 2025 Martin Leduc
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -4,14 +4,54 @@
4
4
  [![JSR Version](https://img.shields.io/jsr/v/%40decimalturn/toml-patch?logo=jsr&color=blue)](https://jsr.io/@decimalturn/toml-patch)
5
5
  [![GitHub branch status](https://img.shields.io/github/check-runs/DecimalTurn/toml-patch/latest)](https://github.com/DecimalTurn/toml-patch/actions/workflows/test-and-build.yml)
6
6
 
7
- Patch, parse, and stringify [TOML](https://toml.io/en/) while preserving comments.
7
+ Patch, parse, and stringify [TOML](https://toml.io/en/) while preserving comments and formatting.
8
8
 
9
- Note that this is a maintenance fork of the original toml-patch package. This fork aims at addressing existing issues from the original project and perform dev-dependencies updates.
9
+ Note that this is a maintenance fork of the original toml-patch package. This fork aims at addressing existing issues from the original project, add small to medium sized features and perform dev-dependencies updates.
10
10
  Hopefully, the work done here can go upstream one day if timhall returns, but until then, welcome aboard![^1]
11
11
 
12
+
13
+ **What's New in v0.4.0:**
14
+ - **TomlDocument class**: A new document-oriented API for stateful TOML manipulation
15
+ - **TomlFormat class**: A class encapsulating all TOML formatting options
16
+ - **TOML date patching support**: You can now safely patch/update a date value inside a TOML document and preserve the original formatting.
17
+
18
+ Note: The functional API (`patch`, `parse`, `stringify`) remains fully compatible with previous versions. The new `TomlDocument` class is an additive feature that doesn't break existing code. You can also still use anonymous objects to pass in formatting options. Globally, v0.4.0 shouldn't introduce any breaking changes.
19
+
20
+ ## Table of Contents
21
+
22
+ - [Installation](#installation)
23
+ - [API](#api)
24
+ - [Functional API](#functional-api)
25
+ - [patch](#patch)
26
+ - [Example 1](#example-1)
27
+ - [Example 2](#example-2)
28
+ - [parse](#parse)
29
+ - [Example](#example)
30
+ - [stringify](#stringify)
31
+ - [Example](#example-1)
32
+ - [TomlDocument Class ](#tomldocument-class)
33
+ - [Constructor](#constructor)
34
+ - [Basic Usage Example](#basic-usage-example)
35
+ - [Properties](#properties)
36
+ - [Methods](#methods)
37
+ - [patch() Example](#patch-example)
38
+ - [update() Example](#update-example)
39
+ - [When to Use](#when-to-use-tomldocument-vs-functional-api)
40
+ - [Formatting](#formatting)
41
+ - [TomlFormat Class](#tomlformat-class)
42
+ - [Basic Usage](#basic-usage)
43
+ - [Formatting Options](#formatting-options)
44
+ - [Auto-Detection and Patching](#auto-detection-and-patching)
45
+ - [Complete Example](#complete-example)
46
+ - [Legacy Format Objects](#legacy-format-objects)
47
+ - [Development](#development)
48
+
49
+
12
50
  ## Installation
13
51
 
14
- toml-patch is dependency-free and can be installed via npm or yarn.
52
+ toml-patch is dependency-free and can be installed via your favorite package manager.
53
+
54
+ *Example with NPM*
15
55
 
16
56
  ```
17
57
  $ npm install --save @decimalturn/toml-patch
@@ -25,13 +65,39 @@ For browser usage, you can use unpkg:
25
65
 
26
66
  ## API
27
67
 
28
- <a href="#patch" name="patch">#</a> <b>patch</b>(<i>existing</i>, <i>updated</i>)
68
+ toml-patch provides both a [functional API]((#functional-api)) for one-time operations and a [document-oriented API](#tomldocument-class) for more complex workflows.
69
+
70
+ ### Functional API
71
+
72
+ For simple one-time operations, you can use the functional API:
73
+
74
+ #### patch(*existing*, *updated*, *format?*)
75
+
76
+ ```typescript
77
+ function patch(
78
+ existing: string,
79
+ updated: any,
80
+ format?: Format,
81
+ ): string
82
+ ```
83
+
84
+ Applies modifications to a TOML document by comparing an existing TOML string with updated JavaScript data.
85
+
86
+ This function preserves formatting and comments from the existing TOML document while applying changes from the updated data structure. It performs a diff between the existing and updated data, then strategically applies only the necessary changes to maintain the original document structure as much as possible.
87
+
88
+ **Parameters:**
89
+ - `existing: string` - The original TOML document as a string
90
+ - `updated: any` - The updated JavaScript object with desired changes
91
+ - `format?: Format` - Optional formatting options to apply to new or modified sections
92
+
93
+ **Returns:** `string` - A new TOML string with the changes applied
29
94
 
30
95
  Patch an existing TOML string with the given updated JS/JSON value, while attempting to retain the format of the existing document, including comments, indentation, and structure.
31
96
 
97
+ ##### Example 1
32
98
  ```js
33
- const TOML = require('@decimalturn/toml-patch');
34
- const assert = require('assert');
99
+ import * as TOML from '@decimalturn/toml-patch';
100
+ import { strict as assert } from 'assert';
35
101
 
36
102
  const existing = `
37
103
  # This is a TOML document
@@ -57,13 +123,51 @@ owner.name = "Tim"
57
123
  );
58
124
  ```
59
125
 
60
- <a href="#parse" name="parse">#</a> <b>parse</b>(<i>value</i>)
126
+ ##### Example 2
127
+ ```js
128
+ import * as TOML from '@decimalturn/toml-patch';
129
+ import { strict as assert } from 'assert';
130
+
131
+ const existing = `
132
+ # This is a TOML document
61
133
 
62
- Parse a TOML string into a JS/JSON value.
134
+ title = "TOML example"
135
+ owner.name = "Bob"
136
+ `;
137
+
138
+ const jsObject = TOML.parse(existing);
139
+ jsObject.owner.name = "Tim";
140
+
141
+ const patched = TOML.patch(existing, jsObject);
142
+
143
+ assert.strictEqual(
144
+ patched,
145
+ `
146
+ # This is a TOML document
147
+
148
+ title = "TOML example"
149
+ owner.name = "Tim"
150
+ `
151
+ );
152
+ ```
63
153
 
154
+ #### parse(*value*)
155
+
156
+ ```typescript
157
+ function parse(value: string): any
158
+ ```
159
+
160
+ Parses a TOML string into a JavaScript object. The function converts TOML syntax to its JavaScript equivalent. This proceeds in two steps: first, it parses the TOML string into an abstract syntax tree (AST), and then it converts the AST into a JavaScript object.
161
+
162
+ **Parameters:**
163
+ - `value: string` - The TOML string to parse
164
+
165
+ **Returns:** `any` - The parsed JavaScript object
166
+
167
+ ##### Example
64
168
  ```js
65
- const TOML = require('@decimalturn/toml-patch');
66
- const assert = require('assert');
169
+ import * as TOML from '@decimalturn/toml-patch';
170
+ import { strict as assert } from 'assert';
67
171
 
68
172
  const parsed = TOML.parse(`
69
173
  # This is a TOML document.
@@ -81,19 +185,33 @@ assert.deepStrictEqual(parsed, {
81
185
  });
82
186
  ```
83
187
 
84
- <a href="#stringify" name="stringify">#</a> <b>stringify</b>(<i>value</i>[, <i>options</i>])
188
+ #### stringify(*value*, *format?*)
189
+
190
+ ```typescript
191
+ function stringify(
192
+ value: any,
193
+ format?: Format,
194
+ ): string
195
+ ```
196
+
197
+ Converts a JavaScript object to a TOML string.
198
+
199
+ **Parameters:**
200
+ - `value: any` - The JavaScript object to stringify
201
+ - `format?: Format` - Optional formatting options for the resulting TOML
85
202
 
86
- Convert a JS/JSON value to a TOML string. `options` can be provided for high-level formatting guidelines that follows prettier's configuration.
203
+ **Returns:** `string` - The stringified TOML representation
87
204
 
88
- <b>options</b>
205
+ **Format Options:**
89
206
 
90
- - `[printWidth = 80]` - (coming soon)
91
207
  - `[trailingComma = false]` - Add trailing comma to inline tables
92
208
  - `[bracketSpacing = true]` - `true`: `{ key = "value" }`, `false`: `{key = "value"}`
93
209
 
210
+ ##### Example
211
+
94
212
  ```js
95
- const TOML = require('@decimalturn/toml-patch');
96
- const assert = require('assert');
213
+ import * as TOML from '@decimalturn/toml-patch';
214
+ import { strict as assert } from 'assert';
97
215
 
98
216
  const toml = TOML.stringify({
99
217
  title: 'TOML Example',
@@ -111,6 +229,339 @@ name = "Tim"`
111
229
  );
112
230
  ```
113
231
 
232
+ ### TomlDocument Class
233
+
234
+ The `TomlDocument` class provides a stateful interface for working with TOML documents. It's ideal when you need to perform multiple operations on the same document.
235
+
236
+ #### Constructor
237
+
238
+ ```typescript
239
+ new TomlDocument(tomlString: string)
240
+ ```
241
+
242
+ Initializes the TomlDocument with a TOML string, parsing it into an internal representation (AST).
243
+
244
+ **Parameters:**
245
+ - `tomlString: string` - The TOML string to parse
246
+
247
+ ##### Basic Usage Example
248
+
249
+ ```js
250
+ import * as TOML from '@decimalturn/toml-patch';
251
+
252
+ const doc = new TOML.TomlDocument(`
253
+ # Configuration file
254
+ title = "My App"
255
+ version = "1.0.0"
256
+
257
+ [database]
258
+ host = "localhost"
259
+ port = 5432
260
+ `);
261
+
262
+ console.log(doc.toJsObject);
263
+ // Output: { title: "My App", version: "1.0.0", database: { host: "localhost", port: 5432 } }
264
+ ```
265
+
266
+ #### Properties
267
+
268
+ ```typescript
269
+ get toJsObject(): any
270
+ get toTomlString(): string
271
+ ```
272
+
273
+ - `toJsObject: any` - Returns the JavaScript object representation of the TOML document
274
+ - `toTomlString: string` - Returns the TOML string representation (cached for performance)
275
+
276
+ #### Methods
277
+
278
+ ```typescript
279
+ patch(updatedObject: any, format?: Format): void
280
+ update(tomlString: string): void
281
+ overwrite(tomlString: string): void
282
+ ```
283
+
284
+ **patch(updatedObject, format?)**
285
+ - Applies a patch to the current AST using a modified JS object
286
+ - Updates the internal AST while preserving formatting and comments
287
+ - Use `toTomlString` getter to retrieve the updated TOML string
288
+ - **Parameters:**
289
+ - `updatedObject: any` - The modified JS object to patch with
290
+ - `format?: Format` - Optional formatting options
291
+
292
+ **update(tomlString)**
293
+ - Updates the internal AST by supplying a modified TOML string
294
+ - Uses incremental parsing for efficiency (only re-parses changed portions)
295
+ - Use `toJsObject` getter to retrieve the updated JS object representation
296
+ - **Parameters:**
297
+ - `tomlString: string` - The modified TOML string to update with
298
+
299
+ **overwrite(tomlString)**
300
+ - Overwrites the internal AST by fully re-parsing the supplied TOML string
301
+ - Simpler but slower than `update()` which uses incremental parsing
302
+ - **Parameters:**
303
+ - `tomlString: string` - The TOML string to overwrite with
304
+
305
+ ##### patch() Example
306
+
307
+ *Using patch() to modify values while preserving formatting*
308
+
309
+ ```js
310
+ import * as TOML from '@decimalturn/toml-patch';
311
+ // or: import { TomlDocument } from '@decimalturn/toml-patch';
312
+
313
+ const doc = new TOML.TomlDocument(`
314
+ # Configuration file
315
+ title = "My App"
316
+ version = "1.0.0"
317
+
318
+ [database]
319
+ host = "localhost"
320
+ port = 5432
321
+ `);
322
+
323
+ // Modify the JavaScript object
324
+ const config = doc.toJsObject;
325
+ config.version = "2.0.0";
326
+ config.database.port = 3306;
327
+ config.database.name = "myapp_db";
328
+
329
+ // Apply changes while preserving comments and formatting
330
+ doc.patch(config);
331
+
332
+ console.log(doc.toTomlString);
333
+ // Output:
334
+ // # Configuration file
335
+ // title = "My App"
336
+ // version = "2.0.0"
337
+ //
338
+ // [database]
339
+ // host = "localhost"
340
+ // port = 3306
341
+ // name = "myapp_db"
342
+ ```
343
+
344
+ ##### update() Example
345
+
346
+ *Using update() for efficient incremental parsing when the TOML string was edited*
347
+
348
+ ```js
349
+ import * as TOML from '@decimalturn/toml-patch';
350
+ // or: import { TomlDocument } from '@decimalturn/toml-patch';
351
+
352
+ const originalToml = `
353
+ # Server configuration
354
+ [server]
355
+ host = "localhost"
356
+ port = 8080
357
+ debug = true
358
+ `;
359
+
360
+ const doc = new TOML.TomlDocument(originalToml);
361
+
362
+ // Make a small change to the TOML string
363
+ const updatedToml = originalToml.replace('port = 8080', 'port = 3000');
364
+
365
+ // Efficiently update - only re-parses the changed portion
366
+ doc.update(updatedToml);
367
+
368
+ console.log(doc.toJsObject.server.port); // 3000
369
+ ```
370
+
371
+ #### When to use TomlDocument vs Functional API
372
+
373
+ | Use Case | TomlDocument | Functional API |
374
+ |----------|-------------|----------------|
375
+ | Multiple operations on same document | ✅ Preferred | ❌ Inefficient |
376
+ | One-time parsing/patching | ⚠️ Overkill | ✅ Preferred |
377
+ | Incremental text updates | ✅ `update()` method | ❌ Not supported |
378
+ | Preserving document state | ✅ Built-in | ❌ Manual |
379
+ | Working with large files | ✅ Better performance | ❌ Re-parses entirely |
380
+
381
+ ### Formatting
382
+
383
+ The `TomlFormat` class provides comprehensive control over how TOML documents are formatted during stringification and patching operations. This class encapsulates all formatting preferences, making it easy to maintain consistent styling across your TOML documents.
384
+
385
+ #### TomlFormat Class
386
+
387
+ ```typescript
388
+ class TomlFormat {
389
+ newLine: string
390
+ trailingNewline: number
391
+ trailingComma: boolean
392
+ bracketSpacing: boolean
393
+
394
+ static default(): TomlFormat
395
+ static autoDetectFormat(tomlString: string): TomlFormat
396
+ }
397
+ ```
398
+
399
+ #### Basic Usage
400
+
401
+ The recommended approach is to start with `TomlFormat.default()` and override specific options as needed:
402
+
403
+ ```js
404
+ import { patch, stringify, TomlFormat } from '@decimalturn/toml-patch';
405
+
406
+ // Create a custom format configuration
407
+ const format = TomlFormat.default();
408
+ format.newLine = '\r\n'; // Windows line endings
409
+ format.trailingNewline = 0; // No trailing newline
410
+ format.trailingComma = true; // Add trailing commas
411
+ format.bracketSpacing = false; // No spaces in brackets
412
+
413
+ // Use with stringify
414
+ const toml = stringify({
415
+ title: 'My App',
416
+ tags: ['dev', 'config'],
417
+ database: { host: 'localhost', port: 5432 }
418
+ }, format);
419
+
420
+ ```
421
+
422
+ #### Formatting Options
423
+
424
+ **newLine**
425
+ - **Type:** `string`
426
+ - **Default:** `'\n'`
427
+ - **Description:** The line ending character(s) to use in the output TOML. This option affects only the stringification process, not the internal representation (AST).
428
+
429
+ ```js
430
+ const format = TomlFormat.default();
431
+ format.newLine = '\n'; // Unix/Linux line endings
432
+ format.newLine = '\r\n'; // Windows line endings
433
+ ```
434
+
435
+ **trailingNewline**
436
+ - **Type:** `number`
437
+ - **Default:** `1`
438
+ - **Description:** The number of trailing newlines to add at the end of the TOML document. This option affects only the stringification process, not the internal representation (AST).
439
+
440
+ ```js
441
+ const format = TomlFormat.default();
442
+ format.trailingNewline = 0; // No trailing newline
443
+ format.trailingNewline = 1; // One trailing newline (standard)
444
+ format.trailingNewline = 2; // Two trailing newlines (adds extra spacing)
445
+ ```
446
+
447
+ **trailingComma**
448
+ - **Type:** `boolean`
449
+ - **Default:** `false`
450
+ - **Description:** Whether to add trailing commas after the last element in arrays and inline tables.
451
+
452
+ ```js
453
+ const format = TomlFormat.default();
454
+ format.trailingComma = false; // [1, 2, 3] and { x = 1, y = 2 }
455
+ format.trailingComma = true; // [1, 2, 3,] and { x = 1, y = 2, }
456
+ ```
457
+
458
+ **bracketSpacing**
459
+ - **Type:** `boolean`
460
+ - **Default:** `true`
461
+ - **Description:** Whether to add spaces after opening brackets/braces and before closing brackets/braces in arrays and inline tables.
462
+
463
+ ```js
464
+ const format = TomlFormat.default();
465
+ format.bracketSpacing = true; // [ 1, 2, 3 ] and { x = 1, y = 2 }
466
+ format.bracketSpacing = false; // [1, 2, 3] and {x = 1, y = 2}
467
+ ```
468
+
469
+ #### Auto-Detection and Patching
470
+
471
+ The `TomlFormat.autoDetectFormat()` method analyzes existing TOML strings to automatically detect and preserve their current formatting. If you don't supply the `format` argument when patching an existing document, this is what will be used to determine the formatting to use when inserting new elements.
472
+
473
+ Note that formatting of existing elements of a TOML string won't be affected by the `format` passed to `patch()` except for `newLine` and `trailingNewline` which are applied at the document level.
474
+
475
+
476
+ #### Complete Example
477
+
478
+ Here's a comprehensive example showing different formatting configurations:
479
+
480
+ ```js
481
+ import { stringify, TomlFormat } from '@decimalturn/toml-patch';
482
+
483
+ const data = {
484
+ title: 'Configuration Example',
485
+ settings: {
486
+ debug: true,
487
+ timeout: 30
488
+ },
489
+ servers: ['web1', 'web2', 'db1'],
490
+ database: {
491
+ host: 'localhost',
492
+ port: 5432,
493
+ ssl: true
494
+ }
495
+ };
496
+
497
+ // Compact formatting (minimal whitespace)
498
+ const compact = TomlFormat.default();
499
+ compact.bracketSpacing = false;
500
+ compact.trailingNewline = 0;
501
+
502
+ console.log(stringify(data, compact));
503
+ // Output:
504
+ // title = "Configuration Example"
505
+ // servers = ["web1", "web2", "db1"]
506
+ //
507
+ // [settings]
508
+ // debug = true
509
+ // timeout = 30
510
+ //
511
+ // [database]
512
+ // host = "localhost"
513
+ // port = 5432
514
+ // ssl = true
515
+
516
+ // Spacious formatting (with trailing commas and extra spacing)
517
+ const spacious = TomlFormat.default();
518
+ spacious.trailingComma = true;
519
+ spacious.bracketSpacing = true;
520
+ spacious.trailingNewline = 2;
521
+
522
+ console.log(stringify(data, spacious));
523
+ // Output:
524
+ // title = "Configuration Example"
525
+ // servers = [ "web1", "web2", "db1", ]
526
+ //
527
+ // [settings]
528
+ // debug = true
529
+ // timeout = 30
530
+ //
531
+ // [database]
532
+ // host = "localhost"
533
+ // port = 5432
534
+ // ssl = true
535
+ //
536
+ //
537
+
538
+ // Windows-style formatting
539
+ const windows = TomlFormat.default();
540
+ windows.newLine = '\r\n';
541
+ windows.bracketSpacing = false;
542
+ windows.trailingNewline = 1;
543
+
544
+ console.log(stringify(data, windows));
545
+ // Same structure as compact but with \r\n line endings
546
+ ```
547
+
548
+ #### Legacy Format Objects
549
+
550
+ For backward compatibility, you can still use anonymous objects for formatting options.
551
+ ```js
552
+ // Legacy approach (still supported)
553
+ const result = stringify(data, {
554
+ trailingComma: true,
555
+ bracketSpacing: false
556
+ });
557
+
558
+ // Recommended approach
559
+ const format = TomlFormat.default();
560
+ format.trailingComma = true;
561
+ format.bracketSpacing = false;
562
+ const result = stringify(data, format);
563
+ ```
564
+
114
565
  ## Development
115
566
 
116
567
  1. Update submodules: `git submodule update --remote`
@@ -118,6 +569,6 @@ name = "Tim"`
118
569
  3. Build: `npm run build`
119
570
  4. Test: `npm test`
120
571
  5. Specs compliance: `npm run specs`
121
- 6. Benchmark: `npm run benchmark [<filter>] [--help] [--example] [--reference]`
572
+ 6. Benchmark: `npm run benchmark`
122
573
 
123
574
  [^1]: Tim Hall has been inactive on most of his open source projects for more than 3 years. The sentence wording was inspired by the npm-run-all2 project.