@nyaomaru/divider 1.8.18 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -158,11 +158,11 @@ const result5 = dividerLoop('abcdefghij', 3, { maxChunks: 2 });
158
158
  ```ts
159
159
  import { dividerNumberString } from '@nyaomaru/divider';
160
160
 
161
- // Split numbers and letters from a string
161
+ // Divide numbers and letters from a string
162
162
  const result = dividerNumberString('abc123def456');
163
163
  // ['abc', '123', 'def', '456']
164
164
 
165
- // Split each string in a string[]
165
+ // Divide each string in a string[]
166
166
  const result2 = dividerNumberString(['abc123', '45z']);
167
167
  // [['abc', '123'], ['45', 'z']]
168
168
 
@@ -171,6 +171,18 @@ const result3 = dividerNumberString(['abc123', '45z'], { flatten: true });
171
171
  // ['abc', '123', '45', 'z']
172
172
  ```
173
173
 
174
+ ### 📌 Presets
175
+
176
+ Some common use cases are wrapped as presets for convenience.
177
+
178
+ | Preset name | Description |
179
+ | -------------- | --------------------------------------------------------- |
180
+ | `emailDivider` | Divide email into [local-part, domain] (by '@') |
181
+ | `csvDivider` | Divide comma-separated strings, with quoted field support |
182
+ | `pathDivider` | Divide file paths by / or \| |
183
+
184
+ [Presets detail](src/presets/README.md)
185
+
174
186
  ## 🎯 General Options
175
187
 
176
188
  | Option | Type | Default | Description |
package/dist/index.cjs CHANGED
@@ -20,11 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ csvDivider: () => csvDivider,
23
24
  divider: () => divider,
24
25
  dividerFirst: () => dividerFirst,
25
26
  dividerLast: () => dividerLast,
26
27
  dividerLoop: () => dividerLoop,
27
- dividerNumberString: () => dividerNumberString
28
+ dividerNumberString: () => dividerNumberString,
29
+ emailDivider: () => emailDivider,
30
+ pathDivider: () => pathDivider
28
31
  });
29
32
  module.exports = __toCommonJS(index_exports);
30
33
 
@@ -46,6 +49,12 @@ var PERFORMANCE_CONSTANTS = {
46
49
  DEFAULT_MAX_CHUNKS: 0
47
50
  };
48
51
  var CACHE_KEY_SEPARATOR = "\0";
52
+ var WHITE_SPACE = " ";
53
+ var TAB = " ";
54
+ var PATH_SEPARATORS = {
55
+ SLASH: "/",
56
+ ALT: "|"
57
+ };
49
58
 
50
59
  // src/utils/is.ts
51
60
  function isString(arg) {
@@ -362,11 +371,121 @@ function dividerNumberString(input, options) {
362
371
  const result = isString(input) ? divideNumberString(input) : input.map(divideNumberString);
363
372
  return applyDividerOptions(result, options ?? {});
364
373
  }
374
+
375
+ // src/utils/quoted.ts
376
+ function dividePreserve(input, separator) {
377
+ if (input === "") return [""];
378
+ const divided = divider(input, separator);
379
+ return divided.join(separator) === input ? divided : input.split(separator);
380
+ }
381
+ function countUnescaped(text, quote) {
382
+ const pair = quote + quote;
383
+ let count = 0;
384
+ for (const chunk of dividePreserve(text, pair)) {
385
+ count += dividePreserve(chunk, quote).length - 1;
386
+ }
387
+ return count;
388
+ }
389
+ function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
390
+ const escapedPair = quoteChar + quoteChar;
391
+ const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
392
+ const restoreEscapedQuotes = (fieldText) => fieldText.split(escapedPair).join(quoteChar);
393
+ let left = 0;
394
+ let right = text.length - 1;
395
+ while (left <= right && isWhitespace(text[left])) left++;
396
+ while (right >= left && isWhitespace(text[right])) right--;
397
+ if (left > right) return restoreEscapedQuotes(text);
398
+ const startsWithQuote = text[left] === quoteChar;
399
+ if (!startsWithQuote) return restoreEscapedQuotes(text);
400
+ const endsWithQuote = text[right] === quoteChar;
401
+ if (endsWithQuote && right > left) {
402
+ const withoutPair = text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
403
+ return restoreEscapedQuotes(withoutPair);
404
+ }
405
+ if (!lenient) return restoreEscapedQuotes(text);
406
+ let result = text.slice(0, left) + text.slice(left + 1);
407
+ let lastNonSpaceIndexAfterTrim = result.length - 1;
408
+ while (lastNonSpaceIndexAfterTrim >= 0 && isWhitespace(result[lastNonSpaceIndexAfterTrim])) {
409
+ lastNonSpaceIndexAfterTrim--;
410
+ }
411
+ if (lastNonSpaceIndexAfterTrim >= 0 && result[lastNonSpaceIndexAfterTrim] === quoteChar) {
412
+ result = result.slice(0, lastNonSpaceIndexAfterTrim) + result.slice(lastNonSpaceIndexAfterTrim + 1);
413
+ }
414
+ return restoreEscapedQuotes(result);
415
+ }
416
+ function quotedDivide(line, {
417
+ delimiter = ",",
418
+ quote = '"',
419
+ trim = false,
420
+ lenient = true
421
+ } = {}) {
422
+ if (line === "") return [""];
423
+ const pieces = dividePreserve(line, delimiter);
424
+ const fields = [];
425
+ let currentFieldBuffer = "";
426
+ let insideQuotes = false;
427
+ const flush = () => {
428
+ let fieldValue = stripOuterQuotes(currentFieldBuffer, quote, { lenient });
429
+ if (trim) fieldValue = fieldValue.trim();
430
+ fields.push(fieldValue);
431
+ currentFieldBuffer = "";
432
+ };
433
+ for (const piece of pieces) {
434
+ currentFieldBuffer = currentFieldBuffer === "" ? piece : currentFieldBuffer + delimiter + piece;
435
+ insideQuotes = countUnescaped(currentFieldBuffer, quote) % 2 === 1;
436
+ if (!insideQuotes) flush();
437
+ }
438
+ if (currentFieldBuffer !== "") flush();
439
+ return fields;
440
+ }
441
+
442
+ // src/presets/csv-divider.ts
443
+ function csvDivider(line, options = {}) {
444
+ const { delimiter = ",", quoteChar = '"', trim = false } = options;
445
+ return quotedDivide(line, {
446
+ delimiter,
447
+ quote: quoteChar,
448
+ trim,
449
+ lenient: true
450
+ });
451
+ }
452
+
453
+ // src/presets/email-divider.ts
454
+ var MAX_EMAIL_PARTS = 2;
455
+ function emailDivider(input, options = {}) {
456
+ const { splitTLD, ...dividerOptions } = options;
457
+ const result = divider(input, "@", dividerOptions);
458
+ if (result.length > MAX_EMAIL_PARTS) {
459
+ console.warn(
460
+ `[divider/emailDivider] Too many "@" symbols in "${input}". Expected at most one.`
461
+ );
462
+ }
463
+ if (splitTLD && result.length === MAX_EMAIL_PARTS) {
464
+ const [local, domain] = result;
465
+ const domainParts = divider(domain, ".", dividerOptions);
466
+ return [local, ...domainParts];
467
+ }
468
+ return result;
469
+ }
470
+
471
+ // src/presets/path-divider.ts
472
+ function pathDivider(input, options = {}) {
473
+ const { trim = false, collapse = true } = options;
474
+ if (input === "") return [""];
475
+ const segments = collapse ? divider(input, PATH_SEPARATORS.SLASH, PATH_SEPARATORS.ALT) : dividePreserve(input, PATH_SEPARATORS.ALT).flatMap(
476
+ (part) => dividePreserve(part, PATH_SEPARATORS.SLASH)
477
+ );
478
+ const maybeTrimmed = trim ? segments.map((segment) => segment.trim()) : segments;
479
+ return collapse ? maybeTrimmed.filter((segment) => segment !== "") : maybeTrimmed;
480
+ }
365
481
  // Annotate the CommonJS export names for ESM import in node:
366
482
  0 && (module.exports = {
483
+ csvDivider,
367
484
  divider,
368
485
  dividerFirst,
369
486
  dividerLast,
370
487
  dividerLoop,
371
- dividerNumberString
488
+ dividerNumberString,
489
+ emailDivider,
490
+ pathDivider
372
491
  });
package/dist/index.d.cts CHANGED
@@ -21,20 +21,20 @@ type DividerInput = StringInput | StringArrayInput;
21
21
  type DividerStringResult = string[];
22
22
  type DividerArrayResult = string[][];
23
23
  type DividerResult<T extends DividerInput> = T extends StringInput ? DividerStringResult : DividerArrayResult;
24
- interface DividerOptions {
24
+ type DividerOptions = {
25
25
  /** If true, flattens nested arrays into a single array */
26
26
  flatten?: boolean;
27
27
  /** If true, trims whitespace from each divided segment */
28
28
  trim?: boolean;
29
29
  /** Controls how empty or whitespace segments are handled */
30
30
  exclude?: DividerExcludeMode;
31
- }
32
- interface DividerLoopOptions extends DividerOptions {
31
+ };
32
+ type DividerLoopOptions = DividerOptions & {
33
33
  /** Starting position for the division (0-based) */
34
34
  startOffset?: number;
35
35
  /** Maximum number of chunks to produce */
36
36
  maxChunks?: number;
37
- }
37
+ };
38
38
  type NumericSeparator = number;
39
39
  type StringSeparator = string;
40
40
  type DividerSeparator = NumericSeparator | StringSeparator;
@@ -115,4 +115,50 @@ declare function dividerLoop<T extends string | string[]>(input: T, size: number
115
115
  */
116
116
  declare function dividerNumberString<T extends string | string[]>(input: T, options?: DividerOptions): DividerResult<T>;
117
117
 
118
- export { type DividerArgs, type DividerArrayResult, type DividerExcludeMode, type DividerInput, type DividerLoopOptions, type DividerOptions, type DividerResult, type DividerSeparator, type DividerSeparators, type DividerStringResult, type NumericSeparator, type StringArrayInput, type StringInput, type StringSeparator, divider, dividerFirst, dividerLast, dividerLoop, dividerNumberString };
118
+ type EmailDividerOptions = Pick<DividerOptions, 'trim'> & {
119
+ /** Split top-level domain from the rest of the email address. */
120
+ splitTLD?: boolean;
121
+ };
122
+ type CsvDividerOptions = Pick<DividerOptions, 'trim'> & {
123
+ /** Character used for quoting values. */
124
+ quoteChar?: string;
125
+ /** Character used to separate CSV fields. */
126
+ delimiter?: string;
127
+ };
128
+ type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
129
+ /** Collapse empty segments produced by leading/trailing or repeated separators. */
130
+ collapse?: boolean;
131
+ };
132
+
133
+ /**
134
+ * Divides a CSV line into an array of fields, handling quoted values appropriately.
135
+ *
136
+ * @param line - The CSV line string to be divided into fields
137
+ * @param options - Configuration options for CSV parsing
138
+ * @param options.delimiter - The character used to separate fields (default: ',')
139
+ * @param options.quoteChar - The character used to quote fields containing delimiters or newlines (default: '"')
140
+ * @param options.trim - Whether to trim whitespace from field values (default: false)
141
+ * @returns A DividerStringResult containing the parsed CSV fields
142
+ */
143
+ declare function csvDivider(line: string, options?: CsvDividerOptions): DividerStringResult;
144
+
145
+ /**
146
+ * Divides an email address string at the "@" symbol into its local and domain parts.
147
+ *
148
+ * @param input - The email address string to divide
149
+ * @param options - Optional configuration for the divider operation
150
+ * @returns A DividerStringResult containing the divided parts of the email address
151
+ */
152
+ declare function emailDivider(input: string, options?: EmailDividerOptions): DividerStringResult;
153
+
154
+ /**
155
+ * Divides a path string into segments using path separators.
156
+ * @param input - The path string to divide
157
+ * @param options - Configuration options for path division
158
+ * @param options.trim - Whether to trim whitespace from segments
159
+ * @param options.collapse - Whether to collapse consecutive separators and filter empty segments
160
+ * @returns Array of path segments
161
+ */
162
+ declare function pathDivider(input: string, options?: PathDividerOptions): DividerStringResult;
163
+
164
+ export { type DividerArgs, type DividerArrayResult, type DividerExcludeMode, type DividerInput, type DividerLoopOptions, type DividerOptions, type DividerResult, type DividerSeparator, type DividerSeparators, type DividerStringResult, type NumericSeparator, type StringArrayInput, type StringInput, type StringSeparator, csvDivider, divider, dividerFirst, dividerLast, dividerLoop, dividerNumberString, emailDivider, pathDivider };
package/dist/index.d.ts CHANGED
@@ -21,20 +21,20 @@ type DividerInput = StringInput | StringArrayInput;
21
21
  type DividerStringResult = string[];
22
22
  type DividerArrayResult = string[][];
23
23
  type DividerResult<T extends DividerInput> = T extends StringInput ? DividerStringResult : DividerArrayResult;
24
- interface DividerOptions {
24
+ type DividerOptions = {
25
25
  /** If true, flattens nested arrays into a single array */
26
26
  flatten?: boolean;
27
27
  /** If true, trims whitespace from each divided segment */
28
28
  trim?: boolean;
29
29
  /** Controls how empty or whitespace segments are handled */
30
30
  exclude?: DividerExcludeMode;
31
- }
32
- interface DividerLoopOptions extends DividerOptions {
31
+ };
32
+ type DividerLoopOptions = DividerOptions & {
33
33
  /** Starting position for the division (0-based) */
34
34
  startOffset?: number;
35
35
  /** Maximum number of chunks to produce */
36
36
  maxChunks?: number;
37
- }
37
+ };
38
38
  type NumericSeparator = number;
39
39
  type StringSeparator = string;
40
40
  type DividerSeparator = NumericSeparator | StringSeparator;
@@ -115,4 +115,50 @@ declare function dividerLoop<T extends string | string[]>(input: T, size: number
115
115
  */
116
116
  declare function dividerNumberString<T extends string | string[]>(input: T, options?: DividerOptions): DividerResult<T>;
117
117
 
118
- export { type DividerArgs, type DividerArrayResult, type DividerExcludeMode, type DividerInput, type DividerLoopOptions, type DividerOptions, type DividerResult, type DividerSeparator, type DividerSeparators, type DividerStringResult, type NumericSeparator, type StringArrayInput, type StringInput, type StringSeparator, divider, dividerFirst, dividerLast, dividerLoop, dividerNumberString };
118
+ type EmailDividerOptions = Pick<DividerOptions, 'trim'> & {
119
+ /** Split top-level domain from the rest of the email address. */
120
+ splitTLD?: boolean;
121
+ };
122
+ type CsvDividerOptions = Pick<DividerOptions, 'trim'> & {
123
+ /** Character used for quoting values. */
124
+ quoteChar?: string;
125
+ /** Character used to separate CSV fields. */
126
+ delimiter?: string;
127
+ };
128
+ type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
129
+ /** Collapse empty segments produced by leading/trailing or repeated separators. */
130
+ collapse?: boolean;
131
+ };
132
+
133
+ /**
134
+ * Divides a CSV line into an array of fields, handling quoted values appropriately.
135
+ *
136
+ * @param line - The CSV line string to be divided into fields
137
+ * @param options - Configuration options for CSV parsing
138
+ * @param options.delimiter - The character used to separate fields (default: ',')
139
+ * @param options.quoteChar - The character used to quote fields containing delimiters or newlines (default: '"')
140
+ * @param options.trim - Whether to trim whitespace from field values (default: false)
141
+ * @returns A DividerStringResult containing the parsed CSV fields
142
+ */
143
+ declare function csvDivider(line: string, options?: CsvDividerOptions): DividerStringResult;
144
+
145
+ /**
146
+ * Divides an email address string at the "@" symbol into its local and domain parts.
147
+ *
148
+ * @param input - The email address string to divide
149
+ * @param options - Optional configuration for the divider operation
150
+ * @returns A DividerStringResult containing the divided parts of the email address
151
+ */
152
+ declare function emailDivider(input: string, options?: EmailDividerOptions): DividerStringResult;
153
+
154
+ /**
155
+ * Divides a path string into segments using path separators.
156
+ * @param input - The path string to divide
157
+ * @param options - Configuration options for path division
158
+ * @param options.trim - Whether to trim whitespace from segments
159
+ * @param options.collapse - Whether to collapse consecutive separators and filter empty segments
160
+ * @returns Array of path segments
161
+ */
162
+ declare function pathDivider(input: string, options?: PathDividerOptions): DividerStringResult;
163
+
164
+ export { type DividerArgs, type DividerArrayResult, type DividerExcludeMode, type DividerInput, type DividerLoopOptions, type DividerOptions, type DividerResult, type DividerSeparator, type DividerSeparators, type DividerStringResult, type NumericSeparator, type StringArrayInput, type StringInput, type StringSeparator, csvDivider, divider, dividerFirst, dividerLast, dividerLoop, dividerNumberString, emailDivider, pathDivider };
package/dist/index.js CHANGED
@@ -16,6 +16,12 @@ var PERFORMANCE_CONSTANTS = {
16
16
  DEFAULT_MAX_CHUNKS: 0
17
17
  };
18
18
  var CACHE_KEY_SEPARATOR = "\0";
19
+ var WHITE_SPACE = " ";
20
+ var TAB = " ";
21
+ var PATH_SEPARATORS = {
22
+ SLASH: "/",
23
+ ALT: "|"
24
+ };
19
25
 
20
26
  // src/utils/is.ts
21
27
  function isString(arg) {
@@ -332,10 +338,120 @@ function dividerNumberString(input, options) {
332
338
  const result = isString(input) ? divideNumberString(input) : input.map(divideNumberString);
333
339
  return applyDividerOptions(result, options ?? {});
334
340
  }
341
+
342
+ // src/utils/quoted.ts
343
+ function dividePreserve(input, separator) {
344
+ if (input === "") return [""];
345
+ const divided = divider(input, separator);
346
+ return divided.join(separator) === input ? divided : input.split(separator);
347
+ }
348
+ function countUnescaped(text, quote) {
349
+ const pair = quote + quote;
350
+ let count = 0;
351
+ for (const chunk of dividePreserve(text, pair)) {
352
+ count += dividePreserve(chunk, quote).length - 1;
353
+ }
354
+ return count;
355
+ }
356
+ function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
357
+ const escapedPair = quoteChar + quoteChar;
358
+ const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
359
+ const restoreEscapedQuotes = (fieldText) => fieldText.split(escapedPair).join(quoteChar);
360
+ let left = 0;
361
+ let right = text.length - 1;
362
+ while (left <= right && isWhitespace(text[left])) left++;
363
+ while (right >= left && isWhitespace(text[right])) right--;
364
+ if (left > right) return restoreEscapedQuotes(text);
365
+ const startsWithQuote = text[left] === quoteChar;
366
+ if (!startsWithQuote) return restoreEscapedQuotes(text);
367
+ const endsWithQuote = text[right] === quoteChar;
368
+ if (endsWithQuote && right > left) {
369
+ const withoutPair = text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
370
+ return restoreEscapedQuotes(withoutPair);
371
+ }
372
+ if (!lenient) return restoreEscapedQuotes(text);
373
+ let result = text.slice(0, left) + text.slice(left + 1);
374
+ let lastNonSpaceIndexAfterTrim = result.length - 1;
375
+ while (lastNonSpaceIndexAfterTrim >= 0 && isWhitespace(result[lastNonSpaceIndexAfterTrim])) {
376
+ lastNonSpaceIndexAfterTrim--;
377
+ }
378
+ if (lastNonSpaceIndexAfterTrim >= 0 && result[lastNonSpaceIndexAfterTrim] === quoteChar) {
379
+ result = result.slice(0, lastNonSpaceIndexAfterTrim) + result.slice(lastNonSpaceIndexAfterTrim + 1);
380
+ }
381
+ return restoreEscapedQuotes(result);
382
+ }
383
+ function quotedDivide(line, {
384
+ delimiter = ",",
385
+ quote = '"',
386
+ trim = false,
387
+ lenient = true
388
+ } = {}) {
389
+ if (line === "") return [""];
390
+ const pieces = dividePreserve(line, delimiter);
391
+ const fields = [];
392
+ let currentFieldBuffer = "";
393
+ let insideQuotes = false;
394
+ const flush = () => {
395
+ let fieldValue = stripOuterQuotes(currentFieldBuffer, quote, { lenient });
396
+ if (trim) fieldValue = fieldValue.trim();
397
+ fields.push(fieldValue);
398
+ currentFieldBuffer = "";
399
+ };
400
+ for (const piece of pieces) {
401
+ currentFieldBuffer = currentFieldBuffer === "" ? piece : currentFieldBuffer + delimiter + piece;
402
+ insideQuotes = countUnescaped(currentFieldBuffer, quote) % 2 === 1;
403
+ if (!insideQuotes) flush();
404
+ }
405
+ if (currentFieldBuffer !== "") flush();
406
+ return fields;
407
+ }
408
+
409
+ // src/presets/csv-divider.ts
410
+ function csvDivider(line, options = {}) {
411
+ const { delimiter = ",", quoteChar = '"', trim = false } = options;
412
+ return quotedDivide(line, {
413
+ delimiter,
414
+ quote: quoteChar,
415
+ trim,
416
+ lenient: true
417
+ });
418
+ }
419
+
420
+ // src/presets/email-divider.ts
421
+ var MAX_EMAIL_PARTS = 2;
422
+ function emailDivider(input, options = {}) {
423
+ const { splitTLD, ...dividerOptions } = options;
424
+ const result = divider(input, "@", dividerOptions);
425
+ if (result.length > MAX_EMAIL_PARTS) {
426
+ console.warn(
427
+ `[divider/emailDivider] Too many "@" symbols in "${input}". Expected at most one.`
428
+ );
429
+ }
430
+ if (splitTLD && result.length === MAX_EMAIL_PARTS) {
431
+ const [local, domain] = result;
432
+ const domainParts = divider(domain, ".", dividerOptions);
433
+ return [local, ...domainParts];
434
+ }
435
+ return result;
436
+ }
437
+
438
+ // src/presets/path-divider.ts
439
+ function pathDivider(input, options = {}) {
440
+ const { trim = false, collapse = true } = options;
441
+ if (input === "") return [""];
442
+ const segments = collapse ? divider(input, PATH_SEPARATORS.SLASH, PATH_SEPARATORS.ALT) : dividePreserve(input, PATH_SEPARATORS.ALT).flatMap(
443
+ (part) => dividePreserve(part, PATH_SEPARATORS.SLASH)
444
+ );
445
+ const maybeTrimmed = trim ? segments.map((segment) => segment.trim()) : segments;
446
+ return collapse ? maybeTrimmed.filter((segment) => segment !== "") : maybeTrimmed;
447
+ }
335
448
  export {
449
+ csvDivider,
336
450
  divider,
337
451
  dividerFirst,
338
452
  dividerLast,
339
453
  dividerLoop,
340
- dividerNumberString
454
+ dividerNumberString,
455
+ emailDivider,
456
+ pathDivider
341
457
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nyaomaru/divider",
3
3
  "type": "module",
4
- "version": "1.8.18",
4
+ "version": "1.9.0",
5
5
  "description": "To divide string or string[] with a given separator",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",