@nyaomaru/divider 2.0.6 → 2.0.7
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/dist/index.cjs +107 -24
- package/dist/index.d.cts +83 -23
- package/dist/index.d.ts +83 -23
- package/dist/index.js +107 -24
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -122,7 +122,10 @@ function isNoneMode(value) {
|
|
|
122
122
|
function normalizeSeparators(separators) {
|
|
123
123
|
return Array.from(new Set(separators)).filter(
|
|
124
124
|
(separator) => !isEmptyString(separator)
|
|
125
|
-
);
|
|
125
|
+
).sort((left, right) => right.length - left.length);
|
|
126
|
+
}
|
|
127
|
+
function createCacheKey(normalizedSeparators) {
|
|
128
|
+
return normalizedSeparators.join(CACHE_KEY_SEPARATOR);
|
|
126
129
|
}
|
|
127
130
|
var RegexCache = class {
|
|
128
131
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -138,6 +141,15 @@ var RegexCache = class {
|
|
|
138
141
|
*/
|
|
139
142
|
get(separators) {
|
|
140
143
|
const key = this.createKey(separators);
|
|
144
|
+
return this.getByKey(key);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Retrieves a cached RegExp for a precomputed cache key.
|
|
148
|
+
*
|
|
149
|
+
* @param key - Cache key string
|
|
150
|
+
* @returns Cached RegExp or null if not found
|
|
151
|
+
*/
|
|
152
|
+
getByKey(key) {
|
|
141
153
|
const regex = this.cache.get(key);
|
|
142
154
|
if (regex) {
|
|
143
155
|
this.cache.delete(key);
|
|
@@ -153,6 +165,15 @@ var RegexCache = class {
|
|
|
153
165
|
*/
|
|
154
166
|
set(separators, regex) {
|
|
155
167
|
const key = this.createKey(separators);
|
|
168
|
+
this.setByKey(key, regex);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Stores a RegExp in the cache for a precomputed cache key.
|
|
172
|
+
*
|
|
173
|
+
* @param key - Cache key string
|
|
174
|
+
* @param regex - Compiled RegExp to cache
|
|
175
|
+
*/
|
|
176
|
+
setByKey(key, regex) {
|
|
156
177
|
if (this.cache.has(key)) {
|
|
157
178
|
this.cache.delete(key);
|
|
158
179
|
}
|
|
@@ -173,7 +194,7 @@ var RegexCache = class {
|
|
|
173
194
|
*/
|
|
174
195
|
createKey(separators) {
|
|
175
196
|
const normalizedSeparators = normalizeSeparators(separators);
|
|
176
|
-
return normalizedSeparators
|
|
197
|
+
return createCacheKey(normalizedSeparators);
|
|
177
198
|
}
|
|
178
199
|
/**
|
|
179
200
|
* Gets current cache size for debugging/monitoring.
|
|
@@ -194,15 +215,16 @@ var RegexCache = class {
|
|
|
194
215
|
var regexCache = new RegexCache();
|
|
195
216
|
function getRegex(separators) {
|
|
196
217
|
if (isEmptyArray(separators)) return null;
|
|
197
|
-
const cached = regexCache.get(separators);
|
|
198
|
-
if (cached) return cached;
|
|
199
218
|
const uniqueSeparators = normalizeSeparators(separators);
|
|
200
219
|
if (uniqueSeparators.length === 0) {
|
|
201
220
|
return null;
|
|
202
221
|
}
|
|
222
|
+
const cacheKey = createCacheKey(uniqueSeparators);
|
|
223
|
+
const cached = regexCache.getByKey(cacheKey);
|
|
224
|
+
if (cached) return cached;
|
|
203
225
|
const pattern = uniqueSeparators.map(escapeRegExp).join("|");
|
|
204
226
|
const regex = new RegExp(`(?:${pattern})`, "g");
|
|
205
|
-
regexCache.
|
|
227
|
+
regexCache.setByKey(cacheKey, regex);
|
|
206
228
|
return regex;
|
|
207
229
|
}
|
|
208
230
|
function escapeRegExp(str) {
|
|
@@ -326,6 +348,13 @@ function classifySeparators(args) {
|
|
|
326
348
|
);
|
|
327
349
|
}
|
|
328
350
|
|
|
351
|
+
// src/utils/transform-divider-input.ts
|
|
352
|
+
function transformDividerInput(input, transform, options) {
|
|
353
|
+
const result = isString(input) ? transform(input) : input.map(transform);
|
|
354
|
+
const resolvedOptions = options ?? {};
|
|
355
|
+
return applyDividerOptions(result, resolvedOptions);
|
|
356
|
+
}
|
|
357
|
+
|
|
329
358
|
// src/core/divider.ts
|
|
330
359
|
function divider(input, ...args) {
|
|
331
360
|
if (!isValidInput(input)) {
|
|
@@ -342,8 +371,7 @@ function divider(input, ...args) {
|
|
|
342
371
|
const applyDivision = (str) => divideString(str, numSeparators, strSeparators, {
|
|
343
372
|
preserveEmpty: options.preserveEmpty
|
|
344
373
|
});
|
|
345
|
-
|
|
346
|
-
return applyDividerOptions(result, options);
|
|
374
|
+
return transformDividerInput(input, applyDivision, options);
|
|
347
375
|
}
|
|
348
376
|
|
|
349
377
|
// src/core/divider-first.ts
|
|
@@ -394,15 +422,15 @@ function dividerLoop(input, size, options) {
|
|
|
394
422
|
console.warn("dividerLoop: chunk size must be a positive number");
|
|
395
423
|
return [];
|
|
396
424
|
}
|
|
397
|
-
const resolvedOptions = options ?? {};
|
|
398
425
|
const {
|
|
399
426
|
startOffset = PERFORMANCE_CONSTANTS.DEFAULT_START_OFFSET,
|
|
400
427
|
maxChunks = PERFORMANCE_CONSTANTS.DEFAULT_MAX_CHUNKS
|
|
401
|
-
} =
|
|
402
|
-
|
|
403
|
-
|
|
428
|
+
} = options ?? {};
|
|
429
|
+
return transformDividerInput(
|
|
430
|
+
input,
|
|
431
|
+
(str) => createChunksFromString(str, size, startOffset, maxChunks),
|
|
432
|
+
options
|
|
404
433
|
);
|
|
405
|
-
return applyDividerOptions(result, resolvedOptions);
|
|
406
434
|
}
|
|
407
435
|
|
|
408
436
|
// src/utils/divide.ts
|
|
@@ -412,9 +440,7 @@ function divideNumberString(str) {
|
|
|
412
440
|
|
|
413
441
|
// src/core/divider-number-string.ts
|
|
414
442
|
function dividerNumberString(input, options) {
|
|
415
|
-
|
|
416
|
-
const resolvedOptions = options ?? {};
|
|
417
|
-
return applyDividerOptions(result, resolvedOptions);
|
|
443
|
+
return transformDividerInput(input, divideNumberString, options);
|
|
418
444
|
}
|
|
419
445
|
|
|
420
446
|
// src/utils/quoted.ts
|
|
@@ -422,13 +448,41 @@ function dividePreserve(input, separator) {
|
|
|
422
448
|
if (isEmptyString(input)) return [""];
|
|
423
449
|
return divider(input, separator, { preserveEmpty: true });
|
|
424
450
|
}
|
|
425
|
-
|
|
426
|
-
|
|
451
|
+
var countUnescapedSingleChar = (text, quote) => {
|
|
452
|
+
let count = 0;
|
|
453
|
+
for (let index = 0; index < text.length; index++) {
|
|
454
|
+
if (text[index] !== quote) continue;
|
|
455
|
+
if (text[index + 1] === quote) {
|
|
456
|
+
index++;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
count++;
|
|
460
|
+
}
|
|
461
|
+
return count;
|
|
462
|
+
};
|
|
463
|
+
var countUnescapedMultiChar = (text, quote) => {
|
|
464
|
+
const escapedPair = quote + quote;
|
|
465
|
+
const quoteLength = quote.length;
|
|
466
|
+
const escapedPairLength = escapedPair.length;
|
|
427
467
|
let count = 0;
|
|
428
|
-
for (
|
|
429
|
-
|
|
468
|
+
for (let index = 0; index < text.length; ) {
|
|
469
|
+
if (text.startsWith(escapedPair, index)) {
|
|
470
|
+
index += escapedPairLength;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (text.startsWith(quote, index)) {
|
|
474
|
+
count++;
|
|
475
|
+
index += quoteLength;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
index++;
|
|
430
479
|
}
|
|
431
480
|
return count;
|
|
481
|
+
};
|
|
482
|
+
function countUnescaped(text, quote) {
|
|
483
|
+
if (isEmptyString(quote)) return 0;
|
|
484
|
+
if (quote.length === 1) return countUnescapedSingleChar(text, quote);
|
|
485
|
+
return countUnescapedMultiChar(text, quote);
|
|
432
486
|
}
|
|
433
487
|
function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
|
|
434
488
|
const escapedPair = quoteChar + quoteChar;
|
|
@@ -480,7 +534,8 @@ var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
|
480
534
|
const pieces = dividePreserve(line, delimiter);
|
|
481
535
|
const state = {
|
|
482
536
|
fields: [],
|
|
483
|
-
current: ""
|
|
537
|
+
current: "",
|
|
538
|
+
insideQuotes: false
|
|
484
539
|
};
|
|
485
540
|
for (const piece of pieces) {
|
|
486
541
|
appendPiece(state, piece, delimiter, quote, trim, lenient);
|
|
@@ -490,10 +545,18 @@ var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
|
490
545
|
}
|
|
491
546
|
return state.fields;
|
|
492
547
|
};
|
|
548
|
+
var advanceQuoteState = (insideQuotes, segment, quote) => {
|
|
549
|
+
let nextInsideQuotes = insideQuotes;
|
|
550
|
+
for (const char of segment) {
|
|
551
|
+
if (char === quote) nextInsideQuotes = !nextInsideQuotes;
|
|
552
|
+
}
|
|
553
|
+
return nextInsideQuotes;
|
|
554
|
+
};
|
|
493
555
|
var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
556
|
+
const segment = isEmptyString(state.current) ? piece : delimiter + piece;
|
|
557
|
+
state.current += segment;
|
|
558
|
+
state.insideQuotes = quote.length === 1 ? advanceQuoteState(state.insideQuotes, segment, quote) : countUnescaped(state.current, quote) % 2 === 1;
|
|
559
|
+
if (!state.insideQuotes) {
|
|
497
560
|
flushField(state, quote, trim, lenient);
|
|
498
561
|
}
|
|
499
562
|
};
|
|
@@ -502,6 +565,7 @@ var flushField = (state, quote, trim, lenient) => {
|
|
|
502
565
|
if (trim) fieldValue = fieldValue.trim();
|
|
503
566
|
state.fields.push(fieldValue);
|
|
504
567
|
state.current = "";
|
|
568
|
+
state.insideQuotes = false;
|
|
505
569
|
};
|
|
506
570
|
|
|
507
571
|
// src/presets/csv-divider.ts
|
|
@@ -553,8 +617,27 @@ function tryExtractQuery(input) {
|
|
|
553
617
|
const url = new URL(input);
|
|
554
618
|
return url.search.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? url.search.slice(1) : url.search;
|
|
555
619
|
} catch {
|
|
556
|
-
return input;
|
|
620
|
+
return extractQueryFromQuestionMark(input);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function extractQueryFromQuestionMark(input) {
|
|
624
|
+
const fragmentIndex = input.indexOf("#");
|
|
625
|
+
const withoutFragment = fragmentIndex >= 0 ? input.slice(0, fragmentIndex) : input;
|
|
626
|
+
const questionMarkIndex = withoutFragment.indexOf(
|
|
627
|
+
QUERY_SEPARATORS.QUESTION_MARK
|
|
628
|
+
);
|
|
629
|
+
if (questionMarkIndex < 0) {
|
|
630
|
+
return withoutFragment;
|
|
631
|
+
}
|
|
632
|
+
if (questionMarkIndex === 0) {
|
|
633
|
+
return withoutFragment.slice(1);
|
|
634
|
+
}
|
|
635
|
+
const prefix = withoutFragment.slice(0, questionMarkIndex);
|
|
636
|
+
const hasQuerySeparatorBefore = prefix.includes(QUERY_SEPARATORS.AMPERSAND) || prefix.includes(QUERY_SEPARATORS.EQUALS);
|
|
637
|
+
if (hasQuerySeparatorBefore) {
|
|
638
|
+
return withoutFragment;
|
|
557
639
|
}
|
|
640
|
+
return withoutFragment.slice(questionMarkIndex + 1);
|
|
558
641
|
}
|
|
559
642
|
function stripLeadingQuestionMark(query) {
|
|
560
643
|
return query.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? query.slice(1) : query;
|
package/dist/index.d.cts
CHANGED
|
@@ -21,21 +21,42 @@ declare const QUERY_DECODE_MODES: {
|
|
|
21
21
|
readonly RAW: "raw";
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Supported exclusion modes used to filter divider output.
|
|
26
|
+
*/
|
|
24
27
|
type DividerExcludeMode = (typeof DIVIDER_EXCLUDE_MODES)[keyof typeof DIVIDER_EXCLUDE_MODES];
|
|
28
|
+
/**
|
|
29
|
+
* Single string input accepted by divider functions.
|
|
30
|
+
*/
|
|
25
31
|
type StringInput = string;
|
|
32
|
+
/**
|
|
33
|
+
* Readonly string array input accepted by divider functions.
|
|
34
|
+
*/
|
|
26
35
|
type StringArrayInput = readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Supported input shapes for divider functions.
|
|
38
|
+
*/
|
|
27
39
|
type DividerInput = StringInput | StringArrayInput;
|
|
40
|
+
/**
|
|
41
|
+
* Flat divider result.
|
|
42
|
+
*/
|
|
28
43
|
type DividerStringResult = string[];
|
|
44
|
+
/**
|
|
45
|
+
* Nested divider result for `string[]` inputs when `flatten` is not enabled.
|
|
46
|
+
*/
|
|
29
47
|
type DividerArrayResult = string[][];
|
|
48
|
+
/**
|
|
49
|
+
* Shared options supported by divider functions.
|
|
50
|
+
*/
|
|
30
51
|
type DividerOptions = {
|
|
31
52
|
/** If true, flattens nested arrays into a single array */
|
|
32
|
-
flatten?: boolean;
|
|
53
|
+
readonly flatten?: boolean;
|
|
33
54
|
/** If true, trims whitespace from each divided segment */
|
|
34
|
-
trim?: boolean;
|
|
55
|
+
readonly trim?: boolean;
|
|
35
56
|
/** If true, retains empty segments produced by division */
|
|
36
|
-
preserveEmpty?: boolean;
|
|
57
|
+
readonly preserveEmpty?: boolean;
|
|
37
58
|
/** Controls how empty or whitespace segments are handled */
|
|
38
|
-
exclude?: DividerExcludeMode;
|
|
59
|
+
readonly exclude?: DividerExcludeMode;
|
|
39
60
|
};
|
|
40
61
|
/**
|
|
41
62
|
* Represents the absence of user-specified divider options while retaining
|
|
@@ -52,37 +73,76 @@ type DividerEmptyOptions = {
|
|
|
52
73
|
readonly exclude?: never;
|
|
53
74
|
};
|
|
54
75
|
type DividerInferredOptions = DividerOptions | DividerEmptyOptions;
|
|
55
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Computes whether the provided options explicitly enable `flatten`.
|
|
78
|
+
*/
|
|
79
|
+
type IsFlattenEnabled<TOptions extends DividerInferredOptions> = TOptions extends {
|
|
56
80
|
readonly flatten?: infer Flag;
|
|
57
|
-
} ? Flag extends true ? true : false : false;
|
|
58
|
-
|
|
81
|
+
} ? [Flag] extends [true] ? true : false : false;
|
|
82
|
+
/**
|
|
83
|
+
* Resolves the divider output shape from the input and options.
|
|
84
|
+
*/
|
|
85
|
+
type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : IsFlattenEnabled<TOptions> extends true ? DividerStringResult : DividerArrayResult;
|
|
86
|
+
/**
|
|
87
|
+
* Numeric separator interpreted as an index.
|
|
88
|
+
*/
|
|
89
|
+
type NumericSeparator = number;
|
|
90
|
+
/**
|
|
91
|
+
* String separator interpreted as a delimiter.
|
|
92
|
+
*/
|
|
93
|
+
type StringSeparator = string;
|
|
94
|
+
/**
|
|
95
|
+
* Any supported divider separator.
|
|
96
|
+
*/
|
|
97
|
+
type DividerSeparator = NumericSeparator | StringSeparator;
|
|
98
|
+
/**
|
|
99
|
+
* A readonly list of separators without options.
|
|
100
|
+
*/
|
|
101
|
+
type DividerSeparators = readonly DividerSeparator[];
|
|
102
|
+
/**
|
|
103
|
+
* Any single divider argument.
|
|
104
|
+
*/
|
|
105
|
+
type DividerArg = DividerSeparator | DividerOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Divider arguments with an optional trailing options object.
|
|
108
|
+
*
|
|
109
|
+
* WHY: Runtime only consumes options when they are passed last. This tuple
|
|
110
|
+
* shape keeps the public type contract aligned with that behavior.
|
|
111
|
+
*/
|
|
112
|
+
type DividerArgs = DividerSeparators | readonly [...DividerSeparator[], DividerOptions];
|
|
113
|
+
/**
|
|
114
|
+
* Extracts trailing divider options from a variadic argument tuple.
|
|
115
|
+
*/
|
|
116
|
+
type ExtractedDividerOptions<TArgs extends readonly unknown[]> = TArgs extends readonly [...unknown[], infer Last] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions;
|
|
59
117
|
/**
|
|
60
118
|
* Computes the divider return type based on the input and provided arguments.
|
|
61
119
|
*
|
|
62
120
|
* WHY: When no arguments are provided, divider returns the input as-is for
|
|
63
121
|
* string arrays. This type keeps the signature aligned with runtime behavior.
|
|
64
122
|
*/
|
|
65
|
-
type DividerReturn<T extends DividerInput, TArgs extends DividerArgs> = T extends StringInput ? DividerStringResult : TArgs['length'] extends 0 ? T :
|
|
66
|
-
|
|
123
|
+
type DividerReturn<T extends DividerInput, TArgs extends DividerArgs> = T extends StringInput ? DividerStringResult : TArgs['length'] extends 0 ? T : IsFlattenEnabled<ExtractedDividerOptions<TArgs>> extends true ? DividerStringResult : DividerArrayResult;
|
|
124
|
+
/**
|
|
125
|
+
* Additional options supported by `dividerLoop`.
|
|
126
|
+
*/
|
|
67
127
|
type DividerLoopOptions = DividerOptions & {
|
|
68
128
|
/** Starting position for the division (0-based) */
|
|
69
|
-
startOffset?: number;
|
|
129
|
+
readonly startOffset?: number;
|
|
70
130
|
/** Maximum number of chunks to produce */
|
|
71
|
-
maxChunks?: number;
|
|
131
|
+
readonly maxChunks?: number;
|
|
72
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Represents the absence of loop-specific options while preserving inference.
|
|
135
|
+
*/
|
|
73
136
|
type DividerLoopEmptyOptions = DividerEmptyOptions & {
|
|
74
137
|
/** Placeholder to signal startOffset is intentionally absent */
|
|
75
138
|
readonly startOffset?: never;
|
|
76
139
|
/** Placeholder to signal maxChunks is intentionally absent */
|
|
77
140
|
readonly maxChunks?: never;
|
|
78
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Accepted option shapes for `dividerLoop`.
|
|
144
|
+
*/
|
|
79
145
|
type DividerLoopOptionsLike = DividerLoopOptions | DividerLoopEmptyOptions;
|
|
80
|
-
type NumericSeparator = number;
|
|
81
|
-
type StringSeparator = string;
|
|
82
|
-
type DividerSeparator = NumericSeparator | StringSeparator;
|
|
83
|
-
type DividerSeparators = readonly DividerSeparator[];
|
|
84
|
-
type DividerArg = DividerSeparator | DividerOptions;
|
|
85
|
-
type DividerArgs = readonly DividerArg[];
|
|
86
146
|
|
|
87
147
|
/**
|
|
88
148
|
* Main divider function that splits input based on numeric positions or string delimiters.
|
|
@@ -96,7 +156,7 @@ type DividerArgs = readonly DividerArg[];
|
|
|
96
156
|
* @param args - Array of separators (numbers/strings) and optional options object
|
|
97
157
|
* @returns Divided string segments based on input type and options
|
|
98
158
|
*/
|
|
99
|
-
declare function divider<T extends DividerInput, const TArgs extends
|
|
159
|
+
declare function divider<T extends DividerInput, const TArgs extends DividerArgs>(input: T, ...args: TArgs): DividerReturn<T, TArgs>;
|
|
100
160
|
|
|
101
161
|
/**
|
|
102
162
|
* Extracts the first segment after dividing the input using specified separators.
|
|
@@ -160,22 +220,22 @@ declare function dividerNumberString<T extends DividerInput, O extends DividerIn
|
|
|
160
220
|
|
|
161
221
|
type EmailDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
162
222
|
/** Split top-level domain from the rest of the email address. */
|
|
163
|
-
splitTLD?: boolean;
|
|
223
|
+
readonly splitTLD?: boolean;
|
|
164
224
|
};
|
|
165
225
|
type CsvDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
166
226
|
/** Character used for quoting values. */
|
|
167
|
-
quoteChar?: string;
|
|
227
|
+
readonly quoteChar?: string;
|
|
168
228
|
/** Character used to separate CSV fields. */
|
|
169
|
-
delimiter?: string;
|
|
229
|
+
readonly delimiter?: string;
|
|
170
230
|
};
|
|
171
231
|
type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
172
232
|
/** Collapse empty segments produced by leading/trailing or repeated separators. */
|
|
173
|
-
collapse?: boolean;
|
|
233
|
+
readonly collapse?: boolean;
|
|
174
234
|
};
|
|
175
235
|
type QueryDecodeMode = (typeof QUERY_DECODE_MODES)[keyof typeof QUERY_DECODE_MODES];
|
|
176
236
|
type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
177
237
|
/** Decoding mode: 'auto' applies standard URL decoding; 'raw' leaves values untouched. */
|
|
178
|
-
mode?: QueryDecodeMode;
|
|
238
|
+
readonly mode?: QueryDecodeMode;
|
|
179
239
|
};
|
|
180
240
|
|
|
181
241
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -21,21 +21,42 @@ declare const QUERY_DECODE_MODES: {
|
|
|
21
21
|
readonly RAW: "raw";
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Supported exclusion modes used to filter divider output.
|
|
26
|
+
*/
|
|
24
27
|
type DividerExcludeMode = (typeof DIVIDER_EXCLUDE_MODES)[keyof typeof DIVIDER_EXCLUDE_MODES];
|
|
28
|
+
/**
|
|
29
|
+
* Single string input accepted by divider functions.
|
|
30
|
+
*/
|
|
25
31
|
type StringInput = string;
|
|
32
|
+
/**
|
|
33
|
+
* Readonly string array input accepted by divider functions.
|
|
34
|
+
*/
|
|
26
35
|
type StringArrayInput = readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Supported input shapes for divider functions.
|
|
38
|
+
*/
|
|
27
39
|
type DividerInput = StringInput | StringArrayInput;
|
|
40
|
+
/**
|
|
41
|
+
* Flat divider result.
|
|
42
|
+
*/
|
|
28
43
|
type DividerStringResult = string[];
|
|
44
|
+
/**
|
|
45
|
+
* Nested divider result for `string[]` inputs when `flatten` is not enabled.
|
|
46
|
+
*/
|
|
29
47
|
type DividerArrayResult = string[][];
|
|
48
|
+
/**
|
|
49
|
+
* Shared options supported by divider functions.
|
|
50
|
+
*/
|
|
30
51
|
type DividerOptions = {
|
|
31
52
|
/** If true, flattens nested arrays into a single array */
|
|
32
|
-
flatten?: boolean;
|
|
53
|
+
readonly flatten?: boolean;
|
|
33
54
|
/** If true, trims whitespace from each divided segment */
|
|
34
|
-
trim?: boolean;
|
|
55
|
+
readonly trim?: boolean;
|
|
35
56
|
/** If true, retains empty segments produced by division */
|
|
36
|
-
preserveEmpty?: boolean;
|
|
57
|
+
readonly preserveEmpty?: boolean;
|
|
37
58
|
/** Controls how empty or whitespace segments are handled */
|
|
38
|
-
exclude?: DividerExcludeMode;
|
|
59
|
+
readonly exclude?: DividerExcludeMode;
|
|
39
60
|
};
|
|
40
61
|
/**
|
|
41
62
|
* Represents the absence of user-specified divider options while retaining
|
|
@@ -52,37 +73,76 @@ type DividerEmptyOptions = {
|
|
|
52
73
|
readonly exclude?: never;
|
|
53
74
|
};
|
|
54
75
|
type DividerInferredOptions = DividerOptions | DividerEmptyOptions;
|
|
55
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Computes whether the provided options explicitly enable `flatten`.
|
|
78
|
+
*/
|
|
79
|
+
type IsFlattenEnabled<TOptions extends DividerInferredOptions> = TOptions extends {
|
|
56
80
|
readonly flatten?: infer Flag;
|
|
57
|
-
} ? Flag extends true ? true : false : false;
|
|
58
|
-
|
|
81
|
+
} ? [Flag] extends [true] ? true : false : false;
|
|
82
|
+
/**
|
|
83
|
+
* Resolves the divider output shape from the input and options.
|
|
84
|
+
*/
|
|
85
|
+
type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : IsFlattenEnabled<TOptions> extends true ? DividerStringResult : DividerArrayResult;
|
|
86
|
+
/**
|
|
87
|
+
* Numeric separator interpreted as an index.
|
|
88
|
+
*/
|
|
89
|
+
type NumericSeparator = number;
|
|
90
|
+
/**
|
|
91
|
+
* String separator interpreted as a delimiter.
|
|
92
|
+
*/
|
|
93
|
+
type StringSeparator = string;
|
|
94
|
+
/**
|
|
95
|
+
* Any supported divider separator.
|
|
96
|
+
*/
|
|
97
|
+
type DividerSeparator = NumericSeparator | StringSeparator;
|
|
98
|
+
/**
|
|
99
|
+
* A readonly list of separators without options.
|
|
100
|
+
*/
|
|
101
|
+
type DividerSeparators = readonly DividerSeparator[];
|
|
102
|
+
/**
|
|
103
|
+
* Any single divider argument.
|
|
104
|
+
*/
|
|
105
|
+
type DividerArg = DividerSeparator | DividerOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Divider arguments with an optional trailing options object.
|
|
108
|
+
*
|
|
109
|
+
* WHY: Runtime only consumes options when they are passed last. This tuple
|
|
110
|
+
* shape keeps the public type contract aligned with that behavior.
|
|
111
|
+
*/
|
|
112
|
+
type DividerArgs = DividerSeparators | readonly [...DividerSeparator[], DividerOptions];
|
|
113
|
+
/**
|
|
114
|
+
* Extracts trailing divider options from a variadic argument tuple.
|
|
115
|
+
*/
|
|
116
|
+
type ExtractedDividerOptions<TArgs extends readonly unknown[]> = TArgs extends readonly [...unknown[], infer Last] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions;
|
|
59
117
|
/**
|
|
60
118
|
* Computes the divider return type based on the input and provided arguments.
|
|
61
119
|
*
|
|
62
120
|
* WHY: When no arguments are provided, divider returns the input as-is for
|
|
63
121
|
* string arrays. This type keeps the signature aligned with runtime behavior.
|
|
64
122
|
*/
|
|
65
|
-
type DividerReturn<T extends DividerInput, TArgs extends DividerArgs> = T extends StringInput ? DividerStringResult : TArgs['length'] extends 0 ? T :
|
|
66
|
-
|
|
123
|
+
type DividerReturn<T extends DividerInput, TArgs extends DividerArgs> = T extends StringInput ? DividerStringResult : TArgs['length'] extends 0 ? T : IsFlattenEnabled<ExtractedDividerOptions<TArgs>> extends true ? DividerStringResult : DividerArrayResult;
|
|
124
|
+
/**
|
|
125
|
+
* Additional options supported by `dividerLoop`.
|
|
126
|
+
*/
|
|
67
127
|
type DividerLoopOptions = DividerOptions & {
|
|
68
128
|
/** Starting position for the division (0-based) */
|
|
69
|
-
startOffset?: number;
|
|
129
|
+
readonly startOffset?: number;
|
|
70
130
|
/** Maximum number of chunks to produce */
|
|
71
|
-
maxChunks?: number;
|
|
131
|
+
readonly maxChunks?: number;
|
|
72
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Represents the absence of loop-specific options while preserving inference.
|
|
135
|
+
*/
|
|
73
136
|
type DividerLoopEmptyOptions = DividerEmptyOptions & {
|
|
74
137
|
/** Placeholder to signal startOffset is intentionally absent */
|
|
75
138
|
readonly startOffset?: never;
|
|
76
139
|
/** Placeholder to signal maxChunks is intentionally absent */
|
|
77
140
|
readonly maxChunks?: never;
|
|
78
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Accepted option shapes for `dividerLoop`.
|
|
144
|
+
*/
|
|
79
145
|
type DividerLoopOptionsLike = DividerLoopOptions | DividerLoopEmptyOptions;
|
|
80
|
-
type NumericSeparator = number;
|
|
81
|
-
type StringSeparator = string;
|
|
82
|
-
type DividerSeparator = NumericSeparator | StringSeparator;
|
|
83
|
-
type DividerSeparators = readonly DividerSeparator[];
|
|
84
|
-
type DividerArg = DividerSeparator | DividerOptions;
|
|
85
|
-
type DividerArgs = readonly DividerArg[];
|
|
86
146
|
|
|
87
147
|
/**
|
|
88
148
|
* Main divider function that splits input based on numeric positions or string delimiters.
|
|
@@ -96,7 +156,7 @@ type DividerArgs = readonly DividerArg[];
|
|
|
96
156
|
* @param args - Array of separators (numbers/strings) and optional options object
|
|
97
157
|
* @returns Divided string segments based on input type and options
|
|
98
158
|
*/
|
|
99
|
-
declare function divider<T extends DividerInput, const TArgs extends
|
|
159
|
+
declare function divider<T extends DividerInput, const TArgs extends DividerArgs>(input: T, ...args: TArgs): DividerReturn<T, TArgs>;
|
|
100
160
|
|
|
101
161
|
/**
|
|
102
162
|
* Extracts the first segment after dividing the input using specified separators.
|
|
@@ -160,22 +220,22 @@ declare function dividerNumberString<T extends DividerInput, O extends DividerIn
|
|
|
160
220
|
|
|
161
221
|
type EmailDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
162
222
|
/** Split top-level domain from the rest of the email address. */
|
|
163
|
-
splitTLD?: boolean;
|
|
223
|
+
readonly splitTLD?: boolean;
|
|
164
224
|
};
|
|
165
225
|
type CsvDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
166
226
|
/** Character used for quoting values. */
|
|
167
|
-
quoteChar?: string;
|
|
227
|
+
readonly quoteChar?: string;
|
|
168
228
|
/** Character used to separate CSV fields. */
|
|
169
|
-
delimiter?: string;
|
|
229
|
+
readonly delimiter?: string;
|
|
170
230
|
};
|
|
171
231
|
type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
172
232
|
/** Collapse empty segments produced by leading/trailing or repeated separators. */
|
|
173
|
-
collapse?: boolean;
|
|
233
|
+
readonly collapse?: boolean;
|
|
174
234
|
};
|
|
175
235
|
type QueryDecodeMode = (typeof QUERY_DECODE_MODES)[keyof typeof QUERY_DECODE_MODES];
|
|
176
236
|
type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
177
237
|
/** Decoding mode: 'auto' applies standard URL decoding; 'raw' leaves values untouched. */
|
|
178
|
-
mode?: QueryDecodeMode;
|
|
238
|
+
readonly mode?: QueryDecodeMode;
|
|
179
239
|
};
|
|
180
240
|
|
|
181
241
|
/**
|
package/dist/index.js
CHANGED
|
@@ -88,7 +88,10 @@ function isNoneMode(value) {
|
|
|
88
88
|
function normalizeSeparators(separators) {
|
|
89
89
|
return Array.from(new Set(separators)).filter(
|
|
90
90
|
(separator) => !isEmptyString(separator)
|
|
91
|
-
);
|
|
91
|
+
).sort((left, right) => right.length - left.length);
|
|
92
|
+
}
|
|
93
|
+
function createCacheKey(normalizedSeparators) {
|
|
94
|
+
return normalizedSeparators.join(CACHE_KEY_SEPARATOR);
|
|
92
95
|
}
|
|
93
96
|
var RegexCache = class {
|
|
94
97
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -104,6 +107,15 @@ var RegexCache = class {
|
|
|
104
107
|
*/
|
|
105
108
|
get(separators) {
|
|
106
109
|
const key = this.createKey(separators);
|
|
110
|
+
return this.getByKey(key);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Retrieves a cached RegExp for a precomputed cache key.
|
|
114
|
+
*
|
|
115
|
+
* @param key - Cache key string
|
|
116
|
+
* @returns Cached RegExp or null if not found
|
|
117
|
+
*/
|
|
118
|
+
getByKey(key) {
|
|
107
119
|
const regex = this.cache.get(key);
|
|
108
120
|
if (regex) {
|
|
109
121
|
this.cache.delete(key);
|
|
@@ -119,6 +131,15 @@ var RegexCache = class {
|
|
|
119
131
|
*/
|
|
120
132
|
set(separators, regex) {
|
|
121
133
|
const key = this.createKey(separators);
|
|
134
|
+
this.setByKey(key, regex);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Stores a RegExp in the cache for a precomputed cache key.
|
|
138
|
+
*
|
|
139
|
+
* @param key - Cache key string
|
|
140
|
+
* @param regex - Compiled RegExp to cache
|
|
141
|
+
*/
|
|
142
|
+
setByKey(key, regex) {
|
|
122
143
|
if (this.cache.has(key)) {
|
|
123
144
|
this.cache.delete(key);
|
|
124
145
|
}
|
|
@@ -139,7 +160,7 @@ var RegexCache = class {
|
|
|
139
160
|
*/
|
|
140
161
|
createKey(separators) {
|
|
141
162
|
const normalizedSeparators = normalizeSeparators(separators);
|
|
142
|
-
return normalizedSeparators
|
|
163
|
+
return createCacheKey(normalizedSeparators);
|
|
143
164
|
}
|
|
144
165
|
/**
|
|
145
166
|
* Gets current cache size for debugging/monitoring.
|
|
@@ -160,15 +181,16 @@ var RegexCache = class {
|
|
|
160
181
|
var regexCache = new RegexCache();
|
|
161
182
|
function getRegex(separators) {
|
|
162
183
|
if (isEmptyArray(separators)) return null;
|
|
163
|
-
const cached = regexCache.get(separators);
|
|
164
|
-
if (cached) return cached;
|
|
165
184
|
const uniqueSeparators = normalizeSeparators(separators);
|
|
166
185
|
if (uniqueSeparators.length === 0) {
|
|
167
186
|
return null;
|
|
168
187
|
}
|
|
188
|
+
const cacheKey = createCacheKey(uniqueSeparators);
|
|
189
|
+
const cached = regexCache.getByKey(cacheKey);
|
|
190
|
+
if (cached) return cached;
|
|
169
191
|
const pattern = uniqueSeparators.map(escapeRegExp).join("|");
|
|
170
192
|
const regex = new RegExp(`(?:${pattern})`, "g");
|
|
171
|
-
regexCache.
|
|
193
|
+
regexCache.setByKey(cacheKey, regex);
|
|
172
194
|
return regex;
|
|
173
195
|
}
|
|
174
196
|
function escapeRegExp(str) {
|
|
@@ -292,6 +314,13 @@ function classifySeparators(args) {
|
|
|
292
314
|
);
|
|
293
315
|
}
|
|
294
316
|
|
|
317
|
+
// src/utils/transform-divider-input.ts
|
|
318
|
+
function transformDividerInput(input, transform, options) {
|
|
319
|
+
const result = isString(input) ? transform(input) : input.map(transform);
|
|
320
|
+
const resolvedOptions = options ?? {};
|
|
321
|
+
return applyDividerOptions(result, resolvedOptions);
|
|
322
|
+
}
|
|
323
|
+
|
|
295
324
|
// src/core/divider.ts
|
|
296
325
|
function divider(input, ...args) {
|
|
297
326
|
if (!isValidInput(input)) {
|
|
@@ -308,8 +337,7 @@ function divider(input, ...args) {
|
|
|
308
337
|
const applyDivision = (str) => divideString(str, numSeparators, strSeparators, {
|
|
309
338
|
preserveEmpty: options.preserveEmpty
|
|
310
339
|
});
|
|
311
|
-
|
|
312
|
-
return applyDividerOptions(result, options);
|
|
340
|
+
return transformDividerInput(input, applyDivision, options);
|
|
313
341
|
}
|
|
314
342
|
|
|
315
343
|
// src/core/divider-first.ts
|
|
@@ -360,15 +388,15 @@ function dividerLoop(input, size, options) {
|
|
|
360
388
|
console.warn("dividerLoop: chunk size must be a positive number");
|
|
361
389
|
return [];
|
|
362
390
|
}
|
|
363
|
-
const resolvedOptions = options ?? {};
|
|
364
391
|
const {
|
|
365
392
|
startOffset = PERFORMANCE_CONSTANTS.DEFAULT_START_OFFSET,
|
|
366
393
|
maxChunks = PERFORMANCE_CONSTANTS.DEFAULT_MAX_CHUNKS
|
|
367
|
-
} =
|
|
368
|
-
|
|
369
|
-
|
|
394
|
+
} = options ?? {};
|
|
395
|
+
return transformDividerInput(
|
|
396
|
+
input,
|
|
397
|
+
(str) => createChunksFromString(str, size, startOffset, maxChunks),
|
|
398
|
+
options
|
|
370
399
|
);
|
|
371
|
-
return applyDividerOptions(result, resolvedOptions);
|
|
372
400
|
}
|
|
373
401
|
|
|
374
402
|
// src/utils/divide.ts
|
|
@@ -378,9 +406,7 @@ function divideNumberString(str) {
|
|
|
378
406
|
|
|
379
407
|
// src/core/divider-number-string.ts
|
|
380
408
|
function dividerNumberString(input, options) {
|
|
381
|
-
|
|
382
|
-
const resolvedOptions = options ?? {};
|
|
383
|
-
return applyDividerOptions(result, resolvedOptions);
|
|
409
|
+
return transformDividerInput(input, divideNumberString, options);
|
|
384
410
|
}
|
|
385
411
|
|
|
386
412
|
// src/utils/quoted.ts
|
|
@@ -388,13 +414,41 @@ function dividePreserve(input, separator) {
|
|
|
388
414
|
if (isEmptyString(input)) return [""];
|
|
389
415
|
return divider(input, separator, { preserveEmpty: true });
|
|
390
416
|
}
|
|
391
|
-
|
|
392
|
-
|
|
417
|
+
var countUnescapedSingleChar = (text, quote) => {
|
|
418
|
+
let count = 0;
|
|
419
|
+
for (let index = 0; index < text.length; index++) {
|
|
420
|
+
if (text[index] !== quote) continue;
|
|
421
|
+
if (text[index + 1] === quote) {
|
|
422
|
+
index++;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
count++;
|
|
426
|
+
}
|
|
427
|
+
return count;
|
|
428
|
+
};
|
|
429
|
+
var countUnescapedMultiChar = (text, quote) => {
|
|
430
|
+
const escapedPair = quote + quote;
|
|
431
|
+
const quoteLength = quote.length;
|
|
432
|
+
const escapedPairLength = escapedPair.length;
|
|
393
433
|
let count = 0;
|
|
394
|
-
for (
|
|
395
|
-
|
|
434
|
+
for (let index = 0; index < text.length; ) {
|
|
435
|
+
if (text.startsWith(escapedPair, index)) {
|
|
436
|
+
index += escapedPairLength;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (text.startsWith(quote, index)) {
|
|
440
|
+
count++;
|
|
441
|
+
index += quoteLength;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
index++;
|
|
396
445
|
}
|
|
397
446
|
return count;
|
|
447
|
+
};
|
|
448
|
+
function countUnescaped(text, quote) {
|
|
449
|
+
if (isEmptyString(quote)) return 0;
|
|
450
|
+
if (quote.length === 1) return countUnescapedSingleChar(text, quote);
|
|
451
|
+
return countUnescapedMultiChar(text, quote);
|
|
398
452
|
}
|
|
399
453
|
function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
|
|
400
454
|
const escapedPair = quoteChar + quoteChar;
|
|
@@ -446,7 +500,8 @@ var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
|
446
500
|
const pieces = dividePreserve(line, delimiter);
|
|
447
501
|
const state = {
|
|
448
502
|
fields: [],
|
|
449
|
-
current: ""
|
|
503
|
+
current: "",
|
|
504
|
+
insideQuotes: false
|
|
450
505
|
};
|
|
451
506
|
for (const piece of pieces) {
|
|
452
507
|
appendPiece(state, piece, delimiter, quote, trim, lenient);
|
|
@@ -456,10 +511,18 @@ var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
|
456
511
|
}
|
|
457
512
|
return state.fields;
|
|
458
513
|
};
|
|
514
|
+
var advanceQuoteState = (insideQuotes, segment, quote) => {
|
|
515
|
+
let nextInsideQuotes = insideQuotes;
|
|
516
|
+
for (const char of segment) {
|
|
517
|
+
if (char === quote) nextInsideQuotes = !nextInsideQuotes;
|
|
518
|
+
}
|
|
519
|
+
return nextInsideQuotes;
|
|
520
|
+
};
|
|
459
521
|
var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
522
|
+
const segment = isEmptyString(state.current) ? piece : delimiter + piece;
|
|
523
|
+
state.current += segment;
|
|
524
|
+
state.insideQuotes = quote.length === 1 ? advanceQuoteState(state.insideQuotes, segment, quote) : countUnescaped(state.current, quote) % 2 === 1;
|
|
525
|
+
if (!state.insideQuotes) {
|
|
463
526
|
flushField(state, quote, trim, lenient);
|
|
464
527
|
}
|
|
465
528
|
};
|
|
@@ -468,6 +531,7 @@ var flushField = (state, quote, trim, lenient) => {
|
|
|
468
531
|
if (trim) fieldValue = fieldValue.trim();
|
|
469
532
|
state.fields.push(fieldValue);
|
|
470
533
|
state.current = "";
|
|
534
|
+
state.insideQuotes = false;
|
|
471
535
|
};
|
|
472
536
|
|
|
473
537
|
// src/presets/csv-divider.ts
|
|
@@ -519,8 +583,27 @@ function tryExtractQuery(input) {
|
|
|
519
583
|
const url = new URL(input);
|
|
520
584
|
return url.search.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? url.search.slice(1) : url.search;
|
|
521
585
|
} catch {
|
|
522
|
-
return input;
|
|
586
|
+
return extractQueryFromQuestionMark(input);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function extractQueryFromQuestionMark(input) {
|
|
590
|
+
const fragmentIndex = input.indexOf("#");
|
|
591
|
+
const withoutFragment = fragmentIndex >= 0 ? input.slice(0, fragmentIndex) : input;
|
|
592
|
+
const questionMarkIndex = withoutFragment.indexOf(
|
|
593
|
+
QUERY_SEPARATORS.QUESTION_MARK
|
|
594
|
+
);
|
|
595
|
+
if (questionMarkIndex < 0) {
|
|
596
|
+
return withoutFragment;
|
|
597
|
+
}
|
|
598
|
+
if (questionMarkIndex === 0) {
|
|
599
|
+
return withoutFragment.slice(1);
|
|
600
|
+
}
|
|
601
|
+
const prefix = withoutFragment.slice(0, questionMarkIndex);
|
|
602
|
+
const hasQuerySeparatorBefore = prefix.includes(QUERY_SEPARATORS.AMPERSAND) || prefix.includes(QUERY_SEPARATORS.EQUALS);
|
|
603
|
+
if (hasQuerySeparatorBefore) {
|
|
604
|
+
return withoutFragment;
|
|
523
605
|
}
|
|
606
|
+
return withoutFragment.slice(questionMarkIndex + 1);
|
|
524
607
|
}
|
|
525
608
|
function stripLeadingQuestionMark(query) {
|
|
526
609
|
return query.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? query.slice(1) : query;
|