@decimalturn/toml-patch 0.3.8 → 0.4.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/README.md +475 -19
- package/dist/toml-patch.cjs.min.js +2 -2
- package/dist/toml-patch.cjs.min.js.map +1 -1
- package/dist/toml-patch.d.ts +289 -10
- package/dist/toml-patch.es.js +1525 -101
- package/dist/toml-patch.umd.min.js +2 -2
- package/dist/toml-patch.umd.min.js.map +1 -1
- package/package.json +8 -5
package/dist/toml-patch.es.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
//! @decimalturn/toml-patch v0.
|
|
1
|
+
//! @decimalturn/toml-patch v0.4.1 - https://github.com/DecimalTurn/toml-patch - @license: MIT
|
|
2
2
|
var NodeType;
|
|
3
3
|
(function (NodeType) {
|
|
4
4
|
NodeType["Document"] = "Document";
|
|
5
5
|
NodeType["Table"] = "Table";
|
|
6
6
|
NodeType["TableKey"] = "TableKey";
|
|
7
|
+
/**
|
|
8
|
+
* Array of Tables node
|
|
9
|
+
* More info: https://toml.io/en/latest#array-of-tables
|
|
10
|
+
*/
|
|
7
11
|
NodeType["TableArray"] = "TableArray";
|
|
8
12
|
NodeType["TableArrayKey"] = "TableArrayKey";
|
|
9
13
|
NodeType["KeyValue"] = "KeyValue";
|
|
@@ -16,6 +20,10 @@ var NodeType;
|
|
|
16
20
|
NodeType["InlineArray"] = "InlineArray";
|
|
17
21
|
NodeType["InlineItem"] = "InlineItem";
|
|
18
22
|
NodeType["InlineTable"] = "InlineTable";
|
|
23
|
+
/**
|
|
24
|
+
* Comment node
|
|
25
|
+
* More info: https://toml.io/en/latest#comment
|
|
26
|
+
*/
|
|
19
27
|
NodeType["Comment"] = "Comment";
|
|
20
28
|
})(NodeType || (NodeType = {}));
|
|
21
29
|
function isDocument(node) {
|
|
@@ -27,6 +35,11 @@ function isTable(node) {
|
|
|
27
35
|
function isTableKey(node) {
|
|
28
36
|
return node.type === NodeType.TableKey;
|
|
29
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Is a TableArray (aka array of tables)
|
|
40
|
+
* @param node
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
30
43
|
function isTableArray(node) {
|
|
31
44
|
return node.type === NodeType.TableArray;
|
|
32
45
|
}
|
|
@@ -36,6 +49,9 @@ function isTableArrayKey(node) {
|
|
|
36
49
|
function isKeyValue(node) {
|
|
37
50
|
return node.type === NodeType.KeyValue;
|
|
38
51
|
}
|
|
52
|
+
function isDateTime(node) {
|
|
53
|
+
return node.type === NodeType.DateTime;
|
|
54
|
+
}
|
|
39
55
|
function isInlineArray(node) {
|
|
40
56
|
return node.type === NodeType.InlineArray;
|
|
41
57
|
}
|
|
@@ -568,6 +584,359 @@ function lineEndingBackslash(value) {
|
|
|
568
584
|
return value.replace(IS_LINE_ENDING_BACKSLASH, (match, group) => match.replace(group, ''));
|
|
569
585
|
}
|
|
570
586
|
|
|
587
|
+
/**
|
|
588
|
+
* Central module for TOML date/time format handling and custom date classes.
|
|
589
|
+
* This module provides all the patterns, classes, and utilities needed to work
|
|
590
|
+
* with the different date/time formats supported by the TOML specification.
|
|
591
|
+
*/
|
|
592
|
+
/**
|
|
593
|
+
* Helper class containing all date format patterns and utilities for TOML date/time handling
|
|
594
|
+
*/
|
|
595
|
+
class DateFormatHelper {
|
|
596
|
+
/**
|
|
597
|
+
* Creates a custom date/time object that preserves the original TOML date/time format.
|
|
598
|
+
*
|
|
599
|
+
* This method detects the TOML date/time format from the raw string and returns an appropriate
|
|
600
|
+
* custom date/time instance (e.g., LocalDate, LocalTime, LocalDateTime, OffsetDateTime) or a Date,
|
|
601
|
+
* using the provided new JavaScript Date value.
|
|
602
|
+
*
|
|
603
|
+
* @param {Date} newJSDate - The new JavaScript Date object representing the updated
|
|
604
|
+
* date/time value. This is used as the source for constructing the custom date/time object.
|
|
605
|
+
* In some cases, this may be a custom date/time object (e.g., LocalTime) instead of a native Date.
|
|
606
|
+
* @param {string} originalRaw - The original TOML date/time string as it appeared in the input.
|
|
607
|
+
* Used to detect the specific TOML date/time format and to extract formatting details (e.g., separator, offset).
|
|
608
|
+
*
|
|
609
|
+
* @returns {Date | LocalDate | LocalTime | LocalDateTime | OffsetDateTime}
|
|
610
|
+
* Returns a custom date/time object that matches the original TOML format:
|
|
611
|
+
* - LocalDate for date-only values (e.g., "2024-01-15")
|
|
612
|
+
* - LocalTime for time-only values (e.g., "10:30:00")
|
|
613
|
+
* - LocalDateTime for local datetimes (e.g., "2024-01-15T10:30:00" or "2024-01-15 10:30:00")
|
|
614
|
+
* - OffsetDateTime for datetimes with offsets (e.g., "2024-01-15T10:30:00+02:00")
|
|
615
|
+
* - Date (native JS Date) as a fallback if the format is unrecognized
|
|
616
|
+
*
|
|
617
|
+
* Format-specific behavior:
|
|
618
|
+
* - Date-only: Returns a LocalDate constructed from the date part of newJSDate.
|
|
619
|
+
* - Time-only: Returns a LocalTime, either from newJSDate (if already LocalTime) or constructed from its time part.
|
|
620
|
+
* - Local datetime: Returns a LocalDateTime, preserving the separator (T or space).
|
|
621
|
+
* - Offset datetime: Returns an OffsetDateTime, reconstructing the date/time with the original offset and separator.
|
|
622
|
+
* - Fallback: Returns newJSDate as-is.
|
|
623
|
+
*/
|
|
624
|
+
static createDateWithOriginalFormat(newJSDate, originalRaw) {
|
|
625
|
+
if (DateFormatHelper.IS_DATE_ONLY.test(originalRaw)) {
|
|
626
|
+
// Local date (date-only) - format: 2024-01-15
|
|
627
|
+
// Check if newJSDate has time components - if so, upgrade appropriately
|
|
628
|
+
if (newJSDate.getUTCHours() !== 0 ||
|
|
629
|
+
newJSDate.getUTCMinutes() !== 0 ||
|
|
630
|
+
newJSDate.getUTCSeconds() !== 0 ||
|
|
631
|
+
newJSDate.getUTCMilliseconds() !== 0) {
|
|
632
|
+
// Check if the new value is an OffsetDateTime (has offset information)
|
|
633
|
+
if (newJSDate instanceof OffsetDateTime) {
|
|
634
|
+
// Upgrade to OffsetDateTime - it already has the right format
|
|
635
|
+
return newJSDate;
|
|
636
|
+
}
|
|
637
|
+
// Upgrade from date-only to local datetime with time components
|
|
638
|
+
// Use T separator as it's the more common format
|
|
639
|
+
let isoString = newJSDate.toISOString().replace('Z', '');
|
|
640
|
+
// Strip .000 milliseconds if present (don't show unnecessary precision)
|
|
641
|
+
isoString = isoString.replace(/\.000$/, '');
|
|
642
|
+
return new LocalDateTime(isoString, false);
|
|
643
|
+
}
|
|
644
|
+
const dateStr = newJSDate.toISOString().split('T')[0];
|
|
645
|
+
return new LocalDate(dateStr);
|
|
646
|
+
}
|
|
647
|
+
else if (DateFormatHelper.IS_TIME_ONLY.test(originalRaw)) {
|
|
648
|
+
// Local time (time-only) - format: 10:30:00
|
|
649
|
+
// For time-only values, we need to handle this more carefully
|
|
650
|
+
// The newJSDate might be a LocalTime object itself
|
|
651
|
+
if (newJSDate instanceof LocalTime) {
|
|
652
|
+
// If the new date is already a LocalTime, use its toISOString
|
|
653
|
+
return newJSDate;
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
// Determine if originalRaw had milliseconds and how many digits
|
|
657
|
+
const msMatch = originalRaw.match(/\.(\d+)\s*$/);
|
|
658
|
+
// Extract time from a regular Date object
|
|
659
|
+
const isoString = newJSDate.toISOString();
|
|
660
|
+
if (isoString && isoString.includes('T')) {
|
|
661
|
+
let newTime = isoString.split('T')[1].split('Z')[0];
|
|
662
|
+
if (msMatch) {
|
|
663
|
+
// Original had milliseconds, preserve the number of digits
|
|
664
|
+
const msDigits = msMatch[1].length;
|
|
665
|
+
const [h, m, sMs] = newTime.split(':');
|
|
666
|
+
const [s] = sMs.split('.');
|
|
667
|
+
const ms = String(newJSDate.getUTCMilliseconds()).padStart(3, '0').slice(0, msDigits);
|
|
668
|
+
newTime = `${h}:${m}:${s}.${ms}`;
|
|
669
|
+
}
|
|
670
|
+
// If original had no milliseconds, keep newTime as-is (with milliseconds if present)
|
|
671
|
+
return new LocalTime(newTime, originalRaw);
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
// Fallback: construct time from the Date object directly
|
|
675
|
+
const hours = String(newJSDate.getUTCHours()).padStart(2, '0');
|
|
676
|
+
const minutes = String(newJSDate.getUTCMinutes()).padStart(2, '0');
|
|
677
|
+
const seconds = String(newJSDate.getUTCSeconds()).padStart(2, '0');
|
|
678
|
+
const milliseconds = newJSDate.getUTCMilliseconds();
|
|
679
|
+
let timeStr;
|
|
680
|
+
if (msMatch) {
|
|
681
|
+
const msDigits = msMatch[1].length;
|
|
682
|
+
let ms = String(milliseconds).padStart(3, '0').slice(0, msDigits);
|
|
683
|
+
timeStr = `${hours}:${minutes}:${seconds}.${ms}`;
|
|
684
|
+
}
|
|
685
|
+
else if (milliseconds > 0) {
|
|
686
|
+
// No original milliseconds, but new value has them - include them
|
|
687
|
+
// Note: milliseconds > 0 ensures we don't format ".0" for zero milliseconds
|
|
688
|
+
const ms = String(milliseconds).padStart(3, '0').replace(/0+$/, '');
|
|
689
|
+
timeStr = `${hours}:${minutes}:${seconds}.${ms}`;
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
timeStr = `${hours}:${minutes}:${seconds}`;
|
|
693
|
+
}
|
|
694
|
+
return new LocalTime(timeStr, originalRaw);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
else if (DateFormatHelper.IS_LOCAL_DATETIME_T.test(originalRaw)) {
|
|
699
|
+
// Local datetime with T separator - format: 2024-01-15T10:30:00
|
|
700
|
+
// Determine if originalRaw had milliseconds and how many digits
|
|
701
|
+
const msMatch = originalRaw.match(/\.(\d+)\s*$/);
|
|
702
|
+
let isoString = newJSDate.toISOString().replace('Z', '');
|
|
703
|
+
if (msMatch) {
|
|
704
|
+
// Original had milliseconds, preserve the number of digits
|
|
705
|
+
const msDigits = msMatch[1].length;
|
|
706
|
+
// isoString is like "2024-01-15T10:30:00.123"
|
|
707
|
+
const [datePart, timePart] = isoString.split('T');
|
|
708
|
+
const [h, m, sMs] = timePart.split(':');
|
|
709
|
+
const [s] = sMs.split('.');
|
|
710
|
+
const ms = String(newJSDate.getUTCMilliseconds()).padStart(3, '0').slice(0, msDigits);
|
|
711
|
+
isoString = `${datePart}T${h}:${m}:${s}.${ms}`;
|
|
712
|
+
}
|
|
713
|
+
// If original had no milliseconds, keep isoString as-is (with milliseconds if present)
|
|
714
|
+
return new LocalDateTime(isoString, false, originalRaw);
|
|
715
|
+
}
|
|
716
|
+
else if (DateFormatHelper.IS_LOCAL_DATETIME_SPACE.test(originalRaw)) {
|
|
717
|
+
// Local datetime with space separator - format: 2024-01-15 10:30:00
|
|
718
|
+
const msMatch = originalRaw.match(/\.(\d+)\s*$/);
|
|
719
|
+
let isoString = newJSDate.toISOString().replace('Z', '').replace('T', ' ');
|
|
720
|
+
if (msMatch) {
|
|
721
|
+
const msDigits = msMatch[1].length;
|
|
722
|
+
// isoString is like "2024-01-15 10:30:00.123"
|
|
723
|
+
const [datePart, timePart] = isoString.split(' ');
|
|
724
|
+
const [h, m, sMs] = timePart.split(':');
|
|
725
|
+
const [s] = sMs.split('.');
|
|
726
|
+
const ms = String(newJSDate.getUTCMilliseconds()).padStart(3, '0').slice(0, msDigits);
|
|
727
|
+
isoString = `${datePart} ${h}:${m}:${s}.${ms}`;
|
|
728
|
+
}
|
|
729
|
+
// If original had no milliseconds, keep isoString as-is (with milliseconds if present)
|
|
730
|
+
return new LocalDateTime(isoString, true, originalRaw);
|
|
731
|
+
}
|
|
732
|
+
else if (DateFormatHelper.IS_OFFSET_DATETIME_T.test(originalRaw) || DateFormatHelper.IS_OFFSET_DATETIME_SPACE.test(originalRaw)) {
|
|
733
|
+
// Offset datetime - preserve the original timezone offset and separator
|
|
734
|
+
const offsetMatch = originalRaw.match(/([+-]\d{2}:\d{2}|[Zz])$/);
|
|
735
|
+
const originalOffset = offsetMatch ? (offsetMatch[1] === 'z' ? 'Z' : offsetMatch[1]) : 'Z';
|
|
736
|
+
const useSpaceSeparator = DateFormatHelper.IS_OFFSET_DATETIME_SPACE.test(originalRaw);
|
|
737
|
+
// Check if original had milliseconds and preserve precision
|
|
738
|
+
const msMatch = originalRaw.match(/\.(\d+)(?:[Zz]|[+-]\d{2}:\d{2})\s*$/);
|
|
739
|
+
// Convert UTC time to local time in the original timezone
|
|
740
|
+
const utcTime = newJSDate.getTime();
|
|
741
|
+
let offsetMinutes = 0;
|
|
742
|
+
if (originalOffset !== 'Z') {
|
|
743
|
+
const sign = originalOffset[0] === '+' ? 1 : -1;
|
|
744
|
+
const [hours, minutes] = originalOffset.slice(1).split(':');
|
|
745
|
+
offsetMinutes = sign * (parseInt(hours) * 60 + parseInt(minutes));
|
|
746
|
+
}
|
|
747
|
+
// Create local time by applying the offset to UTC
|
|
748
|
+
const localTime = new Date(utcTime + offsetMinutes * 60000);
|
|
749
|
+
// Format the local time components
|
|
750
|
+
const year = localTime.getUTCFullYear();
|
|
751
|
+
const month = String(localTime.getUTCMonth() + 1).padStart(2, '0');
|
|
752
|
+
const day = String(localTime.getUTCDate()).padStart(2, '0');
|
|
753
|
+
const hours = String(localTime.getUTCHours()).padStart(2, '0');
|
|
754
|
+
const minutes = String(localTime.getUTCMinutes()).padStart(2, '0');
|
|
755
|
+
const seconds = String(localTime.getUTCSeconds()).padStart(2, '0');
|
|
756
|
+
const milliseconds = localTime.getUTCMilliseconds();
|
|
757
|
+
const separator = useSpaceSeparator ? ' ' : 'T';
|
|
758
|
+
let timePart = `${hours}:${minutes}:${seconds}`;
|
|
759
|
+
// Handle millisecond precision
|
|
760
|
+
if (msMatch) {
|
|
761
|
+
const msDigits = msMatch[1].length;
|
|
762
|
+
const ms = String(milliseconds).padStart(3, '0').slice(0, msDigits);
|
|
763
|
+
timePart += `.${ms}`;
|
|
764
|
+
}
|
|
765
|
+
else if (milliseconds > 0) {
|
|
766
|
+
// Original had no milliseconds, but new value has them
|
|
767
|
+
const ms = String(milliseconds).padStart(3, '0').replace(/0+$/, '');
|
|
768
|
+
timePart += `.${ms}`;
|
|
769
|
+
}
|
|
770
|
+
const newDateTimeString = `${year}-${month}-${day}${separator}${timePart}${originalOffset}`;
|
|
771
|
+
return new OffsetDateTime(newDateTimeString, useSpaceSeparator);
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
// Fallback to regular Date
|
|
775
|
+
return newJSDate;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
// Patterns for different date/time formats
|
|
780
|
+
DateFormatHelper.IS_DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
781
|
+
DateFormatHelper.IS_TIME_ONLY = /^\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
|
|
782
|
+
DateFormatHelper.IS_LOCAL_DATETIME_T = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
|
|
783
|
+
DateFormatHelper.IS_LOCAL_DATETIME_SPACE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
|
|
784
|
+
DateFormatHelper.IS_OFFSET_DATETIME_T = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/;
|
|
785
|
+
DateFormatHelper.IS_OFFSET_DATETIME_SPACE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/;
|
|
786
|
+
// Legacy patterns from parse-toml.ts (for compatibility)
|
|
787
|
+
DateFormatHelper.IS_FULL_DATE = /(\d{4})-(\d{2})-(\d{2})/;
|
|
788
|
+
DateFormatHelper.IS_FULL_TIME = /(\d{2}):(\d{2}):(\d{2})/;
|
|
789
|
+
/**
|
|
790
|
+
* Custom Date class for local dates (date-only).
|
|
791
|
+
* Format: 1979-05-27
|
|
792
|
+
*/
|
|
793
|
+
class LocalDate extends Date {
|
|
794
|
+
constructor(value) {
|
|
795
|
+
super(value);
|
|
796
|
+
}
|
|
797
|
+
toISOString() {
|
|
798
|
+
const year = this.getUTCFullYear();
|
|
799
|
+
const month = String(this.getUTCMonth() + 1).padStart(2, '0');
|
|
800
|
+
const day = String(this.getUTCDate()).padStart(2, '0');
|
|
801
|
+
return `${year}-${month}-${day}`;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Custom Date class for local times (time-only)
|
|
806
|
+
* Format: 07:32:00 or 07:32:00.999
|
|
807
|
+
*/
|
|
808
|
+
class LocalTime extends Date {
|
|
809
|
+
constructor(value, originalFormat) {
|
|
810
|
+
// For local time, use a fixed date (1970-01-01) and the provided time
|
|
811
|
+
super(`1970-01-01T${value}`);
|
|
812
|
+
this.originalFormat = originalFormat;
|
|
813
|
+
}
|
|
814
|
+
toISOString() {
|
|
815
|
+
const hours = String(this.getUTCHours()).padStart(2, '0');
|
|
816
|
+
const minutes = String(this.getUTCMinutes()).padStart(2, '0');
|
|
817
|
+
const seconds = String(this.getUTCSeconds()).padStart(2, '0');
|
|
818
|
+
const milliseconds = this.getUTCMilliseconds();
|
|
819
|
+
// Check if the original format had milliseconds
|
|
820
|
+
const originalHadMs = this.originalFormat && this.originalFormat.includes('.');
|
|
821
|
+
if (originalHadMs) {
|
|
822
|
+
// Determine the number of millisecond digits from the original format
|
|
823
|
+
const msMatch = this.originalFormat.match(/\.(\d+)\s*$/);
|
|
824
|
+
const msDigits = msMatch ? msMatch[1].length : 3;
|
|
825
|
+
const ms = String(milliseconds).padStart(3, '0').slice(0, msDigits);
|
|
826
|
+
return `${hours}:${minutes}:${seconds}.${ms}`;
|
|
827
|
+
}
|
|
828
|
+
else if (milliseconds > 0) {
|
|
829
|
+
// Original had no milliseconds, but current has non-zero milliseconds
|
|
830
|
+
// Show them with trailing zeros removed
|
|
831
|
+
const ms = String(milliseconds).padStart(3, '0').replace(/0+$/, '');
|
|
832
|
+
return `${hours}:${minutes}:${seconds}.${ms}`;
|
|
833
|
+
}
|
|
834
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Custom Date class for local datetime (no timezone)
|
|
839
|
+
* Format: 1979-05-27T07:32:00 or 1979-05-27 07:32:00
|
|
840
|
+
*/
|
|
841
|
+
class LocalDateTime extends Date {
|
|
842
|
+
constructor(value, useSpaceSeparator = false, originalFormat) {
|
|
843
|
+
// Convert space to T for Date parsing, but remember the original format
|
|
844
|
+
super(value.replace(' ', 'T') + 'Z');
|
|
845
|
+
this.useSpaceSeparator = false;
|
|
846
|
+
this.useSpaceSeparator = useSpaceSeparator;
|
|
847
|
+
this.originalFormat = originalFormat || value;
|
|
848
|
+
}
|
|
849
|
+
toISOString() {
|
|
850
|
+
const year = this.getUTCFullYear();
|
|
851
|
+
const month = String(this.getUTCMonth() + 1).padStart(2, '0');
|
|
852
|
+
const day = String(this.getUTCDate()).padStart(2, '0');
|
|
853
|
+
const hours = String(this.getUTCHours()).padStart(2, '0');
|
|
854
|
+
const minutes = String(this.getUTCMinutes()).padStart(2, '0');
|
|
855
|
+
const seconds = String(this.getUTCSeconds()).padStart(2, '0');
|
|
856
|
+
const milliseconds = this.getUTCMilliseconds();
|
|
857
|
+
const datePart = `${year}-${month}-${day}`;
|
|
858
|
+
const separator = this.useSpaceSeparator ? ' ' : 'T';
|
|
859
|
+
// Check if the original format had milliseconds
|
|
860
|
+
const originalHadMs = this.originalFormat && this.originalFormat.includes('.');
|
|
861
|
+
if (originalHadMs) {
|
|
862
|
+
// Determine the number of millisecond digits from the original format
|
|
863
|
+
const msMatch = this.originalFormat.match(/\.(\d+)\s*$/);
|
|
864
|
+
const msDigits = msMatch ? msMatch[1].length : 3;
|
|
865
|
+
const ms = String(milliseconds).padStart(3, '0').slice(0, msDigits);
|
|
866
|
+
return `${datePart}${separator}${hours}:${minutes}:${seconds}.${ms}`;
|
|
867
|
+
}
|
|
868
|
+
else if (milliseconds > 0) {
|
|
869
|
+
// Original had no milliseconds, but current has non-zero milliseconds
|
|
870
|
+
// Show them with trailing zeros removed
|
|
871
|
+
const ms = String(milliseconds).padStart(3, '0').replace(/0+$/, '');
|
|
872
|
+
return `${datePart}${separator}${hours}:${minutes}:${seconds}.${ms}`;
|
|
873
|
+
}
|
|
874
|
+
return `${datePart}${separator}${hours}:${minutes}:${seconds}`;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Custom Date class for offset datetime that preserves space separator
|
|
879
|
+
* Format: 1979-05-27T07:32:00Z or 1979-05-27 07:32:00-07:00
|
|
880
|
+
*/
|
|
881
|
+
class OffsetDateTime extends Date {
|
|
882
|
+
constructor(value, useSpaceSeparator = false) {
|
|
883
|
+
super(value.replace(' ', 'T'));
|
|
884
|
+
this.useSpaceSeparator = false;
|
|
885
|
+
this.useSpaceSeparator = useSpaceSeparator;
|
|
886
|
+
this.originalFormat = value;
|
|
887
|
+
// Extract and preserve the original offset
|
|
888
|
+
const offsetMatch = value.match(/([+-]\d{2}:\d{2}|[Zz])$/);
|
|
889
|
+
if (offsetMatch) {
|
|
890
|
+
this.originalOffset = offsetMatch[1] === 'z' ? 'Z' : offsetMatch[1];
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
toISOString() {
|
|
894
|
+
if (this.originalOffset) {
|
|
895
|
+
// Calculate the local time in the original timezone
|
|
896
|
+
const utcTime = this.getTime();
|
|
897
|
+
let offsetMinutes = 0;
|
|
898
|
+
if (this.originalOffset !== 'Z') {
|
|
899
|
+
const sign = this.originalOffset[0] === '+' ? 1 : -1;
|
|
900
|
+
const [hours, minutes] = this.originalOffset.slice(1).split(':');
|
|
901
|
+
offsetMinutes = sign * (parseInt(hours) * 60 + parseInt(minutes));
|
|
902
|
+
}
|
|
903
|
+
const localTime = new Date(utcTime + offsetMinutes * 60000);
|
|
904
|
+
const year = localTime.getUTCFullYear();
|
|
905
|
+
const month = String(localTime.getUTCMonth() + 1).padStart(2, '0');
|
|
906
|
+
const day = String(localTime.getUTCDate()).padStart(2, '0');
|
|
907
|
+
const hours = String(localTime.getUTCHours()).padStart(2, '0');
|
|
908
|
+
const minutes = String(localTime.getUTCMinutes()).padStart(2, '0');
|
|
909
|
+
const seconds = String(localTime.getUTCSeconds()).padStart(2, '0');
|
|
910
|
+
const milliseconds = localTime.getUTCMilliseconds();
|
|
911
|
+
const datePart = `${year}-${month}-${day}`;
|
|
912
|
+
const separator = this.useSpaceSeparator ? ' ' : 'T';
|
|
913
|
+
// Check if the original format had milliseconds
|
|
914
|
+
const originalHadMs = this.originalFormat && this.originalFormat.includes('.');
|
|
915
|
+
if (originalHadMs) {
|
|
916
|
+
// Determine the number of millisecond digits from the original format
|
|
917
|
+
const msMatch = this.originalFormat.match(/\.(\d+)(?:[Zz]|[+-]\d{2}:\d{2})\s*$/);
|
|
918
|
+
const msDigits = msMatch ? msMatch[1].length : 3;
|
|
919
|
+
const ms = String(milliseconds).padStart(3, '0').slice(0, msDigits);
|
|
920
|
+
return `${datePart}${separator}${hours}:${minutes}:${seconds}.${ms}${this.originalOffset}`;
|
|
921
|
+
}
|
|
922
|
+
else if (milliseconds > 0) {
|
|
923
|
+
// Original had no milliseconds, but current has non-zero milliseconds
|
|
924
|
+
// Show them with trailing zeros removed
|
|
925
|
+
const ms = String(milliseconds).padStart(3, '0').replace(/0+$/, '');
|
|
926
|
+
return `${datePart}${separator}${hours}:${minutes}:${seconds}.${ms}${this.originalOffset}`;
|
|
927
|
+
}
|
|
928
|
+
return `${datePart}${separator}${hours}:${minutes}:${seconds}${this.originalOffset}`;
|
|
929
|
+
}
|
|
930
|
+
const isoString = super.toISOString();
|
|
931
|
+
if (this.useSpaceSeparator) {
|
|
932
|
+
return isoString.replace('T', ' ');
|
|
933
|
+
}
|
|
934
|
+
return isoString;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Create a shorter alias for convenience
|
|
939
|
+
const dateFormatHelper$1 = DateFormatHelper;
|
|
571
940
|
const TRUE = 'true';
|
|
572
941
|
const FALSE = 'false';
|
|
573
942
|
const HAS_E = /e/i;
|
|
@@ -577,8 +946,6 @@ const IS_NAN = /nan/;
|
|
|
577
946
|
const IS_HEX = /^0x/;
|
|
578
947
|
const IS_OCTAL = /^0o/;
|
|
579
948
|
const IS_BINARY = /^0b/;
|
|
580
|
-
const IS_FULL_DATE = /(\d{4})-(\d{2})-(\d{2})/;
|
|
581
|
-
const IS_FULL_TIME = /(\d{2}):(\d{2}):(\d{2})/;
|
|
582
949
|
function* parseTOML(input) {
|
|
583
950
|
const tokens = tokenize(input);
|
|
584
951
|
const cursor = new Cursor(tokens);
|
|
@@ -586,6 +953,23 @@ function* parseTOML(input) {
|
|
|
586
953
|
yield* walkBlock(cursor, input);
|
|
587
954
|
}
|
|
588
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Continues parsing TOML from a remaining string and appends the results to an existing AST.
|
|
958
|
+
*
|
|
959
|
+
* @param existingAst - The existing AST to append to
|
|
960
|
+
* @param remainingString - The remaining TOML string to parse
|
|
961
|
+
* @returns A new complete AST with both the existing and newly parsed items
|
|
962
|
+
*/
|
|
963
|
+
function* continueParsingTOML(existingAst, remainingString) {
|
|
964
|
+
// Yield all items from the existing AST
|
|
965
|
+
for (const item of existingAst) {
|
|
966
|
+
yield item;
|
|
967
|
+
}
|
|
968
|
+
// Parse and yield all items from the remaining string
|
|
969
|
+
for (const item of parseTOML(remainingString)) {
|
|
970
|
+
yield item;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
589
973
|
function* walkBlock(cursor, input) {
|
|
590
974
|
if (cursor.value.type === TokenType.Comment) {
|
|
591
975
|
yield comment(cursor);
|
|
@@ -608,7 +992,7 @@ function* walkValue$1(cursor, input) {
|
|
|
608
992
|
else if (cursor.value.raw === TRUE || cursor.value.raw === FALSE) {
|
|
609
993
|
yield boolean(cursor);
|
|
610
994
|
}
|
|
611
|
-
else if (IS_FULL_DATE.test(cursor.value.raw) || IS_FULL_TIME.test(cursor.value.raw)) {
|
|
995
|
+
else if (dateFormatHelper$1.IS_FULL_DATE.test(cursor.value.raw) || dateFormatHelper$1.IS_FULL_TIME.test(cursor.value.raw)) {
|
|
612
996
|
yield datetime(cursor, input);
|
|
613
997
|
}
|
|
614
998
|
else if ((!cursor.peek().done && cursor.peek().value.type === TokenType.Dot) ||
|
|
@@ -821,8 +1205,8 @@ function datetime(cursor, input) {
|
|
|
821
1205
|
// check if raw is full date and following is full time
|
|
822
1206
|
if (!cursor.peek().done &&
|
|
823
1207
|
cursor.peek().value.type === TokenType.Literal &&
|
|
824
|
-
IS_FULL_DATE.test(raw) &&
|
|
825
|
-
IS_FULL_TIME.test(cursor.peek().value.raw)) {
|
|
1208
|
+
dateFormatHelper$1.IS_FULL_DATE.test(raw) &&
|
|
1209
|
+
dateFormatHelper$1.IS_FULL_TIME.test(cursor.peek().value.raw)) {
|
|
826
1210
|
const start = loc.start;
|
|
827
1211
|
cursor.next();
|
|
828
1212
|
loc = { start, end: cursor.value.loc.end };
|
|
@@ -838,12 +1222,39 @@ function datetime(cursor, input) {
|
|
|
838
1222
|
loc = { start, end: cursor.value.loc.end };
|
|
839
1223
|
raw += `.${cursor.value.raw}`;
|
|
840
1224
|
}
|
|
841
|
-
if (!IS_FULL_DATE.test(raw)) {
|
|
842
|
-
//
|
|
843
|
-
|
|
844
|
-
|
|
1225
|
+
if (!dateFormatHelper$1.IS_FULL_DATE.test(raw)) {
|
|
1226
|
+
// Local time only (e.g., "07:32:00" or "07:32:00.999")
|
|
1227
|
+
if (dateFormatHelper$1.IS_TIME_ONLY.test(raw)) {
|
|
1228
|
+
value = new LocalTime(raw, raw);
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
// For other time formats, use local ISO date
|
|
1232
|
+
const [local_date] = new Date().toISOString().split('T');
|
|
1233
|
+
value = new Date(`${local_date}T${raw}`);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
else if (dateFormatHelper$1.IS_DATE_ONLY.test(raw)) {
|
|
1237
|
+
// Local date only (e.g., "1979-05-27")
|
|
1238
|
+
value = new LocalDate(raw);
|
|
1239
|
+
}
|
|
1240
|
+
else if (dateFormatHelper$1.IS_LOCAL_DATETIME_T.test(raw)) {
|
|
1241
|
+
// Local datetime with T separator (e.g., "1979-05-27T07:32:00")
|
|
1242
|
+
value = new LocalDateTime(raw, false);
|
|
1243
|
+
}
|
|
1244
|
+
else if (dateFormatHelper$1.IS_LOCAL_DATETIME_SPACE.test(raw)) {
|
|
1245
|
+
// Local datetime with space separator (e.g., "1979-05-27 07:32:00")
|
|
1246
|
+
value = new LocalDateTime(raw, true);
|
|
1247
|
+
}
|
|
1248
|
+
else if (dateFormatHelper$1.IS_OFFSET_DATETIME_T.test(raw)) {
|
|
1249
|
+
// Offset datetime with T separator (e.g., "1979-05-27T07:32:00Z" or "1979-05-27T07:32:00-07:00")
|
|
1250
|
+
value = new OffsetDateTime(raw, false);
|
|
1251
|
+
}
|
|
1252
|
+
else if (dateFormatHelper$1.IS_OFFSET_DATETIME_SPACE.test(raw)) {
|
|
1253
|
+
// Offset datetime with space separator (e.g., "1979-05-27 07:32:00Z")
|
|
1254
|
+
value = new OffsetDateTime(raw, true);
|
|
845
1255
|
}
|
|
846
1256
|
else {
|
|
1257
|
+
// Default: offset datetime with T separator or any other format
|
|
847
1258
|
value = new Date(raw.replace(' ', 'T'));
|
|
848
1259
|
}
|
|
849
1260
|
return {
|
|
@@ -1076,6 +1487,8 @@ function traverse(ast, visitor) {
|
|
|
1076
1487
|
}
|
|
1077
1488
|
}
|
|
1078
1489
|
|
|
1490
|
+
// Create a shorter alias for convenience
|
|
1491
|
+
const dateFormatHelper = DateFormatHelper;
|
|
1079
1492
|
const enter_offsets = new WeakMap();
|
|
1080
1493
|
const getEnterOffsets = (root) => {
|
|
1081
1494
|
if (!enter_offsets.has(root)) {
|
|
@@ -1092,6 +1505,22 @@ const getExitOffsets = (root) => {
|
|
|
1092
1505
|
};
|
|
1093
1506
|
//TODO: Add getOffsets function to get all offsets contained in the tree
|
|
1094
1507
|
function replace(root, parent, existing, replacement) {
|
|
1508
|
+
// Special handling for DateTime nodes to preserve original format
|
|
1509
|
+
if (isDateTime(existing) && isDateTime(replacement)) {
|
|
1510
|
+
// Analyze the original raw format and create a properly formatted replacement
|
|
1511
|
+
const originalRaw = existing.raw;
|
|
1512
|
+
const newValue = replacement.value;
|
|
1513
|
+
// Create a new date with the original format preserved
|
|
1514
|
+
const formattedDate = dateFormatHelper.createDateWithOriginalFormat(newValue, originalRaw);
|
|
1515
|
+
// Update the replacement with the properly formatted date
|
|
1516
|
+
replacement.value = formattedDate;
|
|
1517
|
+
replacement.raw = formattedDate.toISOString();
|
|
1518
|
+
// Adjust the location information to match the new raw length
|
|
1519
|
+
const lengthDiff = replacement.raw.length - originalRaw.length;
|
|
1520
|
+
if (lengthDiff !== 0) {
|
|
1521
|
+
replacement.loc.end.column = replacement.loc.start.column + replacement.raw.length;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1095
1524
|
// First, replace existing node
|
|
1096
1525
|
// (by index for items, item, or key/value)
|
|
1097
1526
|
if (hasItems(parent)) {
|
|
@@ -1147,8 +1576,9 @@ function replace(root, parent, existing, replacement) {
|
|
|
1147
1576
|
* @param parent - The parent node to insert the child into
|
|
1148
1577
|
* @param child - The child node to insert
|
|
1149
1578
|
* @param index - The index at which to insert the child (optional)
|
|
1579
|
+
* @param forceInline - Whether to force inline positioning even for document-level insertions (optional)
|
|
1150
1580
|
*/
|
|
1151
|
-
function insert(root, parent, child, index) {
|
|
1581
|
+
function insert(root, parent, child, index, forceInline) {
|
|
1152
1582
|
if (!hasItems(parent)) {
|
|
1153
1583
|
throw new Error(`Unsupported parent type "${parent.type}" for insert`);
|
|
1154
1584
|
}
|
|
@@ -1158,6 +1588,9 @@ function insert(root, parent, child, index) {
|
|
|
1158
1588
|
if (isInlineArray(parent) || isInlineTable(parent)) {
|
|
1159
1589
|
({ shift, offset } = insertInline(parent, child, index));
|
|
1160
1590
|
}
|
|
1591
|
+
else if (forceInline && isDocument(parent)) {
|
|
1592
|
+
({ shift, offset } = insertInlineAtRoot(parent, child, index));
|
|
1593
|
+
}
|
|
1161
1594
|
else {
|
|
1162
1595
|
({ shift, offset } = insertOnNewLine(parent, child, index));
|
|
1163
1596
|
}
|
|
@@ -1190,11 +1623,10 @@ function insertOnNewLine(parent, child, index) {
|
|
|
1190
1623
|
column: !isComment(previous) ? previous.loc.start.column : parent.loc.start.column
|
|
1191
1624
|
}
|
|
1192
1625
|
: clonePosition(parent.loc.start);
|
|
1193
|
-
|
|
1194
|
-
const is_block = isTable(child) || isTableArray(child);
|
|
1626
|
+
const isSquareBracketsStructure = isTable(child) || isTableArray(child);
|
|
1195
1627
|
let leading_lines = 0;
|
|
1196
1628
|
if (use_first_line) ;
|
|
1197
|
-
else if (
|
|
1629
|
+
else if (isSquareBracketsStructure) {
|
|
1198
1630
|
leading_lines = 2;
|
|
1199
1631
|
}
|
|
1200
1632
|
else {
|
|
@@ -1214,44 +1646,33 @@ function insertOnNewLine(parent, child, index) {
|
|
|
1214
1646
|
return { shift, offset };
|
|
1215
1647
|
}
|
|
1216
1648
|
/**
|
|
1217
|
-
*
|
|
1218
|
-
* This function handles positioning
|
|
1649
|
+
* Calculates positioning (shift and offset) for inserting a child into a parent container.
|
|
1650
|
+
* This function handles the core positioning logic used to insert an inline item inside a table (or at the document root level).
|
|
1219
1651
|
*
|
|
1220
|
-
* @param parent - The
|
|
1221
|
-
* @param child - The
|
|
1222
|
-
* @param index - The index
|
|
1223
|
-
* @
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
1226
|
-
* @
|
|
1652
|
+
* @param parent - The parent container (Document, InlineArray or InlineTable)
|
|
1653
|
+
* @param child - The child node to be inserted
|
|
1654
|
+
* @param index - The insertion index within the parent's items
|
|
1655
|
+
* @param options - Configuration options for positioning calculation
|
|
1656
|
+
* @param options.useNewLine - Whether to place the child on a new line
|
|
1657
|
+
* @param options.skipCommaSpace - Number of columns to skip for comma + space (default: 2)
|
|
1658
|
+
* @param options.skipBracketSpace - Number of columns to skip for bracket/space (default: 1)
|
|
1659
|
+
* @param options.hasCommaHandling - Whether comma handling logic should be applied
|
|
1660
|
+
* @param options.isLastElement - Whether this is the last element in the container
|
|
1661
|
+
* @param options.hasSeparatingCommaBefore - Whether a comma should precede this element
|
|
1662
|
+
* @param options.hasSeparatingCommaAfter - Whether a comma should follow this element
|
|
1663
|
+
* @param options.hasTrailingComma - Whether the element has a trailing comma
|
|
1664
|
+
* @returns Object containing shift (positioning adjustment for the child) and offset (adjustment for following elements)
|
|
1227
1665
|
*/
|
|
1228
|
-
function
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
const is_last = index == null || index === parent.items.length;
|
|
1235
|
-
parent.items.splice(index, 0, child);
|
|
1236
|
-
// Add commas as-needed
|
|
1237
|
-
const has_seperating_comma_before = !!previous;
|
|
1238
|
-
const has_seperating_comma_after = !is_last;
|
|
1239
|
-
const has_trailing_comma = is_last && child.comma === true;
|
|
1240
|
-
if (has_seperating_comma_before) {
|
|
1241
|
-
previous.comma = true;
|
|
1242
|
-
}
|
|
1243
|
-
if (has_seperating_comma_after) {
|
|
1244
|
-
child.comma = true;
|
|
1245
|
-
}
|
|
1246
|
-
// Use a new line for documents, children of Table/TableArray,
|
|
1247
|
-
// and if an inline table is using new lines
|
|
1248
|
-
const use_new_line = isInlineArray(parent) && perLine(parent);
|
|
1249
|
-
// Set start location from previous item or start of array
|
|
1250
|
-
// (previous is undefined for empty array or inserting at first item)
|
|
1666
|
+
function calculateInlinePositioning(parent, child, index, options = {}) {
|
|
1667
|
+
// Configuration options with default values
|
|
1668
|
+
const { useNewLine = false, skipCommaSpace = 2, skipBracketSpace = 1, hasCommaHandling = false, isLastElement = false, hasSeparatingCommaBefore = false, hasSeparatingCommaAfter = false, hasTrailingComma = false } = options;
|
|
1669
|
+
// Store preceding node
|
|
1670
|
+
const previous = index > 0 ? parent.items[index - 1] : undefined;
|
|
1671
|
+
// Set start location from previous item or start of parent
|
|
1251
1672
|
const start = previous
|
|
1252
1673
|
? {
|
|
1253
1674
|
line: previous.loc.end.line,
|
|
1254
|
-
column:
|
|
1675
|
+
column: useNewLine
|
|
1255
1676
|
? !isComment(previous)
|
|
1256
1677
|
? previous.loc.start.column
|
|
1257
1678
|
: parent.loc.start.column
|
|
@@ -1259,13 +1680,18 @@ function insertInline(parent, child, index) {
|
|
|
1259
1680
|
}
|
|
1260
1681
|
: clonePosition(parent.loc.start);
|
|
1261
1682
|
let leading_lines = 0;
|
|
1262
|
-
if (
|
|
1683
|
+
if (useNewLine) {
|
|
1263
1684
|
leading_lines = 1;
|
|
1264
1685
|
}
|
|
1265
1686
|
else {
|
|
1266
|
-
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1687
|
+
// Add spacing for inline positioning
|
|
1688
|
+
const hasSpacing = hasSeparatingCommaBefore || (!hasCommaHandling && !!previous);
|
|
1689
|
+
if (hasSpacing && hasCommaHandling) {
|
|
1690
|
+
start.column += skipCommaSpace;
|
|
1691
|
+
}
|
|
1692
|
+
else if (hasSpacing || (hasCommaHandling && !previous)) {
|
|
1693
|
+
start.column += skipBracketSpace;
|
|
1694
|
+
}
|
|
1269
1695
|
}
|
|
1270
1696
|
start.line += leading_lines;
|
|
1271
1697
|
const shift = {
|
|
@@ -1274,12 +1700,74 @@ function insertInline(parent, child, index) {
|
|
|
1274
1700
|
};
|
|
1275
1701
|
// Apply offsets after child node
|
|
1276
1702
|
const child_span = getSpan(child.loc);
|
|
1703
|
+
if (!hasCommaHandling) {
|
|
1704
|
+
// For documents or contexts without comma handling, simpler offset calculation
|
|
1705
|
+
const offset = {
|
|
1706
|
+
lines: child_span.lines + (leading_lines - 1),
|
|
1707
|
+
columns: child_span.columns
|
|
1708
|
+
};
|
|
1709
|
+
return { shift, offset };
|
|
1710
|
+
}
|
|
1711
|
+
// Special case: Fix trailing comma spacing issue for arrays that have trailing commas
|
|
1712
|
+
const has_trailing_comma_spacing_bug = hasSeparatingCommaBefore &&
|
|
1713
|
+
hasTrailingComma &&
|
|
1714
|
+
!hasSeparatingCommaAfter &&
|
|
1715
|
+
isLastElement;
|
|
1716
|
+
let trailing_comma_offset_adjustment = 0;
|
|
1717
|
+
if (has_trailing_comma_spacing_bug) {
|
|
1718
|
+
trailing_comma_offset_adjustment = -1;
|
|
1719
|
+
}
|
|
1277
1720
|
const offset = {
|
|
1278
1721
|
lines: child_span.lines + (leading_lines - 1),
|
|
1279
|
-
columns: child_span.columns +
|
|
1722
|
+
columns: child_span.columns +
|
|
1723
|
+
(hasSeparatingCommaBefore || hasSeparatingCommaAfter ? skipCommaSpace : 0) +
|
|
1724
|
+
(hasTrailingComma ? 1 + trailing_comma_offset_adjustment : 0)
|
|
1280
1725
|
};
|
|
1281
1726
|
return { shift, offset };
|
|
1282
1727
|
}
|
|
1728
|
+
function insertInline(parent, child, index) {
|
|
1729
|
+
if (!isInlineItem(child)) {
|
|
1730
|
+
throw new Error(`Incompatible child type "${child.type}"`);
|
|
1731
|
+
}
|
|
1732
|
+
// Store preceding node and insert
|
|
1733
|
+
const previous = index != null ? parent.items[index - 1] : last(parent.items);
|
|
1734
|
+
const is_last = index == null || index === parent.items.length;
|
|
1735
|
+
parent.items.splice(index, 0, child);
|
|
1736
|
+
// Add commas as-needed
|
|
1737
|
+
const has_separating_comma_before = !!previous;
|
|
1738
|
+
const has_separating_comma_after = !is_last;
|
|
1739
|
+
if (has_separating_comma_before) {
|
|
1740
|
+
previous.comma = true;
|
|
1741
|
+
}
|
|
1742
|
+
if (has_separating_comma_after) {
|
|
1743
|
+
child.comma = true;
|
|
1744
|
+
}
|
|
1745
|
+
// Use new line for inline arrays that span multiple lines
|
|
1746
|
+
const use_new_line = isInlineArray(parent) && perLine(parent);
|
|
1747
|
+
const has_trailing_comma = is_last && child.comma === true;
|
|
1748
|
+
return calculateInlinePositioning(parent, child, index, {
|
|
1749
|
+
useNewLine: use_new_line,
|
|
1750
|
+
hasCommaHandling: true,
|
|
1751
|
+
isLastElement: is_last,
|
|
1752
|
+
hasSeparatingCommaBefore: has_separating_comma_before,
|
|
1753
|
+
hasSeparatingCommaAfter: has_separating_comma_after,
|
|
1754
|
+
hasTrailingComma: has_trailing_comma
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Inserts a child into a Document with inline positioning behavior.
|
|
1759
|
+
* This provides inline-style spacing while maintaining Document's Block item types.
|
|
1760
|
+
*/
|
|
1761
|
+
function insertInlineAtRoot(parent, child, index) {
|
|
1762
|
+
// Calculate positioning as if inserting into an inline context
|
|
1763
|
+
const result = calculateInlinePositioning(parent, child, index, {
|
|
1764
|
+
useNewLine: false,
|
|
1765
|
+
hasCommaHandling: false
|
|
1766
|
+
});
|
|
1767
|
+
// Insert the child directly into the Document (as a Block item)
|
|
1768
|
+
parent.items.splice(index, 0, child);
|
|
1769
|
+
return result;
|
|
1770
|
+
}
|
|
1283
1771
|
function remove(root, parent, node) {
|
|
1284
1772
|
// Remove an element from the parent's items
|
|
1285
1773
|
// (supports Document, Table, TableArray, InlineTable, and InlineArray
|
|
@@ -1351,7 +1839,15 @@ function remove(root, parent, node) {
|
|
|
1351
1839
|
offset.columns -= 2;
|
|
1352
1840
|
}
|
|
1353
1841
|
if (is_inline && previous && !next) {
|
|
1354
|
-
|
|
1842
|
+
// When removing the last element, preserve trailing comma preference
|
|
1843
|
+
// If the removed element had a trailing comma, transfer it to the new last element
|
|
1844
|
+
const removedHadTrailingComma = node.comma;
|
|
1845
|
+
if (removedHadTrailingComma) {
|
|
1846
|
+
previous.comma = true;
|
|
1847
|
+
}
|
|
1848
|
+
else {
|
|
1849
|
+
previous.comma = false;
|
|
1850
|
+
}
|
|
1355
1851
|
}
|
|
1356
1852
|
// Apply offsets after preceding node or before children of parent node
|
|
1357
1853
|
const target = previous || parent;
|
|
@@ -1659,6 +2155,8 @@ function generateBoolean(value) {
|
|
|
1659
2155
|
};
|
|
1660
2156
|
}
|
|
1661
2157
|
function generateDateTime(value) {
|
|
2158
|
+
// Custom date classes have their own toISOString() implementations
|
|
2159
|
+
// that return the properly formatted strings for each TOML date/time type
|
|
1662
2160
|
const raw = value.toISOString();
|
|
1663
2161
|
return {
|
|
1664
2162
|
type: NodeType.DateTime,
|
|
@@ -1690,7 +2188,382 @@ function generateInlineTable() {
|
|
|
1690
2188
|
};
|
|
1691
2189
|
}
|
|
1692
2190
|
|
|
1693
|
-
|
|
2191
|
+
// Default formatting values
|
|
2192
|
+
const DEFAULT_NEWLINE = '\n';
|
|
2193
|
+
const DEFAULT_TRAILING_NEWLINE = 1;
|
|
2194
|
+
const DEFAULT_TRAILING_COMMA = false;
|
|
2195
|
+
const DEFAULT_BRACKET_SPACING = true;
|
|
2196
|
+
const DEFAULT_INLINE_TABLE_START = 1;
|
|
2197
|
+
// Detects if trailing commas are used in the existing TOML by examining the AST
|
|
2198
|
+
// Returns true if trailing commas are used, false if not or comma-separated structures found (ie. default to false)
|
|
2199
|
+
function detectTrailingComma(ast) {
|
|
2200
|
+
// Convert iterable to array and look for the first inline array or inline table to determine trailing comma preference
|
|
2201
|
+
const items = Array.from(ast);
|
|
2202
|
+
for (const item of items) {
|
|
2203
|
+
const result = findTrailingCommaInNode(item);
|
|
2204
|
+
if (result !== null) {
|
|
2205
|
+
return result;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
// Return default if no comma-separated structures are found
|
|
2209
|
+
return DEFAULT_TRAILING_COMMA;
|
|
2210
|
+
}
|
|
2211
|
+
// Detects if bracket spacing is used in inline arrays and tables by examining the raw string
|
|
2212
|
+
// Returns true if bracket spacing is found, false if not or no bracket structures found (default to true)
|
|
2213
|
+
function detectBracketSpacing(tomlString, ast) {
|
|
2214
|
+
// Convert iterable to array and look for inline arrays and tables
|
|
2215
|
+
const items = Array.from(ast);
|
|
2216
|
+
for (const item of items) {
|
|
2217
|
+
const result = findBracketSpacingInNode(item, tomlString);
|
|
2218
|
+
if (result !== null) {
|
|
2219
|
+
return result;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
// Return default if no bracket structures are found
|
|
2223
|
+
return DEFAULT_BRACKET_SPACING;
|
|
2224
|
+
}
|
|
2225
|
+
// Helper function to recursively search for bracket spacing in a node
|
|
2226
|
+
function findBracketSpacingInNode(node, tomlString) {
|
|
2227
|
+
if (!node || typeof node !== 'object') {
|
|
2228
|
+
return null;
|
|
2229
|
+
}
|
|
2230
|
+
// Check if this is an InlineArray or InlineTable
|
|
2231
|
+
if ((node.type === 'InlineArray' || node.type === 'InlineTable') && node.loc) {
|
|
2232
|
+
const bracketSpacing = checkBracketSpacingInLocation(node.loc, tomlString);
|
|
2233
|
+
if (bracketSpacing !== null) {
|
|
2234
|
+
return bracketSpacing;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
// Recursively check nested structures
|
|
2238
|
+
if (node.items && Array.isArray(node.items)) {
|
|
2239
|
+
for (const child of node.items) {
|
|
2240
|
+
const result = findBracketSpacingInNode(child, tomlString);
|
|
2241
|
+
if (result !== null) {
|
|
2242
|
+
return result;
|
|
2243
|
+
}
|
|
2244
|
+
// Also check nested item if it exists
|
|
2245
|
+
if (child.item) {
|
|
2246
|
+
const nestedResult = findBracketSpacingInNode(child.item, tomlString);
|
|
2247
|
+
if (nestedResult !== null) {
|
|
2248
|
+
return nestedResult;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
// Check other properties that might contain nodes
|
|
2254
|
+
for (const prop of ['value', 'key', 'item']) {
|
|
2255
|
+
if (node[prop]) {
|
|
2256
|
+
const result = findBracketSpacingInNode(node[prop], tomlString);
|
|
2257
|
+
if (result !== null) {
|
|
2258
|
+
return result;
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return null;
|
|
2263
|
+
}
|
|
2264
|
+
// Helper function to check bracket spacing in a specific location
|
|
2265
|
+
function checkBracketSpacingInLocation(loc, tomlString) {
|
|
2266
|
+
var _a;
|
|
2267
|
+
if (!loc || !loc.start || !loc.end) {
|
|
2268
|
+
return null;
|
|
2269
|
+
}
|
|
2270
|
+
// Extract the raw text for this location
|
|
2271
|
+
const lines = tomlString.split(/\r?\n/);
|
|
2272
|
+
const startLine = loc.start.line - 1; // Convert to 0-based
|
|
2273
|
+
const endLine = loc.end.line - 1;
|
|
2274
|
+
const startCol = loc.start.column;
|
|
2275
|
+
const endCol = loc.end.column;
|
|
2276
|
+
let rawText = '';
|
|
2277
|
+
if (startLine === endLine) {
|
|
2278
|
+
rawText = ((_a = lines[startLine]) === null || _a === void 0 ? void 0 : _a.substring(startCol, endCol + 1)) || '';
|
|
2279
|
+
}
|
|
2280
|
+
else {
|
|
2281
|
+
// Multi-line case
|
|
2282
|
+
if (lines[startLine]) {
|
|
2283
|
+
rawText += lines[startLine].substring(startCol);
|
|
2284
|
+
}
|
|
2285
|
+
for (let i = startLine + 1; i < endLine; i++) {
|
|
2286
|
+
rawText += '\n' + (lines[i] || '');
|
|
2287
|
+
}
|
|
2288
|
+
if (lines[endLine]) {
|
|
2289
|
+
rawText += '\n' + lines[endLine].substring(0, endCol + 1);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
// Check for bracket spacing patterns
|
|
2293
|
+
// For arrays: [ elements ] vs [elements]
|
|
2294
|
+
// For tables: { elements } vs {elements}
|
|
2295
|
+
const arrayMatch = rawText.match(/^\[(\s*)/);
|
|
2296
|
+
const tableMatch = rawText.match(/^\{(\s*)/);
|
|
2297
|
+
if (arrayMatch) {
|
|
2298
|
+
// Check if there's a space after the opening bracket
|
|
2299
|
+
return arrayMatch[1].length > 0;
|
|
2300
|
+
}
|
|
2301
|
+
if (tableMatch) {
|
|
2302
|
+
// Check if there's a space after the opening brace
|
|
2303
|
+
return tableMatch[1].length > 0;
|
|
2304
|
+
}
|
|
2305
|
+
return null;
|
|
2306
|
+
}
|
|
2307
|
+
// Helper function to recursively search for comma usage in a node
|
|
2308
|
+
function findTrailingCommaInNode(node) {
|
|
2309
|
+
if (!node || typeof node !== 'object') {
|
|
2310
|
+
return null;
|
|
2311
|
+
}
|
|
2312
|
+
// Check if this is an InlineArray
|
|
2313
|
+
if (node.type === 'InlineArray' && node.items && Array.isArray(node.items)) {
|
|
2314
|
+
return checkTrailingCommaInItems(node.items);
|
|
2315
|
+
}
|
|
2316
|
+
// Check if this is an InlineTable
|
|
2317
|
+
if (node.type === 'InlineTable' && node.items && Array.isArray(node.items)) {
|
|
2318
|
+
return checkTrailingCommaInItems(node.items);
|
|
2319
|
+
}
|
|
2320
|
+
// Check if this is a KeyValue with a value that might contain arrays/tables
|
|
2321
|
+
if (node.type === 'KeyValue' && node.value) {
|
|
2322
|
+
return findTrailingCommaInNode(node.value);
|
|
2323
|
+
}
|
|
2324
|
+
// For other node types, recursively check any items array
|
|
2325
|
+
if (node.items && Array.isArray(node.items)) {
|
|
2326
|
+
for (const item of node.items) {
|
|
2327
|
+
const result = findTrailingCommaInNode(item);
|
|
2328
|
+
if (result !== null) {
|
|
2329
|
+
return result;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return null;
|
|
2334
|
+
}
|
|
2335
|
+
// Check trailing comma usage in an array of inline items
|
|
2336
|
+
function checkTrailingCommaInItems(items) {
|
|
2337
|
+
if (items.length === 0) {
|
|
2338
|
+
return null;
|
|
2339
|
+
}
|
|
2340
|
+
// Check the last item to see if it has a trailing comma
|
|
2341
|
+
const lastItem = items[items.length - 1];
|
|
2342
|
+
if (lastItem && typeof lastItem === 'object' && 'comma' in lastItem) {
|
|
2343
|
+
return lastItem.comma === true;
|
|
2344
|
+
}
|
|
2345
|
+
return false;
|
|
2346
|
+
}
|
|
2347
|
+
// Helper function to detect if an InlineArray originally had trailing commas
|
|
2348
|
+
function arrayHadTrailingCommas(node) {
|
|
2349
|
+
if (!isInlineArray(node))
|
|
2350
|
+
return false;
|
|
2351
|
+
if (node.items.length === 0)
|
|
2352
|
+
return false;
|
|
2353
|
+
// Check if the last item has a trailing comma
|
|
2354
|
+
const lastItem = node.items[node.items.length - 1];
|
|
2355
|
+
return lastItem.comma === true;
|
|
2356
|
+
}
|
|
2357
|
+
// Helper function to detect if an InlineTable originally had trailing commas
|
|
2358
|
+
function tableHadTrailingCommas(node) {
|
|
2359
|
+
if (!isInlineTable(node))
|
|
2360
|
+
return false;
|
|
2361
|
+
if (node.items.length === 0)
|
|
2362
|
+
return false;
|
|
2363
|
+
// Check if the last item has a trailing comma
|
|
2364
|
+
const lastItem = node.items[node.items.length - 1];
|
|
2365
|
+
return lastItem.comma === true;
|
|
2366
|
+
}
|
|
2367
|
+
// Returns the detected newline (\n or \r\n) from a string, defaulting to \n
|
|
2368
|
+
function detectNewline(str) {
|
|
2369
|
+
const lfIndex = str.indexOf('\n');
|
|
2370
|
+
if (lfIndex > 0 && str.substring(lfIndex - 1, lfIndex) === '\r') {
|
|
2371
|
+
return '\r\n';
|
|
2372
|
+
}
|
|
2373
|
+
return '\n';
|
|
2374
|
+
}
|
|
2375
|
+
// Counts consecutive trailing newlines at the end of a string
|
|
2376
|
+
function countTrailingNewlines(str, newlineChar) {
|
|
2377
|
+
let count = 0;
|
|
2378
|
+
let pos = str.length;
|
|
2379
|
+
while (pos >= newlineChar.length) {
|
|
2380
|
+
if (str.substring(pos - newlineChar.length, pos) === newlineChar) {
|
|
2381
|
+
count++;
|
|
2382
|
+
pos -= newlineChar.length;
|
|
2383
|
+
}
|
|
2384
|
+
else {
|
|
2385
|
+
break;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
return count;
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Validates a format object and warns about unsupported properties.
|
|
2392
|
+
* Throws errors for supported properties with invalid types.
|
|
2393
|
+
* @param format - The format object to validate
|
|
2394
|
+
* @returns The validated format object with only supported properties and correct types
|
|
2395
|
+
*/
|
|
2396
|
+
function validateFormatObject(format) {
|
|
2397
|
+
if (!format || typeof format !== 'object') {
|
|
2398
|
+
return {};
|
|
2399
|
+
}
|
|
2400
|
+
const supportedProperties = new Set(['newLine', 'trailingNewline', 'trailingComma', 'bracketSpacing', 'inlineTableStart']);
|
|
2401
|
+
const validatedFormat = {};
|
|
2402
|
+
const unsupportedProperties = [];
|
|
2403
|
+
const invalidTypeProperties = [];
|
|
2404
|
+
// Check all enumerable properties of the format object
|
|
2405
|
+
for (const key in format) {
|
|
2406
|
+
if (Object.prototype.hasOwnProperty.call(format, key)) {
|
|
2407
|
+
if (supportedProperties.has(key)) {
|
|
2408
|
+
const value = format[key];
|
|
2409
|
+
// Type validation for each property
|
|
2410
|
+
switch (key) {
|
|
2411
|
+
case 'newLine':
|
|
2412
|
+
if (typeof value === 'string') {
|
|
2413
|
+
validatedFormat.newLine = value;
|
|
2414
|
+
}
|
|
2415
|
+
else {
|
|
2416
|
+
invalidTypeProperties.push(`${key} (expected string, got ${typeof value})`);
|
|
2417
|
+
}
|
|
2418
|
+
break;
|
|
2419
|
+
case 'trailingNewline':
|
|
2420
|
+
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
2421
|
+
validatedFormat.trailingNewline = value;
|
|
2422
|
+
}
|
|
2423
|
+
else {
|
|
2424
|
+
invalidTypeProperties.push(`${key} (expected boolean or number, got ${typeof value})`);
|
|
2425
|
+
}
|
|
2426
|
+
break;
|
|
2427
|
+
case 'trailingComma':
|
|
2428
|
+
case 'bracketSpacing':
|
|
2429
|
+
if (typeof value === 'boolean') {
|
|
2430
|
+
validatedFormat[key] = value;
|
|
2431
|
+
}
|
|
2432
|
+
else {
|
|
2433
|
+
invalidTypeProperties.push(`${key} (expected boolean, got ${typeof value})`);
|
|
2434
|
+
}
|
|
2435
|
+
break;
|
|
2436
|
+
case 'inlineTableStart':
|
|
2437
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
|
|
2438
|
+
validatedFormat.inlineTableStart = value;
|
|
2439
|
+
}
|
|
2440
|
+
else if (value === undefined || value === null) {
|
|
2441
|
+
// Allow undefined/null to use default
|
|
2442
|
+
validatedFormat.inlineTableStart = value;
|
|
2443
|
+
}
|
|
2444
|
+
else {
|
|
2445
|
+
invalidTypeProperties.push(`${key} (expected non-negative integer or undefined, got ${typeof value})`);
|
|
2446
|
+
}
|
|
2447
|
+
break;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
else {
|
|
2451
|
+
unsupportedProperties.push(key);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
// Warn about unsupported properties
|
|
2456
|
+
if (unsupportedProperties.length > 0) {
|
|
2457
|
+
console.warn(`toml-patch: Ignoring unsupported format properties: ${unsupportedProperties.join(', ')}. Supported properties are: ${Array.from(supportedProperties).join(', ')}`);
|
|
2458
|
+
}
|
|
2459
|
+
// Throw error for invalid types
|
|
2460
|
+
if (invalidTypeProperties.length > 0) {
|
|
2461
|
+
throw new TypeError(`Invalid types for format properties: ${invalidTypeProperties.join(', ')}`);
|
|
2462
|
+
}
|
|
2463
|
+
return validatedFormat;
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Resolves a format parameter to a TomlFormat instance.
|
|
2467
|
+
* Handles TomlFormat instances and partial TomlFormat objects as well as undefined.
|
|
2468
|
+
*
|
|
2469
|
+
* @param format - The format parameter to resolve (TomlFormat instance, partial format object, or undefined)
|
|
2470
|
+
* @param fallbackFormat - The fallback TomlFormat to use when no format is provided
|
|
2471
|
+
* @returns A resolved TomlFormat instance
|
|
2472
|
+
*/
|
|
2473
|
+
function resolveTomlFormat(format, fallbackFormat) {
|
|
2474
|
+
var _a, _b, _c, _d;
|
|
2475
|
+
if (format) {
|
|
2476
|
+
// If format is provided, validate and merge it with fallback
|
|
2477
|
+
if (format instanceof TomlFormat) {
|
|
2478
|
+
return format;
|
|
2479
|
+
}
|
|
2480
|
+
else {
|
|
2481
|
+
// Validate the format object and warn about unsupported properties
|
|
2482
|
+
const validatedFormat = validateFormatObject(format);
|
|
2483
|
+
// Create a new TomlFormat instance with validated properties
|
|
2484
|
+
return new TomlFormat((_a = validatedFormat.newLine) !== null && _a !== void 0 ? _a : fallbackFormat.newLine, (_b = validatedFormat.trailingNewline) !== null && _b !== void 0 ? _b : fallbackFormat.trailingNewline, (_c = validatedFormat.trailingComma) !== null && _c !== void 0 ? _c : fallbackFormat.trailingComma, (_d = validatedFormat.bracketSpacing) !== null && _d !== void 0 ? _d : fallbackFormat.bracketSpacing, validatedFormat.inlineTableStart !== undefined ? validatedFormat.inlineTableStart : fallbackFormat.inlineTableStart);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
else {
|
|
2488
|
+
// Use fallback format when no format is provided
|
|
2489
|
+
return fallbackFormat;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
class TomlFormat {
|
|
2493
|
+
// These options were part of the original TimHall's version and are not yet implemented
|
|
2494
|
+
//printWidth?: number;
|
|
2495
|
+
//tabWidth?: number;
|
|
2496
|
+
//useTabs?: boolean;
|
|
2497
|
+
constructor(newLine, trailingNewline, trailingComma, bracketSpacing, inlineTableStart) {
|
|
2498
|
+
// Use provided values or fall back to defaults
|
|
2499
|
+
this.newLine = newLine !== null && newLine !== void 0 ? newLine : DEFAULT_NEWLINE;
|
|
2500
|
+
this.trailingNewline = trailingNewline !== null && trailingNewline !== void 0 ? trailingNewline : DEFAULT_TRAILING_NEWLINE;
|
|
2501
|
+
this.trailingComma = trailingComma !== null && trailingComma !== void 0 ? trailingComma : DEFAULT_TRAILING_COMMA;
|
|
2502
|
+
this.bracketSpacing = bracketSpacing !== null && bracketSpacing !== void 0 ? bracketSpacing : DEFAULT_BRACKET_SPACING;
|
|
2503
|
+
this.inlineTableStart = inlineTableStart !== null && inlineTableStart !== void 0 ? inlineTableStart : DEFAULT_INLINE_TABLE_START;
|
|
2504
|
+
}
|
|
2505
|
+
/**
|
|
2506
|
+
* Creates a new TomlFormat instance with default formatting preferences.
|
|
2507
|
+
*
|
|
2508
|
+
* @returns A new TomlFormat instance with default values:
|
|
2509
|
+
* - newLine: '\n'
|
|
2510
|
+
* - trailingNewline: 1
|
|
2511
|
+
* - trailingComma: false
|
|
2512
|
+
* - bracketSpacing: true
|
|
2513
|
+
* - inlineTableStart: 1
|
|
2514
|
+
*/
|
|
2515
|
+
static default() {
|
|
2516
|
+
return new TomlFormat(DEFAULT_NEWLINE, DEFAULT_TRAILING_NEWLINE, DEFAULT_TRAILING_COMMA, DEFAULT_BRACKET_SPACING, DEFAULT_INLINE_TABLE_START);
|
|
2517
|
+
}
|
|
2518
|
+
/**
|
|
2519
|
+
* Auto-detects formatting preferences from an existing TOML string.
|
|
2520
|
+
*
|
|
2521
|
+
* This method analyzes the provided TOML string to determine formatting
|
|
2522
|
+
* preferences such as line endings, trailing newlines, and comma usage.
|
|
2523
|
+
*
|
|
2524
|
+
* @param tomlString - The TOML string to analyze for formatting patterns
|
|
2525
|
+
* @returns A new TomlFormat instance with detected formatting preferences
|
|
2526
|
+
*
|
|
2527
|
+
* @example
|
|
2528
|
+
* ```typescript
|
|
2529
|
+
* const toml = 'array = ["a", "b", "c",]\ntable = { x = 1, y = 2, }';
|
|
2530
|
+
* const format = TomlFormat.autoDetectFormat(toml);
|
|
2531
|
+
* // format.trailingComma will be true
|
|
2532
|
+
* // format.newLine will be '\n'
|
|
2533
|
+
* // format.trailingNewline will be 0 (no trailing newline)
|
|
2534
|
+
* ```
|
|
2535
|
+
*/
|
|
2536
|
+
static autoDetectFormat(tomlString) {
|
|
2537
|
+
const format = TomlFormat.default();
|
|
2538
|
+
// Detect line ending style
|
|
2539
|
+
format.newLine = detectNewline(tomlString);
|
|
2540
|
+
// Detect trailing newline count
|
|
2541
|
+
format.trailingNewline = countTrailingNewlines(tomlString, format.newLine);
|
|
2542
|
+
// Parse the TOML to detect comma and bracket spacing usage patterns
|
|
2543
|
+
try {
|
|
2544
|
+
const ast = parseTOML(tomlString);
|
|
2545
|
+
// Convert to array once to avoid consuming the iterator multiple times
|
|
2546
|
+
const astArray = Array.from(ast);
|
|
2547
|
+
format.trailingComma = detectTrailingComma(astArray);
|
|
2548
|
+
format.bracketSpacing = detectBracketSpacing(tomlString, astArray);
|
|
2549
|
+
}
|
|
2550
|
+
catch (error) {
|
|
2551
|
+
// If parsing fails, fall back to defaults
|
|
2552
|
+
// This ensures the method is robust against malformed TOML
|
|
2553
|
+
format.trailingComma = DEFAULT_TRAILING_COMMA;
|
|
2554
|
+
format.bracketSpacing = DEFAULT_BRACKET_SPACING;
|
|
2555
|
+
}
|
|
2556
|
+
// inlineTableStart uses default value since auto-detection would require
|
|
2557
|
+
// complex analysis of nested table formatting preferences
|
|
2558
|
+
format.inlineTableStart = DEFAULT_INLINE_TABLE_START;
|
|
2559
|
+
return format;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
function formatTopLevel(document, format) {
|
|
2563
|
+
// If inlineTableStart is 0, convert all top-level tables to inline tables
|
|
2564
|
+
if (format.inlineTableStart === 0) {
|
|
2565
|
+
return document;
|
|
2566
|
+
}
|
|
1694
2567
|
const move_to_top_level = document.items.filter(item => {
|
|
1695
2568
|
if (!isKeyValue(item))
|
|
1696
2569
|
return false;
|
|
@@ -1698,7 +2571,12 @@ function formatTopLevel(document) {
|
|
|
1698
2571
|
const is_inline_array = isInlineArray(item.value) &&
|
|
1699
2572
|
item.value.items.length &&
|
|
1700
2573
|
isInlineTable(item.value.items[0].item);
|
|
1701
|
-
|
|
2574
|
+
// Only move to top level if the depth is less than inlineTableStart
|
|
2575
|
+
if (is_inline_table || is_inline_array) {
|
|
2576
|
+
const depth = calculateTableDepth(item.key.value);
|
|
2577
|
+
return format.inlineTableStart === undefined || depth < format.inlineTableStart;
|
|
2578
|
+
}
|
|
2579
|
+
return false;
|
|
1702
2580
|
});
|
|
1703
2581
|
move_to_top_level.forEach(node => {
|
|
1704
2582
|
remove(document, document, node);
|
|
@@ -1734,6 +2612,109 @@ function formatTableArray(key_value) {
|
|
|
1734
2612
|
applyWrites(root);
|
|
1735
2613
|
return root.items;
|
|
1736
2614
|
}
|
|
2615
|
+
/**
|
|
2616
|
+
* Updates a table's location end position after removing inline table items.
|
|
2617
|
+
* When inline table content is removed from a parent table, the parent table's
|
|
2618
|
+
* end position needs to be adjusted to reflect where the content actually ends.
|
|
2619
|
+
*
|
|
2620
|
+
* @param table - The table whose end position should be updated
|
|
2621
|
+
*/
|
|
2622
|
+
function postInlineItemRemovalAdjustment(table) {
|
|
2623
|
+
if (table.items.length > 0) {
|
|
2624
|
+
const lastItem = table.items[table.items.length - 1];
|
|
2625
|
+
table.loc.end.line = lastItem.loc.end.line;
|
|
2626
|
+
table.loc.end.column = lastItem.loc.end.column;
|
|
2627
|
+
}
|
|
2628
|
+
else {
|
|
2629
|
+
// If no items left, table ends at the header line
|
|
2630
|
+
table.loc.end.line = table.key.loc.end.line;
|
|
2631
|
+
table.loc.end.column = table.key.loc.end.column;
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Calculates the nesting depth of a table based on its key path.
|
|
2636
|
+
* Root level tables (e.g., [table]) have depth 0.
|
|
2637
|
+
* First level nested tables (e.g., [table.nested]) have depth 1.
|
|
2638
|
+
*
|
|
2639
|
+
* @param keyPath - Array representing the table key path (e.g., ['table', 'nested'])
|
|
2640
|
+
* @returns The nesting depth (0 for root level, 1+ for nested levels)
|
|
2641
|
+
*/
|
|
2642
|
+
function calculateTableDepth(keyPath) {
|
|
2643
|
+
return Math.max(0, keyPath.length - 1);
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Converts nested inline tables to separate table sections based on the inlineTableStart depth setting.
|
|
2647
|
+
* This function recursively processes all tables in the document and extracts inline tables that are
|
|
2648
|
+
* at a depth less than the inlineTableStart threshold.
|
|
2649
|
+
*/
|
|
2650
|
+
function formatNestedTablesMultiline(document, format) {
|
|
2651
|
+
// If inlineTableStart is undefined, use the default behavior (no conversion)
|
|
2652
|
+
// If inlineTableStart is 0, all should be inline (no conversion)
|
|
2653
|
+
if (format.inlineTableStart === undefined || format.inlineTableStart === 0) {
|
|
2654
|
+
return document;
|
|
2655
|
+
}
|
|
2656
|
+
const additionalTables = [];
|
|
2657
|
+
// Process all existing tables for nested inline tables
|
|
2658
|
+
for (const item of document.items) {
|
|
2659
|
+
if (isKeyValue(item) && isInlineTable(item.value)) {
|
|
2660
|
+
// This is a top-level inline table (depth 0)
|
|
2661
|
+
const depth = calculateTableDepth(item.key.value);
|
|
2662
|
+
if (depth < format.inlineTableStart) {
|
|
2663
|
+
// Convert to a separate table
|
|
2664
|
+
const table = formatTable(item);
|
|
2665
|
+
// Remove the original inline table item
|
|
2666
|
+
remove(document, document, item);
|
|
2667
|
+
// Add the new table
|
|
2668
|
+
insert(document, document, table);
|
|
2669
|
+
// Process this table for further nested inlines
|
|
2670
|
+
processTableForNestedInlines(table, additionalTables, format);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
else if (item.type === 'Table') {
|
|
2674
|
+
// Process existing table for nested inline tables
|
|
2675
|
+
processTableForNestedInlines(item, additionalTables, format);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
// Add all the additional tables to the document
|
|
2679
|
+
for (const table of additionalTables) {
|
|
2680
|
+
insert(document, document, table);
|
|
2681
|
+
}
|
|
2682
|
+
applyWrites(document);
|
|
2683
|
+
return document;
|
|
2684
|
+
}
|
|
2685
|
+
/**
|
|
2686
|
+
* Recursively processes a table for nested inline tables and extracts them as separate tables
|
|
2687
|
+
* when they are at a depth less than the inlineTableStart threshold.
|
|
2688
|
+
*/
|
|
2689
|
+
function processTableForNestedInlines(table, additionalTables, format) {
|
|
2690
|
+
var _a;
|
|
2691
|
+
// Process from end to beginning to avoid index issues when removing items
|
|
2692
|
+
for (let i = table.items.length - 1; i >= 0; i--) {
|
|
2693
|
+
const item = table.items[i];
|
|
2694
|
+
if (isKeyValue(item) && isInlineTable(item.value)) {
|
|
2695
|
+
// Calculate the depth of this nested table
|
|
2696
|
+
const nestedTableKey = [...table.key.item.value, ...item.key.value];
|
|
2697
|
+
const depth = calculateTableDepth(nestedTableKey);
|
|
2698
|
+
// Only convert to separate table if depth is less than inlineTableStart
|
|
2699
|
+
if (depth < ((_a = format.inlineTableStart) !== null && _a !== void 0 ? _a : 1)) {
|
|
2700
|
+
// Convert this inline table to a separate table section
|
|
2701
|
+
const separateTable = generateTable(nestedTableKey);
|
|
2702
|
+
// Move all items from the inline table to the separate table
|
|
2703
|
+
for (const inlineItem of item.value.items) {
|
|
2704
|
+
insert(separateTable, separateTable, inlineItem.item);
|
|
2705
|
+
}
|
|
2706
|
+
// Remove this item from the original table
|
|
2707
|
+
remove(table, table, item);
|
|
2708
|
+
// Update the parent table's end position after removal
|
|
2709
|
+
postInlineItemRemovalAdjustment(table);
|
|
2710
|
+
// Add this table to be inserted into the document
|
|
2711
|
+
additionalTables.push(separateTable);
|
|
2712
|
+
// Recursively process the new table for further nested inlines
|
|
2713
|
+
processTableForNestedInlines(separateTable, additionalTables, format);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
1737
2718
|
function formatPrintWidth(document, format) {
|
|
1738
2719
|
// TODO
|
|
1739
2720
|
return document;
|
|
@@ -1758,13 +2739,7 @@ function formatEmptyLines(document) {
|
|
|
1758
2739
|
return document;
|
|
1759
2740
|
}
|
|
1760
2741
|
|
|
1761
|
-
|
|
1762
|
-
printWidth: 80,
|
|
1763
|
-
trailingComma: false,
|
|
1764
|
-
bracketSpacing: true
|
|
1765
|
-
};
|
|
1766
|
-
function parseJS(value, format = {}) {
|
|
1767
|
-
format = Object.assign({}, default_format, format);
|
|
2742
|
+
function parseJS(value, format = TomlFormat.default()) {
|
|
1768
2743
|
value = toJSON(value);
|
|
1769
2744
|
// Reorder the elements in the object
|
|
1770
2745
|
value = reorderElements(value);
|
|
@@ -1776,8 +2751,10 @@ function parseJS(value, format = {}) {
|
|
|
1776
2751
|
// Heuristics:
|
|
1777
2752
|
// 1. Top-level objects/arrays should be tables/table arrays
|
|
1778
2753
|
// 2. Convert objects/arrays to tables/table arrays based on print width
|
|
1779
|
-
|
|
1780
|
-
|
|
2754
|
+
// 3. Convert nested inline tables to separate tables based on preferNestedTablesMultiline
|
|
2755
|
+
const formatted = pipe(document, document => formatTopLevel(document, format), document => formatNestedTablesMultiline(document, format), document => formatPrintWidth(document));
|
|
2756
|
+
// Apply formatEmptyLines only once at the end
|
|
2757
|
+
return formatEmptyLines(formatted);
|
|
1781
2758
|
}
|
|
1782
2759
|
/**
|
|
1783
2760
|
This function makes sure that properties that are simple values (not objects or arrays) are ordered first,
|
|
@@ -1910,9 +2887,7 @@ const BY_NEW_LINE = /(\r\n|\n)/g;
|
|
|
1910
2887
|
* const tomlString = toTOML(ast, '\n', { trailingNewline: 1 });
|
|
1911
2888
|
* ```
|
|
1912
2889
|
*/
|
|
1913
|
-
function toTOML(ast,
|
|
1914
|
-
var _a;
|
|
1915
|
-
const trailingNewline = (_a = options === null || options === void 0 ? void 0 : options.trailingNewline) !== null && _a !== void 0 ? _a : 1;
|
|
2890
|
+
function toTOML(ast, format) {
|
|
1916
2891
|
const lines = [];
|
|
1917
2892
|
traverse(ast, {
|
|
1918
2893
|
[NodeType.TableKey](node) {
|
|
@@ -1967,7 +2942,7 @@ function toTOML(ast, newline = '\n', options) {
|
|
|
1967
2942
|
write(lines, node.loc, node.raw);
|
|
1968
2943
|
}
|
|
1969
2944
|
});
|
|
1970
|
-
return lines.join(
|
|
2945
|
+
return lines.join(format.newLine) + format.newLine.repeat(format.trailingNewline);
|
|
1971
2946
|
}
|
|
1972
2947
|
/**
|
|
1973
2948
|
* Writes raw string content to specific location coordinates within a lines array.
|
|
@@ -2044,6 +3019,13 @@ function getLine(lines, index) {
|
|
|
2044
3019
|
return lines[index - 1];
|
|
2045
3020
|
}
|
|
2046
3021
|
|
|
3022
|
+
/**
|
|
3023
|
+
* Converts the given AST to a JavaScript object.
|
|
3024
|
+
*
|
|
3025
|
+
* @param ast The abstract syntax tree to convert.
|
|
3026
|
+
* @param input The original input string (used for error reporting).
|
|
3027
|
+
* @returns The JavaScript object representation of the AST.
|
|
3028
|
+
*/
|
|
2047
3029
|
function toJS(ast, input = '') {
|
|
2048
3030
|
const result = blank();
|
|
2049
3031
|
const tables = new Set();
|
|
@@ -2335,8 +3317,13 @@ function compareArrays(before, after, path = []) {
|
|
|
2335
3317
|
}
|
|
2336
3318
|
|
|
2337
3319
|
function findByPath(node, path) {
|
|
2338
|
-
if (!path.length)
|
|
3320
|
+
if (!path.length) {
|
|
3321
|
+
// If this is an InlineItem containing a KeyValue, return the KeyValue
|
|
3322
|
+
if (isInlineItem(node) && isKeyValue(node.item)) {
|
|
3323
|
+
return node.item;
|
|
3324
|
+
}
|
|
2339
3325
|
return node;
|
|
3326
|
+
}
|
|
2340
3327
|
if (isKeyValue(node)) {
|
|
2341
3328
|
return findByPath(node.value, path);
|
|
2342
3329
|
}
|
|
@@ -2419,6 +3406,12 @@ function findParent(node, path) {
|
|
|
2419
3406
|
*/
|
|
2420
3407
|
function patch(existing, updated, format) {
|
|
2421
3408
|
const existing_ast = parseTOML(existing);
|
|
3409
|
+
// Auto-detect formatting preferences from the existing TOML string for fallback
|
|
3410
|
+
const autoDetectedFormat = TomlFormat.autoDetectFormat(existing);
|
|
3411
|
+
const fmt = resolveTomlFormat(format, autoDetectedFormat);
|
|
3412
|
+
return patchAst(existing_ast, updated, fmt).tomlString;
|
|
3413
|
+
}
|
|
3414
|
+
function patchAst(existing_ast, updated, format) {
|
|
2422
3415
|
const items = [...existing_ast];
|
|
2423
3416
|
const existing_js = toJS(items);
|
|
2424
3417
|
const existing_document = {
|
|
@@ -2426,35 +3419,27 @@ function patch(existing, updated, format) {
|
|
|
2426
3419
|
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
2427
3420
|
items
|
|
2428
3421
|
};
|
|
2429
|
-
|
|
3422
|
+
// Certain formatting options should not be applied to the updated document during patching, because it would
|
|
3423
|
+
// override the existing formatting too aggressively. For example, preferNestedTablesMultiline would
|
|
3424
|
+
// convert all nested tables to multiline, which is not be desired during patching.
|
|
3425
|
+
// Therefore, we create a modified format for generating the updated document used for diffing.
|
|
3426
|
+
const diffing_fmt = resolveTomlFormat(Object.assign(Object.assign({}, format), { inlineTableStart: undefined }), format);
|
|
3427
|
+
const updated_document = parseJS(updated, diffing_fmt);
|
|
2430
3428
|
const changes = reorder(diff(existing_js, updated));
|
|
2431
|
-
|
|
3429
|
+
if (changes.length === 0) {
|
|
3430
|
+
return {
|
|
3431
|
+
tomlString: toTOML(items, format),
|
|
3432
|
+
document: existing_document
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
const patched_document = applyChanges(existing_document, updated_document, changes, format);
|
|
2432
3436
|
// Validate the patched_document
|
|
3437
|
+
// This would prevent overlapping element positions in the AST, but since those are handled at stringification time, we can skip this for now
|
|
2433
3438
|
//validate(patched_document);
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
if (lfIndex > 0 && existing.substring(lfIndex - 1, lfIndex) === '\r') {
|
|
2439
|
-
newline = '\r\n'; // File uses CRLF
|
|
2440
|
-
}
|
|
2441
|
-
// Count consecutive trailing newlines
|
|
2442
|
-
function countTrailingNewlines(str, newlineChar) {
|
|
2443
|
-
let count = 0;
|
|
2444
|
-
let pos = str.length;
|
|
2445
|
-
while (pos >= newlineChar.length) {
|
|
2446
|
-
if (str.substring(pos - newlineChar.length, pos) === newlineChar) {
|
|
2447
|
-
count++;
|
|
2448
|
-
pos -= newlineChar.length;
|
|
2449
|
-
}
|
|
2450
|
-
else {
|
|
2451
|
-
break;
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
return count;
|
|
2455
|
-
}
|
|
2456
|
-
const trailingNewlineCount = countTrailingNewlines(existing, newline);
|
|
2457
|
-
return toTOML(patched_document.items, newline, { trailingNewline: trailingNewlineCount });
|
|
3439
|
+
return {
|
|
3440
|
+
tomlString: toTOML(patched_document.items, format),
|
|
3441
|
+
document: patched_document
|
|
3442
|
+
};
|
|
2458
3443
|
}
|
|
2459
3444
|
function reorder(changes) {
|
|
2460
3445
|
for (let i = 0; i < changes.length; i++) {
|
|
@@ -2477,7 +3462,30 @@ function reorder(changes) {
|
|
|
2477
3462
|
}
|
|
2478
3463
|
return changes;
|
|
2479
3464
|
}
|
|
2480
|
-
|
|
3465
|
+
/**
|
|
3466
|
+
* Applies a list of changes to the original TOML document AST while preserving formatting and structure.
|
|
3467
|
+
*
|
|
3468
|
+
* This function processes different types of changes (Add, Edit, Remove, Move, Rename) and applies them
|
|
3469
|
+
* to the original document in a way that maintains the existing formatting preferences, comments, and
|
|
3470
|
+
* structural elements as much as possible. Special handling is provided for different node types like
|
|
3471
|
+
* inline tables, arrays, and table arrays to ensure proper formatting consistency.
|
|
3472
|
+
*
|
|
3473
|
+
* @param original - The original TOML document AST to be modified
|
|
3474
|
+
* @param updated - The updated document AST containing new values for changes
|
|
3475
|
+
* @param changes - Array of change objects describing what modifications to apply
|
|
3476
|
+
* @param format - Formatting preferences to use for newly added elements
|
|
3477
|
+
* @returns The modified original document with all changes applied
|
|
3478
|
+
*
|
|
3479
|
+
* @example
|
|
3480
|
+
* ```typescript
|
|
3481
|
+
* const changes = [
|
|
3482
|
+
* { type: 'add', path: ['newKey'], value: 'newValue' },
|
|
3483
|
+
* { type: 'edit', path: ['existingKey'], value: 'updatedValue' }
|
|
3484
|
+
* ];
|
|
3485
|
+
* const result = applyChanges(originalDoc, updatedDoc, changes, format);
|
|
3486
|
+
* ```
|
|
3487
|
+
*/
|
|
3488
|
+
function applyChanges(original, updated, changes, format) {
|
|
2481
3489
|
// Potential Changes:
|
|
2482
3490
|
//
|
|
2483
3491
|
// Add: Add key-value to object, add item to array
|
|
@@ -2499,6 +3507,7 @@ function applyChanges(original, updated, changes) {
|
|
|
2499
3507
|
is_table_array = true;
|
|
2500
3508
|
}
|
|
2501
3509
|
}
|
|
3510
|
+
// Determine the parent node where the new child will be inserted
|
|
2502
3511
|
let parent;
|
|
2503
3512
|
if (isTable(child)) {
|
|
2504
3513
|
parent = original;
|
|
@@ -2527,10 +3536,66 @@ function applyChanges(original, updated, changes) {
|
|
|
2527
3536
|
}
|
|
2528
3537
|
}
|
|
2529
3538
|
if (isTableArray(parent) || isInlineArray(parent) || isDocument(parent)) {
|
|
2530
|
-
|
|
3539
|
+
// Special handling for InlineArray: preserve original trailing comma format
|
|
3540
|
+
if (isInlineArray(parent)) {
|
|
3541
|
+
const originalHadTrailingCommas = arrayHadTrailingCommas(parent);
|
|
3542
|
+
// If this is an InlineItem being added to an array, check its comma setting
|
|
3543
|
+
if (isInlineItem(child)) {
|
|
3544
|
+
// The child comes from the updated document with global format applied
|
|
3545
|
+
// Override with the original array's format
|
|
3546
|
+
child.comma = originalHadTrailingCommas;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
// Check if we should convert nested inline tables to multiline tables
|
|
3550
|
+
if (format.inlineTableStart !== undefined && format.inlineTableStart > 0 && isDocument(parent) && isTable(child)) {
|
|
3551
|
+
const additionalTables = convertNestedInlineTablesToMultiline(child, original, format);
|
|
3552
|
+
// Insert the main table first
|
|
3553
|
+
insert(original, parent, child, index);
|
|
3554
|
+
// Then insert all the additional tables
|
|
3555
|
+
for (const table of additionalTables) {
|
|
3556
|
+
insert(original, original, table, undefined);
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
else {
|
|
3560
|
+
insert(original, parent, child, index);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
else if (isInlineTable(parent)) {
|
|
3564
|
+
// Special handling for adding KeyValue to InlineTable
|
|
3565
|
+
// Preserve original trailing comma format
|
|
3566
|
+
const originalHadTrailingCommas = tableHadTrailingCommas(parent);
|
|
3567
|
+
// InlineTable items must be wrapped in InlineItem
|
|
3568
|
+
if (isKeyValue(child)) {
|
|
3569
|
+
const inlineItem = generateInlineItem(child);
|
|
3570
|
+
// Override with the original table's format
|
|
3571
|
+
inlineItem.comma = originalHadTrailingCommas;
|
|
3572
|
+
insert(original, parent, inlineItem);
|
|
3573
|
+
}
|
|
3574
|
+
else {
|
|
3575
|
+
insert(original, parent, child);
|
|
3576
|
+
}
|
|
2531
3577
|
}
|
|
2532
3578
|
else {
|
|
2533
|
-
|
|
3579
|
+
// Check if we should convert inline tables to multiline tables when adding to existing tables
|
|
3580
|
+
if (format.inlineTableStart !== undefined && format.inlineTableStart > 0 && isKeyValue(child) && isInlineTable(child.value) && isTable(parent)) {
|
|
3581
|
+
// Calculate the depth of the inline table that would be created
|
|
3582
|
+
const baseTableKey = parent.key.item.value;
|
|
3583
|
+
const nestedTableKey = [...baseTableKey, ...child.key.value];
|
|
3584
|
+
const depth = calculateTableDepth(nestedTableKey);
|
|
3585
|
+
// Convert to separate section only if depth is less than inlineTableStart
|
|
3586
|
+
if (depth < format.inlineTableStart) {
|
|
3587
|
+
convertInlineTableToSeparateSection(child, parent, original, format);
|
|
3588
|
+
}
|
|
3589
|
+
else {
|
|
3590
|
+
insert(original, parent, child);
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
else if (format.inlineTableStart === 0 && isKeyValue(child) && isInlineTable(child.value) && isDocument(parent)) {
|
|
3594
|
+
insert(original, parent, child, undefined, true);
|
|
3595
|
+
}
|
|
3596
|
+
else {
|
|
3597
|
+
insert(original, parent, child);
|
|
3598
|
+
}
|
|
2534
3599
|
}
|
|
2535
3600
|
}
|
|
2536
3601
|
else if (isEdit(change)) {
|
|
@@ -2539,6 +3604,26 @@ function applyChanges(original, updated, changes) {
|
|
|
2539
3604
|
let parent;
|
|
2540
3605
|
if (isKeyValue(existing) && isKeyValue(replacement)) {
|
|
2541
3606
|
// Edit for key-value means value changes
|
|
3607
|
+
// Special handling for arrays: preserve original trailing comma format
|
|
3608
|
+
if (isInlineArray(existing.value) && isInlineArray(replacement.value)) {
|
|
3609
|
+
const originalHadTrailingCommas = arrayHadTrailingCommas(existing.value);
|
|
3610
|
+
const newArray = replacement.value;
|
|
3611
|
+
// Apply or remove trailing comma based on original format
|
|
3612
|
+
if (newArray.items.length > 0) {
|
|
3613
|
+
const lastItem = newArray.items[newArray.items.length - 1];
|
|
3614
|
+
lastItem.comma = originalHadTrailingCommas;
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
// Special handling for inline tables: preserve original trailing comma format
|
|
3618
|
+
if (isInlineTable(existing.value) && isInlineTable(replacement.value)) {
|
|
3619
|
+
const originalHadTrailingCommas = tableHadTrailingCommas(existing.value);
|
|
3620
|
+
const newTable = replacement.value;
|
|
3621
|
+
// Apply or remove trailing comma based on original format
|
|
3622
|
+
if (newTable.items.length > 0) {
|
|
3623
|
+
const lastItem = newTable.items[newTable.items.length - 1];
|
|
3624
|
+
lastItem.comma = originalHadTrailingCommas;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
2542
3627
|
parent = existing;
|
|
2543
3628
|
existing = existing.value;
|
|
2544
3629
|
replacement = replacement.value;
|
|
@@ -2550,8 +3635,23 @@ function applyChanges(original, updated, changes) {
|
|
|
2550
3635
|
existing = existing.value;
|
|
2551
3636
|
replacement = replacement.item.value;
|
|
2552
3637
|
}
|
|
3638
|
+
else if (isInlineItem(existing) && isKeyValue(replacement)) {
|
|
3639
|
+
// Editing inline table item: existing is InlineItem, replacement is KeyValue
|
|
3640
|
+
// We need to replace the KeyValue inside the InlineItem, preserving the InlineItem wrapper
|
|
3641
|
+
parent = existing;
|
|
3642
|
+
existing = existing.item;
|
|
3643
|
+
}
|
|
2553
3644
|
else {
|
|
2554
3645
|
parent = findParent(original, change.path);
|
|
3646
|
+
// Special handling for array element edits
|
|
3647
|
+
if (isKeyValue(parent)) {
|
|
3648
|
+
// Check if we're actually editing an array element
|
|
3649
|
+
const parentPath = change.path.slice(0, -1);
|
|
3650
|
+
const arrayNode = findByPath(original, parentPath);
|
|
3651
|
+
if (isKeyValue(arrayNode) && isInlineArray(arrayNode.value)) {
|
|
3652
|
+
parent = arrayNode.value;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
2555
3655
|
}
|
|
2556
3656
|
replace(original, parent, existing, replacement);
|
|
2557
3657
|
}
|
|
@@ -2585,6 +3685,329 @@ function applyChanges(original, updated, changes) {
|
|
|
2585
3685
|
applyWrites(original);
|
|
2586
3686
|
return original;
|
|
2587
3687
|
}
|
|
3688
|
+
/**
|
|
3689
|
+
* Converts nested inline tables to separate table sections based on the inlineTableStart depth setting.
|
|
3690
|
+
* This function recursively processes a table and extracts any inline tables within it,
|
|
3691
|
+
* creating separate table sections with properly nested keys.
|
|
3692
|
+
*
|
|
3693
|
+
* @param table - The table to process for nested inline tables
|
|
3694
|
+
* @param original - The original document for inserting new items
|
|
3695
|
+
* @param format - The formatting options
|
|
3696
|
+
* @returns Array of additional tables that should be added to the document
|
|
3697
|
+
*/
|
|
3698
|
+
function convertNestedInlineTablesToMultiline(table, original, format) {
|
|
3699
|
+
const additionalTables = [];
|
|
3700
|
+
const processTableForNestedInlines = (currentTable, tablesToAdd) => {
|
|
3701
|
+
var _a;
|
|
3702
|
+
for (let i = currentTable.items.length - 1; i >= 0; i--) {
|
|
3703
|
+
const item = currentTable.items[i];
|
|
3704
|
+
if (isKeyValue(item) && isInlineTable(item.value)) {
|
|
3705
|
+
// Calculate the depth of this nested table
|
|
3706
|
+
const nestedTableKey = [...currentTable.key.item.value, ...item.key.value];
|
|
3707
|
+
const depth = calculateTableDepth(nestedTableKey);
|
|
3708
|
+
// Only convert to separate table if depth is less than inlineTableStart
|
|
3709
|
+
if (depth < ((_a = format.inlineTableStart) !== null && _a !== void 0 ? _a : 1) && format.inlineTableStart !== 0) {
|
|
3710
|
+
// Convert this inline table to a separate table section
|
|
3711
|
+
const separateTable = generateTable(nestedTableKey);
|
|
3712
|
+
// Move all items from the inline table to the separate table
|
|
3713
|
+
for (const inlineItem of item.value.items) {
|
|
3714
|
+
if (isInlineItem(inlineItem) && isKeyValue(inlineItem.item)) {
|
|
3715
|
+
insert(original, separateTable, inlineItem.item, undefined);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
// Remove this item from the original table
|
|
3719
|
+
currentTable.items.splice(i, 1);
|
|
3720
|
+
// Update the parent table's end position after removal
|
|
3721
|
+
postInlineItemRemovalAdjustment(currentTable);
|
|
3722
|
+
// Queue this table to be added to the document
|
|
3723
|
+
tablesToAdd.push(separateTable);
|
|
3724
|
+
// Recursively process the new table for further nested inlines
|
|
3725
|
+
processTableForNestedInlines(separateTable, tablesToAdd);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
};
|
|
3730
|
+
processTableForNestedInlines(table, additionalTables);
|
|
3731
|
+
return additionalTables;
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Converts an inline table to a separate table section when adding to an existing table.
|
|
3735
|
+
* This function creates a new table section with the combined key path and moves all
|
|
3736
|
+
* properties from the inline table to the separate table section.
|
|
3737
|
+
*
|
|
3738
|
+
* @param child - The KeyValue node with an InlineTable as its value
|
|
3739
|
+
* @param parent - The parent table where the KeyValue would be added
|
|
3740
|
+
* @param original - The original document for inserting new items
|
|
3741
|
+
* @param format - The formatting options
|
|
3742
|
+
*/
|
|
3743
|
+
function convertInlineTableToSeparateSection(child, parent, original, format) {
|
|
3744
|
+
// Convert the inline table to a separate table section
|
|
3745
|
+
const baseTableKey = parent.key.item.value; // Get the parent table's key path
|
|
3746
|
+
const nestedTableKey = [...baseTableKey, ...child.key.value]; // Combine with the new key
|
|
3747
|
+
const separateTable = generateTable(nestedTableKey);
|
|
3748
|
+
// We know child.value is an InlineTable from the calling context
|
|
3749
|
+
if (isInlineTable(child.value)) {
|
|
3750
|
+
// Move all items from the inline table to the separate table
|
|
3751
|
+
for (const inlineItem of child.value.items) {
|
|
3752
|
+
if (isInlineItem(inlineItem) && isKeyValue(inlineItem.item)) {
|
|
3753
|
+
insert(original, separateTable, inlineItem.item, undefined);
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
// Add the separate table to the document
|
|
3758
|
+
insert(original, original, separateTable, undefined);
|
|
3759
|
+
// Update the parent table's end position since we're not adding the inline table to it
|
|
3760
|
+
postInlineItemRemovalAdjustment(parent);
|
|
3761
|
+
// Also handle any nested inline tables within the new table
|
|
3762
|
+
const additionalTables = convertNestedInlineTablesToMultiline(separateTable, original, format);
|
|
3763
|
+
for (const table of additionalTables) {
|
|
3764
|
+
insert(original, original, table, undefined);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
/******************************************************************************
|
|
3769
|
+
Copyright (c) Microsoft Corporation.
|
|
3770
|
+
|
|
3771
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
3772
|
+
purpose with or without fee is hereby granted.
|
|
3773
|
+
|
|
3774
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
3775
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
3776
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
3777
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
3778
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
3779
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3780
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
3781
|
+
***************************************************************************** */
|
|
3782
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
3783
|
+
|
|
3784
|
+
|
|
3785
|
+
function __classPrivateFieldGet(receiver, state, kind, f) {
|
|
3786
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3787
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
3788
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
3792
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3793
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
3794
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
3795
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
3799
|
+
var e = new Error(message);
|
|
3800
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3801
|
+
};
|
|
3802
|
+
|
|
3803
|
+
/**
|
|
3804
|
+
* Compares two positions to determine their ordering.
|
|
3805
|
+
* @param pos1 - First position
|
|
3806
|
+
* @param pos2 - Second position
|
|
3807
|
+
* @returns Negative if pos1 < pos2, 0 if equal, positive if pos1 > pos2
|
|
3808
|
+
*/
|
|
3809
|
+
function comparePositions(pos1, pos2) {
|
|
3810
|
+
if (pos1.line !== pos2.line) {
|
|
3811
|
+
return pos1.line - pos2.line;
|
|
3812
|
+
}
|
|
3813
|
+
return pos1.column - pos2.column;
|
|
3814
|
+
}
|
|
3815
|
+
/**
|
|
3816
|
+
* Truncates an AST based on a position (line, column) in the source string.
|
|
3817
|
+
*
|
|
3818
|
+
* This function filters the AST to include only the nodes that end before
|
|
3819
|
+
* the specified position. This ensures that blocks containing changes are
|
|
3820
|
+
* excluded and can be reparsed. This is useful for incremental parsing scenarios
|
|
3821
|
+
* where you want to keep only the unchanged portion of the AST.
|
|
3822
|
+
*
|
|
3823
|
+
* Special handling: If the truncation point falls within a Table or TableArray
|
|
3824
|
+
* (e.g., in a comment inside the table), the entire table is excluded to ensure
|
|
3825
|
+
* proper reparsing.
|
|
3826
|
+
*
|
|
3827
|
+
* @param ast - The AST to truncate
|
|
3828
|
+
* @param line - The line number (1-indexed) at which to truncate
|
|
3829
|
+
* @param column - The column number (0-indexed) at which to truncate
|
|
3830
|
+
* @returns An object containing the truncated AST and the end position of the last included node
|
|
3831
|
+
*
|
|
3832
|
+
* @example
|
|
3833
|
+
* ```typescript
|
|
3834
|
+
* const ast = parseTOML(tomlString);
|
|
3835
|
+
* // Get AST up to line 5, column 10 (only nodes that end before this position)
|
|
3836
|
+
* const { truncatedAst, lastEndPosition } = truncateAst(ast, 5, 10);
|
|
3837
|
+
* for (const node of truncatedAst) {
|
|
3838
|
+
* // process node
|
|
3839
|
+
* }
|
|
3840
|
+
* ```
|
|
3841
|
+
*/
|
|
3842
|
+
function truncateAst(ast, line, column) {
|
|
3843
|
+
const limit = { line, column };
|
|
3844
|
+
const nodes = [];
|
|
3845
|
+
let lastEndPosition = null;
|
|
3846
|
+
for (const node of ast) {
|
|
3847
|
+
const nodeEndsBeforeLimit = comparePositions(node.loc.end, limit) < 0;
|
|
3848
|
+
const nodeStartsBeforeLimit = comparePositions(node.loc.start, limit) < 0;
|
|
3849
|
+
if (nodeEndsBeforeLimit) {
|
|
3850
|
+
// Node completely ends before the limit - include it
|
|
3851
|
+
nodes.push(node);
|
|
3852
|
+
lastEndPosition = node.loc.end;
|
|
3853
|
+
}
|
|
3854
|
+
else if (nodeStartsBeforeLimit && !nodeEndsBeforeLimit) {
|
|
3855
|
+
// Node starts before the limit but ends at or after it
|
|
3856
|
+
// This means the truncation point is within this node
|
|
3857
|
+
// For Table/TableArray nodes, don't include them if the change is inside
|
|
3858
|
+
// This ensures the entire table gets reparsed
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
else {
|
|
3862
|
+
// Node starts at or after the limit - stop
|
|
3863
|
+
break;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
return {
|
|
3867
|
+
truncatedAst: nodes,
|
|
3868
|
+
lastEndPosition
|
|
3869
|
+
};
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
var _TomlDocument_ast, _TomlDocument_currentTomlString, _TomlDocument_Format;
|
|
3873
|
+
/**
|
|
3874
|
+
* TomlDocument encapsulates a TOML AST and provides methods to interact with it.
|
|
3875
|
+
*/
|
|
3876
|
+
class TomlDocument {
|
|
3877
|
+
/**
|
|
3878
|
+
* Initializes the TomlDocument with a TOML string, parsing it into an AST.
|
|
3879
|
+
* @param tomlString - The TOML string to parse
|
|
3880
|
+
*/
|
|
3881
|
+
constructor(tomlString) {
|
|
3882
|
+
_TomlDocument_ast.set(this, void 0);
|
|
3883
|
+
_TomlDocument_currentTomlString.set(this, void 0);
|
|
3884
|
+
_TomlDocument_Format.set(this, void 0);
|
|
3885
|
+
__classPrivateFieldSet(this, _TomlDocument_currentTomlString, tomlString, "f");
|
|
3886
|
+
__classPrivateFieldSet(this, _TomlDocument_ast, Array.from(parseTOML(tomlString)), "f");
|
|
3887
|
+
// Auto-detect formatting preferences from the original TOML string
|
|
3888
|
+
__classPrivateFieldSet(this, _TomlDocument_Format, TomlFormat.autoDetectFormat(tomlString), "f");
|
|
3889
|
+
}
|
|
3890
|
+
get toTomlString() {
|
|
3891
|
+
return __classPrivateFieldGet(this, _TomlDocument_currentTomlString, "f");
|
|
3892
|
+
}
|
|
3893
|
+
/**
|
|
3894
|
+
* Returns the JavaScript object representation of the TOML document.
|
|
3895
|
+
*/
|
|
3896
|
+
get toJsObject() {
|
|
3897
|
+
const jsObject = toJS(__classPrivateFieldGet(this, _TomlDocument_ast, "f"));
|
|
3898
|
+
// Convert custom date classes to regular JavaScript Date objects
|
|
3899
|
+
return convertCustomDateClasses(jsObject);
|
|
3900
|
+
}
|
|
3901
|
+
/**
|
|
3902
|
+
* Returns the internal AST (for testing purposes).
|
|
3903
|
+
* @internal
|
|
3904
|
+
*/
|
|
3905
|
+
get ast() {
|
|
3906
|
+
return __classPrivateFieldGet(this, _TomlDocument_ast, "f");
|
|
3907
|
+
}
|
|
3908
|
+
/**
|
|
3909
|
+
* Applies a patch to the current AST using a modified JS object.
|
|
3910
|
+
* Updates the internal AST. Use toTomlString getter to retrieve the updated TOML string.
|
|
3911
|
+
* @param updatedObject - The modified JS object to patch with
|
|
3912
|
+
* @param format - Optional formatting options
|
|
3913
|
+
*/
|
|
3914
|
+
patch(updatedObject, format) {
|
|
3915
|
+
const fmt = resolveTomlFormat(format, __classPrivateFieldGet(this, _TomlDocument_Format, "f"));
|
|
3916
|
+
const { tomlString, document } = patchAst(__classPrivateFieldGet(this, _TomlDocument_ast, "f"), updatedObject, fmt);
|
|
3917
|
+
__classPrivateFieldSet(this, _TomlDocument_ast, document.items, "f");
|
|
3918
|
+
__classPrivateFieldSet(this, _TomlDocument_currentTomlString, tomlString, "f");
|
|
3919
|
+
}
|
|
3920
|
+
/**
|
|
3921
|
+
* Updates the internal document by supplying a modified tomlString.
|
|
3922
|
+
* Use toJsObject getter to retrieve the updated JS object representation.
|
|
3923
|
+
* @param tomlString - The modified TOML string to update with
|
|
3924
|
+
*/
|
|
3925
|
+
update(tomlString) {
|
|
3926
|
+
if (tomlString === this.toTomlString) {
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
// Now, let's check where the first difference is
|
|
3930
|
+
const existingLines = this.toTomlString.split(__classPrivateFieldGet(this, _TomlDocument_Format, "f").newLine);
|
|
3931
|
+
const newLineChar = detectNewline(tomlString);
|
|
3932
|
+
const newTextLines = tomlString.split(newLineChar);
|
|
3933
|
+
let firstDiffLineIndex = 0;
|
|
3934
|
+
while (firstDiffLineIndex < existingLines.length &&
|
|
3935
|
+
firstDiffLineIndex < newTextLines.length &&
|
|
3936
|
+
existingLines[firstDiffLineIndex] === newTextLines[firstDiffLineIndex]) {
|
|
3937
|
+
firstDiffLineIndex++;
|
|
3938
|
+
}
|
|
3939
|
+
// Calculate the 1-based line number and 0-based column where the first difference occurs
|
|
3940
|
+
let firstDiffColumn = 0;
|
|
3941
|
+
// If we're within the bounds of both arrays, find the column where they differ
|
|
3942
|
+
if (firstDiffLineIndex < existingLines.length && firstDiffLineIndex < newTextLines.length) {
|
|
3943
|
+
const existingLine = existingLines[firstDiffLineIndex];
|
|
3944
|
+
const newLine = newTextLines[firstDiffLineIndex];
|
|
3945
|
+
// Find the first character position where the lines differ
|
|
3946
|
+
for (let i = 0; i < Math.max(existingLine.length, newLine.length); i++) {
|
|
3947
|
+
if (existingLine[i] !== newLine[i]) {
|
|
3948
|
+
firstDiffColumn = i;
|
|
3949
|
+
break;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
let firstDiffLine = firstDiffLineIndex + 1; // Convert to 1-based
|
|
3954
|
+
const { truncatedAst, lastEndPosition } = truncateAst(__classPrivateFieldGet(this, _TomlDocument_ast, "f"), firstDiffLine, firstDiffColumn);
|
|
3955
|
+
// Determine where to continue parsing from in the new string
|
|
3956
|
+
// If lastEndPosition exists, continue from there; otherwise from the start of the document
|
|
3957
|
+
const continueFromLine = lastEndPosition ? lastEndPosition.line : 1;
|
|
3958
|
+
const continueFromColumn = lastEndPosition ? lastEndPosition.column + 1 : 0;
|
|
3959
|
+
// Based on the first difference, we can re-parse only the affected part
|
|
3960
|
+
// We will need to supply the remaining string after where the AST was truncated
|
|
3961
|
+
const remainingLines = newTextLines.slice(continueFromLine - 1);
|
|
3962
|
+
// If there's a partial line match, we need to extract only the part after the continuation column
|
|
3963
|
+
if (remainingLines.length > 0 && continueFromColumn > 0) {
|
|
3964
|
+
remainingLines[0] = remainingLines[0].substring(continueFromColumn);
|
|
3965
|
+
}
|
|
3966
|
+
const remainingToml = remainingLines.join(__classPrivateFieldGet(this, _TomlDocument_Format, "f").newLine);
|
|
3967
|
+
__classPrivateFieldSet(this, _TomlDocument_ast, Array.from(continueParsingTOML(truncatedAst, remainingToml)), "f");
|
|
3968
|
+
__classPrivateFieldSet(this, _TomlDocument_currentTomlString, tomlString, "f");
|
|
3969
|
+
// Update the auto-detected format with the new string's characteristics
|
|
3970
|
+
__classPrivateFieldSet(this, _TomlDocument_Format, TomlFormat.autoDetectFormat(tomlString), "f");
|
|
3971
|
+
}
|
|
3972
|
+
/**
|
|
3973
|
+
* Overwrites the internal AST by fully re-parsing the supplied tomlString.
|
|
3974
|
+
* This is simpler but slower than update() which uses incremental parsing.
|
|
3975
|
+
* @param tomlString - The TOML string to overwrite with
|
|
3976
|
+
*/
|
|
3977
|
+
overwrite(tomlString) {
|
|
3978
|
+
if (tomlString === this.toTomlString) {
|
|
3979
|
+
return;
|
|
3980
|
+
}
|
|
3981
|
+
// Re-parse the entire document
|
|
3982
|
+
__classPrivateFieldSet(this, _TomlDocument_ast, Array.from(parseTOML(tomlString)), "f");
|
|
3983
|
+
__classPrivateFieldSet(this, _TomlDocument_currentTomlString, tomlString, "f");
|
|
3984
|
+
// Update the auto-detected format with the new string's characteristics
|
|
3985
|
+
__classPrivateFieldSet(this, _TomlDocument_Format, TomlFormat.autoDetectFormat(tomlString), "f");
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
_TomlDocument_ast = new WeakMap(), _TomlDocument_currentTomlString = new WeakMap(), _TomlDocument_Format = new WeakMap();
|
|
3989
|
+
/**
|
|
3990
|
+
* Recursively converts custom date classes to regular JavaScript Date objects.
|
|
3991
|
+
* This ensures that the toJsObject property returns standard Date objects
|
|
3992
|
+
* while preserving the custom classes internally for TOML formatting.
|
|
3993
|
+
*/
|
|
3994
|
+
function convertCustomDateClasses(obj) {
|
|
3995
|
+
if (obj instanceof Date) {
|
|
3996
|
+
// Convert custom date classes to regular Date objects
|
|
3997
|
+
return new Date(obj.getTime());
|
|
3998
|
+
}
|
|
3999
|
+
else if (Array.isArray(obj)) {
|
|
4000
|
+
return obj.map(convertCustomDateClasses);
|
|
4001
|
+
}
|
|
4002
|
+
else if (obj && typeof obj === 'object') {
|
|
4003
|
+
const result = {};
|
|
4004
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
4005
|
+
result[key] = convertCustomDateClasses(value);
|
|
4006
|
+
}
|
|
4007
|
+
return result;
|
|
4008
|
+
}
|
|
4009
|
+
return obj;
|
|
4010
|
+
}
|
|
2588
4011
|
|
|
2589
4012
|
/**
|
|
2590
4013
|
* Parses a TOML string into a JavaScript object.
|
|
@@ -2606,8 +4029,9 @@ function parse(value) {
|
|
|
2606
4029
|
* @returns The stringified TOML representation
|
|
2607
4030
|
*/
|
|
2608
4031
|
function stringify(value, format) {
|
|
2609
|
-
const
|
|
2610
|
-
|
|
4032
|
+
const fmt = resolveTomlFormat(format, TomlFormat.default());
|
|
4033
|
+
const document = parseJS(value, fmt);
|
|
4034
|
+
return toTOML(document.items, fmt);
|
|
2611
4035
|
}
|
|
2612
4036
|
|
|
2613
|
-
export { parse, patch, stringify };
|
|
4037
|
+
export { TomlDocument, TomlFormat, parse, patch, stringify };
|