@nyaomaru/divider 1.9.19 → 1.9.21

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 CHANGED
@@ -61,6 +61,15 @@ var PATH_SEPARATORS = {
61
61
  SLASH: "/",
62
62
  ALT: "|"
63
63
  };
64
+ var QUERY_SEPARATORS = {
65
+ QUESTION_MARK: "?",
66
+ AMPERSAND: "&",
67
+ EQUALS: "="
68
+ };
69
+ var QUERY_DECODE_MODES = {
70
+ AUTO: "auto",
71
+ RAW: "raw"
72
+ };
64
73
 
65
74
  // src/utils/is.ts
66
75
  function isString(value) {
@@ -224,12 +233,10 @@ function sortAscending(numbers) {
224
233
 
225
234
  // src/utils/parser.ts
226
235
  function divideString(input, numSeparators, strSeparators, options) {
227
- if (isEmptyArray(numSeparators) && isEmptyArray(strSeparators)) {
236
+ if (hasNoSeparators(numSeparators, strSeparators)) {
228
237
  return [input];
229
238
  }
230
- if (!Array.isArray(numSeparators) || !numSeparators.every(isNumber)) {
231
- throw new Error("Invalid numeric separators");
232
- }
239
+ assertValidNumSeparators(numSeparators);
233
240
  const regex = getRegex(strSeparators);
234
241
  const shouldPreserveEmpty = options?.preserveEmpty === true;
235
242
  const sortedNumSeparators = sortAscending(numSeparators);
@@ -237,6 +244,12 @@ function divideString(input, numSeparators, strSeparators, options) {
237
244
  const segments = regex ? parts.flatMap((part) => part.split(regex)) : parts;
238
245
  return shouldPreserveEmpty ? segments : segments.filter((segment) => !isEmptyString(segment));
239
246
  }
247
+ var hasNoSeparators = (numSeparators, strSeparators) => isEmptyArray(numSeparators) && isEmptyArray(strSeparators);
248
+ var assertValidNumSeparators = (numSeparators) => {
249
+ if (!Array.isArray(numSeparators) || !numSeparators.every(isNumber)) {
250
+ throw new Error("Invalid numeric separators");
251
+ }
252
+ };
240
253
 
241
254
  // src/utils/array.ts
242
255
  function ensureStringArray(input) {
@@ -275,29 +288,32 @@ function trimNestedSegments(rows, preserveEmpty) {
275
288
  return rows.map((row) => trimSegments(row, preserveEmpty));
276
289
  }
277
290
  function applyDividerOptions(result, options) {
278
- let output = result;
279
291
  const shouldPreserveEmpty = options.preserveEmpty === true;
280
- if (options.trim) {
281
- output = isNestedStringArray(output) ? trimNestedSegments(output, shouldPreserveEmpty) : trimSegments(output, shouldPreserveEmpty);
282
- }
283
- if (options.flatten) {
284
- output = output.flat();
285
- }
286
- if (!isNoneMode(options.exclude)) {
287
- const exclude = options.exclude ?? DIVIDER_EXCLUDE_MODES.NONE;
288
- let shouldKeep = () => true;
289
- if (exclude in excludePredicateMap) {
290
- shouldKeep = excludePredicateMap[exclude];
291
- }
292
- const filterNested = (arr) => {
293
- const filteredRows = arr.map((row) => row.filter(shouldKeep));
294
- return shouldPreserveEmpty ? filteredRows : filteredRows.filter((row) => row.length > 0);
295
- };
296
- const filterFlat = (arr) => arr.filter(shouldKeep);
297
- output = isNestedStringArray(output) ? filterNested(output) : filterFlat(output);
298
- }
292
+ let output = result;
293
+ output = applyTrimOption(output, options, shouldPreserveEmpty);
294
+ output = applyFlattenOption(output, options);
295
+ output = applyExcludeOption(output, options, shouldPreserveEmpty);
299
296
  return output;
300
297
  }
298
+ var applyTrimOption = (output, options, shouldPreserveEmpty) => {
299
+ if (!options.trim) return output;
300
+ return isNestedStringArray(output) ? trimNestedSegments(output, shouldPreserveEmpty) : trimSegments(output, shouldPreserveEmpty);
301
+ };
302
+ var applyFlattenOption = (output, options) => options.flatten ? output.flat() : output;
303
+ var applyExcludeOption = (output, options, shouldPreserveEmpty) => {
304
+ if (isNoneMode(options.exclude)) return output;
305
+ const exclude = options.exclude ?? DIVIDER_EXCLUDE_MODES.NONE;
306
+ let shouldKeep = () => true;
307
+ if (exclude in excludePredicateMap) {
308
+ shouldKeep = excludePredicateMap[exclude];
309
+ }
310
+ const filterNested = (arr) => {
311
+ const filteredRows = arr.map((row) => row.filter(shouldKeep));
312
+ return shouldPreserveEmpty ? filteredRows : filteredRows.filter((row) => row.length > 0);
313
+ };
314
+ const filterFlat = (arr) => arr.filter(shouldKeep);
315
+ return isNestedStringArray(output) ? filterNested(output) : filterFlat(output);
316
+ };
301
317
 
302
318
  // src/utils/separator.ts
303
319
  function classifySeparators(args) {
@@ -422,28 +438,8 @@ function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
422
438
  const escapedPair = quoteChar + quoteChar;
423
439
  const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
424
440
  const restoreEscapedQuotes = (fieldText) => fieldText.split(escapedPair).join(quoteChar);
425
- let left = 0;
426
- let right = text.length - 1;
427
- while (left <= right && isWhitespace(text[left])) left++;
428
- while (right >= left && isWhitespace(text[right])) right--;
429
- if (left > right) return restoreEscapedQuotes(text);
430
- const startsWithQuote = text[left] === quoteChar;
431
- if (!startsWithQuote) return restoreEscapedQuotes(text);
432
- const endsWithQuote = text[right] === quoteChar;
433
- if (endsWithQuote && right > left) {
434
- const withoutPair = text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
435
- return restoreEscapedQuotes(withoutPair);
436
- }
437
- if (!lenient) return restoreEscapedQuotes(text);
438
- let result = text.slice(0, left) + text.slice(left + 1);
439
- let lastNonSpaceIndexAfterTrim = result.length - 1;
440
- while (lastNonSpaceIndexAfterTrim >= 0 && isWhitespace(result[lastNonSpaceIndexAfterTrim])) {
441
- lastNonSpaceIndexAfterTrim--;
442
- }
443
- if (lastNonSpaceIndexAfterTrim >= 0 && result[lastNonSpaceIndexAfterTrim] === quoteChar) {
444
- result = result.slice(0, lastNonSpaceIndexAfterTrim) + result.slice(lastNonSpaceIndexAfterTrim + 1);
445
- }
446
- return restoreEscapedQuotes(result);
441
+ const stripped = stripOuterQuotesRaw(text, quoteChar, lenient, isWhitespace);
442
+ return restoreEscapedQuotes(stripped);
447
443
  }
448
444
  function quotedDivide(line, {
449
445
  delimiter = ",",
@@ -452,24 +448,65 @@ function quotedDivide(line, {
452
448
  lenient = true
453
449
  } = {}) {
454
450
  if (isEmptyString(line)) return [""];
451
+ return buildQuotedFields(line, delimiter, quote, trim, lenient);
452
+ }
453
+ var findNonSpaceBounds = (text, isWhitespace) => {
454
+ let left = 0;
455
+ let right = text.length - 1;
456
+ while (left <= right && isWhitespace(text[left])) left++;
457
+ while (right >= left && isWhitespace(text[right])) right--;
458
+ return { left, right };
459
+ };
460
+ var stripMatchedOuterQuotes = (text, left, right) => text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
461
+ var removeCharAt = (text, index) => text.slice(0, index) + text.slice(index + 1);
462
+ var stripTrailingQuote = (text, quoteChar, isWhitespace) => {
463
+ let lastNonSpaceIndex = text.length - 1;
464
+ while (lastNonSpaceIndex >= 0 && isWhitespace(text[lastNonSpaceIndex])) {
465
+ lastNonSpaceIndex--;
466
+ }
467
+ if (lastNonSpaceIndex < 0 || text[lastNonSpaceIndex] !== quoteChar) {
468
+ return text;
469
+ }
470
+ return removeCharAt(text, lastNonSpaceIndex);
471
+ };
472
+ var stripOuterQuotesRaw = (text, quoteChar, lenient, isWhitespace) => {
473
+ const { left, right } = findNonSpaceBounds(text, isWhitespace);
474
+ if (left > right) return text;
475
+ if (text[left] !== quoteChar) return text;
476
+ if (text[right] === quoteChar && right > left) {
477
+ return stripMatchedOuterQuotes(text, left, right);
478
+ }
479
+ if (!lenient) return text;
480
+ const withoutLeading = removeCharAt(text, left);
481
+ return stripTrailingQuote(withoutLeading, quoteChar, isWhitespace);
482
+ };
483
+ var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
455
484
  const pieces = dividePreserve(line, delimiter);
456
- const fields = [];
457
- let currentFieldBuffer = "";
458
- let insideQuotes = false;
459
- const flush = () => {
460
- let fieldValue = stripOuterQuotes(currentFieldBuffer, quote, { lenient });
461
- if (trim) fieldValue = fieldValue.trim();
462
- fields.push(fieldValue);
463
- currentFieldBuffer = "";
485
+ const state = {
486
+ fields: [],
487
+ current: ""
464
488
  };
465
489
  for (const piece of pieces) {
466
- currentFieldBuffer = isEmptyString(currentFieldBuffer) ? piece : currentFieldBuffer + delimiter + piece;
467
- insideQuotes = countUnescaped(currentFieldBuffer, quote) % 2 === 1;
468
- if (!insideQuotes) flush();
490
+ appendPiece(state, piece, delimiter, quote, trim, lenient);
469
491
  }
470
- if (!isEmptyString(currentFieldBuffer)) flush();
471
- return fields;
472
- }
492
+ if (!isEmptyString(state.current)) {
493
+ flushField(state, quote, trim, lenient);
494
+ }
495
+ return state.fields;
496
+ };
497
+ var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
498
+ state.current = isEmptyString(state.current) ? piece : state.current + delimiter + piece;
499
+ const insideQuotes = countUnescaped(state.current, quote) % 2 === 1;
500
+ if (!insideQuotes) {
501
+ flushField(state, quote, trim, lenient);
502
+ }
503
+ };
504
+ var flushField = (state, quote, trim, lenient) => {
505
+ let fieldValue = stripOuterQuotes(state.current, quote, { lenient });
506
+ if (trim) fieldValue = fieldValue.trim();
507
+ state.fields.push(fieldValue);
508
+ state.current = "";
509
+ };
473
510
 
474
511
  // src/presets/csv-divider.ts
475
512
  function csvDivider(line, options = {}) {
@@ -504,33 +541,36 @@ function emailDivider(input, options = {}) {
504
541
  function pathDivider(input, options = {}) {
505
542
  const { trim = false, collapse = true } = options;
506
543
  if (isEmptyString(input)) return [""];
507
- const segments = collapse ? divider(input, PATH_SEPARATORS.SLASH, PATH_SEPARATORS.ALT) : dividePreserve(input, PATH_SEPARATORS.ALT).flatMap(
508
- (part) => dividePreserve(part, PATH_SEPARATORS.SLASH)
509
- );
510
- const maybeTrimmed = trim ? segments.map((segment) => segment.trim()) : segments;
511
- return collapse ? maybeTrimmed.filter((segment) => !isEmptyString(segment)) : maybeTrimmed;
512
- }
544
+ const segments = buildPathSegments(input, collapse);
545
+ const maybeTrimmed = trimSegments2(segments, trim);
546
+ return applyCollapseRules(maybeTrimmed, collapse);
547
+ }
548
+ var buildPathSegments = (input, collapse) => collapse ? divider(input, PATH_SEPARATORS.SLASH, PATH_SEPARATORS.ALT) : dividePreserve(input, PATH_SEPARATORS.ALT).flatMap(
549
+ (part) => dividePreserve(part, PATH_SEPARATORS.SLASH)
550
+ );
551
+ var trimSegments2 = (segments, trim) => trim ? segments.map((segment) => segment.trim()) : segments;
552
+ var applyCollapseRules = (segments, collapse) => collapse ? segments.filter((segment) => !isEmptyString(segment)) : segments;
513
553
 
514
554
  // src/presets/query-divider.ts
515
555
  function tryExtractQuery(input) {
516
556
  try {
517
557
  const url = new URL(input);
518
- return url.search.startsWith("?") ? url.search.slice(1) : url.search;
558
+ return url.search.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? url.search.slice(1) : url.search;
519
559
  } catch {
520
560
  return input;
521
561
  }
522
562
  }
523
563
  function stripLeadingQuestionMark(query) {
524
- return query.startsWith("?") ? query.slice(1) : query;
564
+ return query.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? query.slice(1) : query;
525
565
  }
526
566
  function splitOnFirstEquals(part) {
527
- const kv = dividePreserve(part, "=");
567
+ const kv = dividePreserve(part, QUERY_SEPARATORS.EQUALS);
528
568
  if (kv.length === 1) return [kv[0] ?? "", ""];
529
- return [kv[0] ?? "", kv.slice(1).join("=")];
569
+ return [kv[0] ?? "", kv.slice(1).join(QUERY_SEPARATORS.EQUALS)];
530
570
  }
531
571
  function decodeField(text, mode, trim) {
532
572
  let t = text;
533
- if (mode === "auto") {
573
+ if (mode === QUERY_DECODE_MODES.AUTO) {
534
574
  t = t.replace(/\+/g, " ");
535
575
  try {
536
576
  t = decodeURIComponent(t);
@@ -540,11 +580,11 @@ function decodeField(text, mode, trim) {
540
580
  if (trim) t = t.trim();
541
581
  return t;
542
582
  }
543
- function queryDivider(input, { mode = "auto", trim = false } = {}) {
583
+ function queryDivider(input, { mode = QUERY_DECODE_MODES.AUTO, trim = false } = {}) {
544
584
  if (input.length === 0) return [];
545
585
  const query = stripLeadingQuestionMark(tryExtractQuery(input));
546
586
  if (query.length === 0) return [];
547
- return dividePreserve(query, "&").map((part) => {
587
+ return dividePreserve(query, QUERY_SEPARATORS.AMPERSAND).map((part) => {
548
588
  const [key, value] = splitOnFirstEquals(part);
549
589
  return [decodeField(key, mode, trim), decodeField(value, mode, trim)];
550
590
  });
package/dist/index.d.cts CHANGED
@@ -13,6 +13,13 @@ declare const DIVIDER_EXCLUDE_MODES: {
13
13
  readonly EMPTY: "empty";
14
14
  readonly WHITESPACE: "whitespace";
15
15
  };
16
+ /**
17
+ * Query string decode modes.
18
+ */
19
+ declare const QUERY_DECODE_MODES: {
20
+ readonly AUTO: "auto";
21
+ readonly RAW: "raw";
22
+ };
16
23
 
17
24
  type DividerExcludeMode = (typeof DIVIDER_EXCLUDE_MODES)[keyof typeof DIVIDER_EXCLUDE_MODES];
18
25
  type StringInput = string;
@@ -49,7 +56,7 @@ type HasFlattenOption<TOptions extends DividerInferredOptions> = TOptions extend
49
56
  readonly flatten?: infer Flag;
50
57
  } ? Flag extends true ? true : false : false;
51
58
  type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : HasFlattenOption<TOptions> extends true ? DividerStringResult : DividerArrayResult;
52
- type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer _Rest, infer Last] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions;
59
+ type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer Rest, infer Last] ? Rest extends DividerArg[] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions : DividerEmptyOptions;
53
60
  type DividerLoopOptions = DividerOptions & {
54
61
  /** Starting position for the division (0-based) */
55
62
  startOffset?: number;
@@ -158,7 +165,7 @@ type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
158
165
  /** Collapse empty segments produced by leading/trailing or repeated separators. */
159
166
  collapse?: boolean;
160
167
  };
161
- type QueryDecodeMode = 'auto' | 'raw';
168
+ type QueryDecodeMode = (typeof QUERY_DECODE_MODES)[keyof typeof QUERY_DECODE_MODES];
162
169
  type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
163
170
  /** Decoding mode: 'auto' applies standard URL decoding; 'raw' leaves values untouched. */
164
171
  mode?: QueryDecodeMode;
package/dist/index.d.ts CHANGED
@@ -13,6 +13,13 @@ declare const DIVIDER_EXCLUDE_MODES: {
13
13
  readonly EMPTY: "empty";
14
14
  readonly WHITESPACE: "whitespace";
15
15
  };
16
+ /**
17
+ * Query string decode modes.
18
+ */
19
+ declare const QUERY_DECODE_MODES: {
20
+ readonly AUTO: "auto";
21
+ readonly RAW: "raw";
22
+ };
16
23
 
17
24
  type DividerExcludeMode = (typeof DIVIDER_EXCLUDE_MODES)[keyof typeof DIVIDER_EXCLUDE_MODES];
18
25
  type StringInput = string;
@@ -49,7 +56,7 @@ type HasFlattenOption<TOptions extends DividerInferredOptions> = TOptions extend
49
56
  readonly flatten?: infer Flag;
50
57
  } ? Flag extends true ? true : false : false;
51
58
  type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : HasFlattenOption<TOptions> extends true ? DividerStringResult : DividerArrayResult;
52
- type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer _Rest, infer Last] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions;
59
+ type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer Rest, infer Last] ? Rest extends DividerArg[] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions : DividerEmptyOptions;
53
60
  type DividerLoopOptions = DividerOptions & {
54
61
  /** Starting position for the division (0-based) */
55
62
  startOffset?: number;
@@ -158,7 +165,7 @@ type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
158
165
  /** Collapse empty segments produced by leading/trailing or repeated separators. */
159
166
  collapse?: boolean;
160
167
  };
161
- type QueryDecodeMode = 'auto' | 'raw';
168
+ type QueryDecodeMode = (typeof QUERY_DECODE_MODES)[keyof typeof QUERY_DECODE_MODES];
162
169
  type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
163
170
  /** Decoding mode: 'auto' applies standard URL decoding; 'raw' leaves values untouched. */
164
171
  mode?: QueryDecodeMode;
package/dist/index.js CHANGED
@@ -27,6 +27,15 @@ var PATH_SEPARATORS = {
27
27
  SLASH: "/",
28
28
  ALT: "|"
29
29
  };
30
+ var QUERY_SEPARATORS = {
31
+ QUESTION_MARK: "?",
32
+ AMPERSAND: "&",
33
+ EQUALS: "="
34
+ };
35
+ var QUERY_DECODE_MODES = {
36
+ AUTO: "auto",
37
+ RAW: "raw"
38
+ };
30
39
 
31
40
  // src/utils/is.ts
32
41
  function isString(value) {
@@ -190,12 +199,10 @@ function sortAscending(numbers) {
190
199
 
191
200
  // src/utils/parser.ts
192
201
  function divideString(input, numSeparators, strSeparators, options) {
193
- if (isEmptyArray(numSeparators) && isEmptyArray(strSeparators)) {
202
+ if (hasNoSeparators(numSeparators, strSeparators)) {
194
203
  return [input];
195
204
  }
196
- if (!Array.isArray(numSeparators) || !numSeparators.every(isNumber)) {
197
- throw new Error("Invalid numeric separators");
198
- }
205
+ assertValidNumSeparators(numSeparators);
199
206
  const regex = getRegex(strSeparators);
200
207
  const shouldPreserveEmpty = options?.preserveEmpty === true;
201
208
  const sortedNumSeparators = sortAscending(numSeparators);
@@ -203,6 +210,12 @@ function divideString(input, numSeparators, strSeparators, options) {
203
210
  const segments = regex ? parts.flatMap((part) => part.split(regex)) : parts;
204
211
  return shouldPreserveEmpty ? segments : segments.filter((segment) => !isEmptyString(segment));
205
212
  }
213
+ var hasNoSeparators = (numSeparators, strSeparators) => isEmptyArray(numSeparators) && isEmptyArray(strSeparators);
214
+ var assertValidNumSeparators = (numSeparators) => {
215
+ if (!Array.isArray(numSeparators) || !numSeparators.every(isNumber)) {
216
+ throw new Error("Invalid numeric separators");
217
+ }
218
+ };
206
219
 
207
220
  // src/utils/array.ts
208
221
  function ensureStringArray(input) {
@@ -241,29 +254,32 @@ function trimNestedSegments(rows, preserveEmpty) {
241
254
  return rows.map((row) => trimSegments(row, preserveEmpty));
242
255
  }
243
256
  function applyDividerOptions(result, options) {
244
- let output = result;
245
257
  const shouldPreserveEmpty = options.preserveEmpty === true;
246
- if (options.trim) {
247
- output = isNestedStringArray(output) ? trimNestedSegments(output, shouldPreserveEmpty) : trimSegments(output, shouldPreserveEmpty);
248
- }
249
- if (options.flatten) {
250
- output = output.flat();
251
- }
252
- if (!isNoneMode(options.exclude)) {
253
- const exclude = options.exclude ?? DIVIDER_EXCLUDE_MODES.NONE;
254
- let shouldKeep = () => true;
255
- if (exclude in excludePredicateMap) {
256
- shouldKeep = excludePredicateMap[exclude];
257
- }
258
- const filterNested = (arr) => {
259
- const filteredRows = arr.map((row) => row.filter(shouldKeep));
260
- return shouldPreserveEmpty ? filteredRows : filteredRows.filter((row) => row.length > 0);
261
- };
262
- const filterFlat = (arr) => arr.filter(shouldKeep);
263
- output = isNestedStringArray(output) ? filterNested(output) : filterFlat(output);
264
- }
258
+ let output = result;
259
+ output = applyTrimOption(output, options, shouldPreserveEmpty);
260
+ output = applyFlattenOption(output, options);
261
+ output = applyExcludeOption(output, options, shouldPreserveEmpty);
265
262
  return output;
266
263
  }
264
+ var applyTrimOption = (output, options, shouldPreserveEmpty) => {
265
+ if (!options.trim) return output;
266
+ return isNestedStringArray(output) ? trimNestedSegments(output, shouldPreserveEmpty) : trimSegments(output, shouldPreserveEmpty);
267
+ };
268
+ var applyFlattenOption = (output, options) => options.flatten ? output.flat() : output;
269
+ var applyExcludeOption = (output, options, shouldPreserveEmpty) => {
270
+ if (isNoneMode(options.exclude)) return output;
271
+ const exclude = options.exclude ?? DIVIDER_EXCLUDE_MODES.NONE;
272
+ let shouldKeep = () => true;
273
+ if (exclude in excludePredicateMap) {
274
+ shouldKeep = excludePredicateMap[exclude];
275
+ }
276
+ const filterNested = (arr) => {
277
+ const filteredRows = arr.map((row) => row.filter(shouldKeep));
278
+ return shouldPreserveEmpty ? filteredRows : filteredRows.filter((row) => row.length > 0);
279
+ };
280
+ const filterFlat = (arr) => arr.filter(shouldKeep);
281
+ return isNestedStringArray(output) ? filterNested(output) : filterFlat(output);
282
+ };
267
283
 
268
284
  // src/utils/separator.ts
269
285
  function classifySeparators(args) {
@@ -388,28 +404,8 @@ function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
388
404
  const escapedPair = quoteChar + quoteChar;
389
405
  const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
390
406
  const restoreEscapedQuotes = (fieldText) => fieldText.split(escapedPair).join(quoteChar);
391
- let left = 0;
392
- let right = text.length - 1;
393
- while (left <= right && isWhitespace(text[left])) left++;
394
- while (right >= left && isWhitespace(text[right])) right--;
395
- if (left > right) return restoreEscapedQuotes(text);
396
- const startsWithQuote = text[left] === quoteChar;
397
- if (!startsWithQuote) return restoreEscapedQuotes(text);
398
- const endsWithQuote = text[right] === quoteChar;
399
- if (endsWithQuote && right > left) {
400
- const withoutPair = text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
401
- return restoreEscapedQuotes(withoutPair);
402
- }
403
- if (!lenient) return restoreEscapedQuotes(text);
404
- let result = text.slice(0, left) + text.slice(left + 1);
405
- let lastNonSpaceIndexAfterTrim = result.length - 1;
406
- while (lastNonSpaceIndexAfterTrim >= 0 && isWhitespace(result[lastNonSpaceIndexAfterTrim])) {
407
- lastNonSpaceIndexAfterTrim--;
408
- }
409
- if (lastNonSpaceIndexAfterTrim >= 0 && result[lastNonSpaceIndexAfterTrim] === quoteChar) {
410
- result = result.slice(0, lastNonSpaceIndexAfterTrim) + result.slice(lastNonSpaceIndexAfterTrim + 1);
411
- }
412
- return restoreEscapedQuotes(result);
407
+ const stripped = stripOuterQuotesRaw(text, quoteChar, lenient, isWhitespace);
408
+ return restoreEscapedQuotes(stripped);
413
409
  }
414
410
  function quotedDivide(line, {
415
411
  delimiter = ",",
@@ -418,24 +414,65 @@ function quotedDivide(line, {
418
414
  lenient = true
419
415
  } = {}) {
420
416
  if (isEmptyString(line)) return [""];
417
+ return buildQuotedFields(line, delimiter, quote, trim, lenient);
418
+ }
419
+ var findNonSpaceBounds = (text, isWhitespace) => {
420
+ let left = 0;
421
+ let right = text.length - 1;
422
+ while (left <= right && isWhitespace(text[left])) left++;
423
+ while (right >= left && isWhitespace(text[right])) right--;
424
+ return { left, right };
425
+ };
426
+ var stripMatchedOuterQuotes = (text, left, right) => text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
427
+ var removeCharAt = (text, index) => text.slice(0, index) + text.slice(index + 1);
428
+ var stripTrailingQuote = (text, quoteChar, isWhitespace) => {
429
+ let lastNonSpaceIndex = text.length - 1;
430
+ while (lastNonSpaceIndex >= 0 && isWhitespace(text[lastNonSpaceIndex])) {
431
+ lastNonSpaceIndex--;
432
+ }
433
+ if (lastNonSpaceIndex < 0 || text[lastNonSpaceIndex] !== quoteChar) {
434
+ return text;
435
+ }
436
+ return removeCharAt(text, lastNonSpaceIndex);
437
+ };
438
+ var stripOuterQuotesRaw = (text, quoteChar, lenient, isWhitespace) => {
439
+ const { left, right } = findNonSpaceBounds(text, isWhitespace);
440
+ if (left > right) return text;
441
+ if (text[left] !== quoteChar) return text;
442
+ if (text[right] === quoteChar && right > left) {
443
+ return stripMatchedOuterQuotes(text, left, right);
444
+ }
445
+ if (!lenient) return text;
446
+ const withoutLeading = removeCharAt(text, left);
447
+ return stripTrailingQuote(withoutLeading, quoteChar, isWhitespace);
448
+ };
449
+ var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
421
450
  const pieces = dividePreserve(line, delimiter);
422
- const fields = [];
423
- let currentFieldBuffer = "";
424
- let insideQuotes = false;
425
- const flush = () => {
426
- let fieldValue = stripOuterQuotes(currentFieldBuffer, quote, { lenient });
427
- if (trim) fieldValue = fieldValue.trim();
428
- fields.push(fieldValue);
429
- currentFieldBuffer = "";
451
+ const state = {
452
+ fields: [],
453
+ current: ""
430
454
  };
431
455
  for (const piece of pieces) {
432
- currentFieldBuffer = isEmptyString(currentFieldBuffer) ? piece : currentFieldBuffer + delimiter + piece;
433
- insideQuotes = countUnescaped(currentFieldBuffer, quote) % 2 === 1;
434
- if (!insideQuotes) flush();
456
+ appendPiece(state, piece, delimiter, quote, trim, lenient);
435
457
  }
436
- if (!isEmptyString(currentFieldBuffer)) flush();
437
- return fields;
438
- }
458
+ if (!isEmptyString(state.current)) {
459
+ flushField(state, quote, trim, lenient);
460
+ }
461
+ return state.fields;
462
+ };
463
+ var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
464
+ state.current = isEmptyString(state.current) ? piece : state.current + delimiter + piece;
465
+ const insideQuotes = countUnescaped(state.current, quote) % 2 === 1;
466
+ if (!insideQuotes) {
467
+ flushField(state, quote, trim, lenient);
468
+ }
469
+ };
470
+ var flushField = (state, quote, trim, lenient) => {
471
+ let fieldValue = stripOuterQuotes(state.current, quote, { lenient });
472
+ if (trim) fieldValue = fieldValue.trim();
473
+ state.fields.push(fieldValue);
474
+ state.current = "";
475
+ };
439
476
 
440
477
  // src/presets/csv-divider.ts
441
478
  function csvDivider(line, options = {}) {
@@ -470,33 +507,36 @@ function emailDivider(input, options = {}) {
470
507
  function pathDivider(input, options = {}) {
471
508
  const { trim = false, collapse = true } = options;
472
509
  if (isEmptyString(input)) return [""];
473
- const segments = collapse ? divider(input, PATH_SEPARATORS.SLASH, PATH_SEPARATORS.ALT) : dividePreserve(input, PATH_SEPARATORS.ALT).flatMap(
474
- (part) => dividePreserve(part, PATH_SEPARATORS.SLASH)
475
- );
476
- const maybeTrimmed = trim ? segments.map((segment) => segment.trim()) : segments;
477
- return collapse ? maybeTrimmed.filter((segment) => !isEmptyString(segment)) : maybeTrimmed;
478
- }
510
+ const segments = buildPathSegments(input, collapse);
511
+ const maybeTrimmed = trimSegments2(segments, trim);
512
+ return applyCollapseRules(maybeTrimmed, collapse);
513
+ }
514
+ var buildPathSegments = (input, collapse) => collapse ? divider(input, PATH_SEPARATORS.SLASH, PATH_SEPARATORS.ALT) : dividePreserve(input, PATH_SEPARATORS.ALT).flatMap(
515
+ (part) => dividePreserve(part, PATH_SEPARATORS.SLASH)
516
+ );
517
+ var trimSegments2 = (segments, trim) => trim ? segments.map((segment) => segment.trim()) : segments;
518
+ var applyCollapseRules = (segments, collapse) => collapse ? segments.filter((segment) => !isEmptyString(segment)) : segments;
479
519
 
480
520
  // src/presets/query-divider.ts
481
521
  function tryExtractQuery(input) {
482
522
  try {
483
523
  const url = new URL(input);
484
- return url.search.startsWith("?") ? url.search.slice(1) : url.search;
524
+ return url.search.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? url.search.slice(1) : url.search;
485
525
  } catch {
486
526
  return input;
487
527
  }
488
528
  }
489
529
  function stripLeadingQuestionMark(query) {
490
- return query.startsWith("?") ? query.slice(1) : query;
530
+ return query.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? query.slice(1) : query;
491
531
  }
492
532
  function splitOnFirstEquals(part) {
493
- const kv = dividePreserve(part, "=");
533
+ const kv = dividePreserve(part, QUERY_SEPARATORS.EQUALS);
494
534
  if (kv.length === 1) return [kv[0] ?? "", ""];
495
- return [kv[0] ?? "", kv.slice(1).join("=")];
535
+ return [kv[0] ?? "", kv.slice(1).join(QUERY_SEPARATORS.EQUALS)];
496
536
  }
497
537
  function decodeField(text, mode, trim) {
498
538
  let t = text;
499
- if (mode === "auto") {
539
+ if (mode === QUERY_DECODE_MODES.AUTO) {
500
540
  t = t.replace(/\+/g, " ");
501
541
  try {
502
542
  t = decodeURIComponent(t);
@@ -506,11 +546,11 @@ function decodeField(text, mode, trim) {
506
546
  if (trim) t = t.trim();
507
547
  return t;
508
548
  }
509
- function queryDivider(input, { mode = "auto", trim = false } = {}) {
549
+ function queryDivider(input, { mode = QUERY_DECODE_MODES.AUTO, trim = false } = {}) {
510
550
  if (input.length === 0) return [];
511
551
  const query = stripLeadingQuestionMark(tryExtractQuery(input));
512
552
  if (query.length === 0) return [];
513
- return dividePreserve(query, "&").map((part) => {
553
+ return dividePreserve(query, QUERY_SEPARATORS.AMPERSAND).map((part) => {
514
554
  const [key, value] = splitOnFirstEquals(part);
515
555
  return [decodeField(key, mode, trim), decodeField(value, mode, trim)];
516
556
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nyaomaru/divider",
3
3
  "type": "module",
4
- "version": "1.9.19",
4
+ "version": "1.9.21",
5
5
  "description": "To divide string or string[] with a given separator",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -38,11 +38,14 @@
38
38
  "@eslint/js": "^9.26.0",
39
39
  "@types/jest": "^30.0.0",
40
40
  "@types/node": "^22.15.12",
41
+ "@typescript-eslint/eslint-plugin": "^8.32.1",
42
+ "@typescript-eslint/parser": "^8.32.1",
41
43
  "bun-types": "^1.2.21",
42
44
  "eslint": "^9.39.1",
43
45
  "eslint-config-prettier": "^10.1.8",
44
46
  "eslint-plugin-prettier": "^5.5.4",
45
47
  "jest": "^30.2.0",
48
+ "lefthook": "^2.0.13",
46
49
  "prettier": "^3.5.3",
47
50
  "ts-jest": "^29.4.5",
48
51
  "ts-node": "^10.9.2",