@seedcord/utils 0.6.1 → 0.7.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -175,7 +175,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
175
175
 
176
176
  END OF TERMS AND CONDITIONS
177
177
 
178
- Copyright 2025 Dhruv Jain (materwelonDhruv)
178
+ Copyright 2026 Dhruv Jain (materwelonDhruv)
179
179
 
180
180
  Licensed under the Apache License, Version 2.0 (the "License");
181
181
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  _This repository is a work in progress._
8
8
 
9
9
  - There are no stable releases yet but changes are being made actively.
10
- - Till a major v1.0.0 release for seedcord, expect breaking changes in minor versions.
10
+ - Until a major v1.0.0 release for seedcord, expect breaking changes in minor versions.
11
11
  - Documentation will come soon as well!
12
12
 
13
13
  If you'd like to try it out, you can check out the code in `mock`
package/dist/index.cjs CHANGED
@@ -35,6 +35,19 @@ node_path = __toESM(node_path, 1);
35
35
  * Exhaustiveness guard for discriminated unions. Place in the `default` branch of a `switch` over a
36
36
  * union's discriminant: if a new variant is added without a matching case, the call fails to compile.
37
37
  * Throws at runtime if reached with a value the types said was impossible.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * type Shape = { kind: 'circle' } | { kind: 'square' };
42
+ *
43
+ * function area(shape: Shape): number {
44
+ * switch (shape.kind) {
45
+ * case 'circle': return Math.PI;
46
+ * case 'square': return 1;
47
+ * default: return assertNever(shape); // compile error if a Shape variant is added
48
+ * }
49
+ * }
50
+ * ```
38
51
  */
39
52
  function assertNever(value) {
40
53
  throw new Error(`Unhandled discriminated union member: ${JSON.stringify(value)}`);
@@ -57,6 +70,17 @@ function isTsOrJsFile(entry) {
57
70
  * @param dir - The directory path to traverse.
58
71
  * @param callback - A function that will be called for each imported module. It receives the full file path, the file's relative path, and the imported module as arguments.
59
72
  * @returns A Promise that resolves when the traversal is complete.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * await traverseDirectory(
77
+ * './commands',
78
+ * (fullPath, relativePath, imported) => {
79
+ * for (const exported of Object.values(imported)) register(exported);
80
+ * },
81
+ * logger
82
+ * );
83
+ * ```
60
84
  */
61
85
  async function traverseDirectory(dir, callback, logger) {
62
86
  let entries;
@@ -78,6 +102,14 @@ async function traverseDirectory(dir, callback, logger) {
78
102
  * @param filePath - The file path to format.
79
103
  * @param options - Formatting options.
80
104
  * @returns The formatted file path.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * // cwd is /repo
109
+ * formatFilePath('/repo/src/Bot.ts'); // './src/Bot.ts'
110
+ * formatFilePath('/repo/src/Bot.ts', { onlyDir: true }); // './src'
111
+ * formatFilePath('/repo/src/Bot.ts', { prefix: '' }); // 'src/Bot.ts'
112
+ * ```
81
113
  */
82
114
  function formatFilePath(filePath, options = {}) {
83
115
  const { onlyDir = false, prefix = "./" } = options;
@@ -133,6 +165,11 @@ function currentTime() {
133
165
  *
134
166
  * @param digits - The number of digits for the generated code.
135
167
  * @returns A random numeric code with the specified number of digits.
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * generateCode(6); // e.g. 482915
172
+ * ```
136
173
  */
137
174
  function generateCode(digits) {
138
175
  const min = Math.pow(10, digits - 1);
@@ -186,6 +223,14 @@ const DURATION_PATTERN = new RegExp(`^(\\d+)(${Object.keys(UNIT_MS).join("|")})$
186
223
  *
187
224
  * @param input - The duration string to parse.
188
225
  * @returns The duration in milliseconds, or `null` if `input` is not a well-formed positive duration.
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * parseDuration('24h'); // 86400000
230
+ * parseDuration('30m'); // 1800000
231
+ * parseDuration('1.5h'); // null
232
+ * parseDuration('foo'); // null
233
+ * ```
189
234
  */
190
235
  function parseDuration(input) {
191
236
  const match = DURATION_PATTERN.exec(input);
@@ -203,6 +248,12 @@ function parseDuration(input) {
203
248
  * @param num2 - The second number.
204
249
  *
205
250
  * @returns The percentage of the first number in the second number with two decimal places.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * percentage(25, 200); // 12.5
255
+ * percentage(1, 3); // 33.33
256
+ * ```
206
257
  */
207
258
  function percentage(num1, num2) {
208
259
  return Number((num1 / num2 * 100).toFixed(2));
@@ -216,6 +267,12 @@ function percentage(num1, num2) {
216
267
  * @param num - The number to be rounded.
217
268
  * @param precision - The number of decimal places to round to.
218
269
  * @returns The rounded number.
270
+ *
271
+ * @example
272
+ * ```ts
273
+ * round(3.14159, 2); // 3.14
274
+ * round(2.5, 0); // 3
275
+ * ```
219
276
  */
220
277
  function round(num, precision) {
221
278
  const factor = Math.pow(10, precision);
@@ -459,6 +516,11 @@ function keepDefined(source, ...keys) {
459
516
  * Returns the word with its first letter capitalized and the rest in lowercase.
460
517
  * @param word - The word to be formatted.
461
518
  * @returns The formatted word.
519
+ *
520
+ * @example
521
+ * ```ts
522
+ * capitalize('hELLO'); // 'Hello'
523
+ * ```
462
524
  */
463
525
  function capitalize(word) {
464
526
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
@@ -471,55 +533,320 @@ function capitalize(word) {
471
533
  *
472
534
  * @param arr - The array of strings or numbers
473
535
  * @returns The length of the longest element when converted to string
536
+ *
537
+ * @example
538
+ * ```ts
539
+ * longestStringLength(['ab', 12345]); // 5
540
+ * ```
474
541
  */
475
542
  function longestStringLength(arr) {
476
543
  return Math.max(...arr.map((el) => el.toString().length));
477
544
  }
478
545
 
479
546
  //#endregion
480
- //#region src/strings/generateAsciiTable.ts
481
- /**
482
- * Generates an ASCII table from the provided data.
483
- *
484
- * @param data - The data to be displayed in the table.
485
- * @returns The generated ASCII table as a string.
486
- */
487
- function generateAsciiTable(data) {
488
- if (data.length === 0) return "";
489
- const firstRow = data[0];
490
- if (!firstRow || firstRow.length === 0) return "";
491
- let table = "";
492
- const columnWidths = [];
493
- for (let i = 0; i < firstRow.length; i++) {
494
- let maxWidth = 0;
495
- for (const row of data) {
496
- const cell = row[i];
497
- if (cell !== void 0) maxWidth = Math.max(maxWidth, cell.length);
547
+ //#region src/strings/renderTable/borders.ts
548
+ const DOUBLE = {
549
+ top: {
550
+ left: "╔",
551
+ mid: "╦",
552
+ right: "╗",
553
+ fill: "═"
554
+ },
555
+ bottom: {
556
+ left: "╚",
557
+ mid: "",
558
+ right: "",
559
+ fill: "═"
560
+ },
561
+ sep: {
562
+ left: "╟",
563
+ mid: "╫",
564
+ right: "╢",
565
+ fill: "─"
566
+ },
567
+ headerSep: {
568
+ left: "╠",
569
+ mid: "╬",
570
+ right: "╣",
571
+ fill: "═"
572
+ },
573
+ vertical: "║"
574
+ };
575
+ const ROUNDED = {
576
+ top: {
577
+ left: "╭",
578
+ mid: "┬",
579
+ right: "╮",
580
+ fill: "─"
581
+ },
582
+ bottom: {
583
+ left: "╰",
584
+ mid: "┴",
585
+ right: "╯",
586
+ fill: "─"
587
+ },
588
+ sep: {
589
+ left: "├",
590
+ mid: "┼",
591
+ right: "┤",
592
+ fill: "─"
593
+ },
594
+ headerSep: {
595
+ left: "├",
596
+ mid: "┼",
597
+ right: "┤",
598
+ fill: "─"
599
+ },
600
+ vertical: "│"
601
+ };
602
+ const ASCII = {
603
+ top: {
604
+ left: "+",
605
+ mid: "+",
606
+ right: "+",
607
+ fill: "-"
608
+ },
609
+ bottom: {
610
+ left: "+",
611
+ mid: "+",
612
+ right: "+",
613
+ fill: "-"
614
+ },
615
+ sep: {
616
+ left: "+",
617
+ mid: "+",
618
+ right: "+",
619
+ fill: "-"
620
+ },
621
+ headerSep: {
622
+ left: "+",
623
+ mid: "+",
624
+ right: "+",
625
+ fill: "-"
626
+ },
627
+ vertical: "|"
628
+ };
629
+ const BORDERS = {
630
+ double: DOUBLE,
631
+ rounded: ROUNDED,
632
+ ascii: ASCII
633
+ };
634
+
635
+ //#endregion
636
+ //#region src/strings/renderTable/displayWidth.ts
637
+ const segmenter = new Intl.Segmenter();
638
+ const ZERO_WIDTH_RANGES = [
639
+ [8203, 8203],
640
+ [768, 879],
641
+ [6832, 6911],
642
+ [7616, 7679],
643
+ [8400, 8447],
644
+ [65056, 65071]
645
+ ];
646
+ const WIDE_RANGES = [
647
+ [4352, 4447],
648
+ [11904, 12350],
649
+ [12353, 13311],
650
+ [13312, 19903],
651
+ [19968, 40959],
652
+ [40960, 42191],
653
+ [44032, 55203],
654
+ [63744, 64255],
655
+ [65072, 65103],
656
+ [65280, 65376],
657
+ [65504, 65510],
658
+ [127744, 129791],
659
+ [131072, 262141]
660
+ ];
661
+ function inRanges(cp, ranges) {
662
+ return ranges.some(([lo, hi]) => cp >= lo && cp <= hi);
663
+ }
664
+ function segmentWidth(segment) {
665
+ const cp = segment.codePointAt(0) ?? 0;
666
+ if (inRanges(cp, ZERO_WIDTH_RANGES)) return 0;
667
+ return inRanges(cp, WIDE_RANGES) ? 2 : 1;
668
+ }
669
+ function displayWidth(text) {
670
+ let width = 0;
671
+ for (const { segment } of segmenter.segment(text)) width += segmentWidth(segment);
672
+ return width;
673
+ }
674
+ function segments(text) {
675
+ return Array.from(segmenter.segment(text), (entry) => entry.segment);
676
+ }
677
+ function takeWidth(text, maxColumns) {
678
+ let width = 0;
679
+ let taken = "";
680
+ for (const segment of segments(text)) {
681
+ const next = width + segmentWidth(segment);
682
+ if (next > maxColumns) break;
683
+ width = next;
684
+ taken += segment;
685
+ }
686
+ return taken;
687
+ }
688
+ function hardBreak(token, maxColumns) {
689
+ const pieces = [];
690
+ let rest = token;
691
+ while (displayWidth(rest) > maxColumns) {
692
+ const head = takeWidth(rest, maxColumns);
693
+ pieces.push(head);
694
+ rest = rest.slice(head.length);
695
+ }
696
+ if (rest.length > 0) pieces.push(rest);
697
+ return pieces;
698
+ }
699
+ function wrapText(text, maxColumns) {
700
+ const lines = [];
701
+ let current = "";
702
+ for (const token of text.split(" ")) {
703
+ const candidate = current === "" ? token : `${current} ${token}`;
704
+ if (displayWidth(candidate) <= maxColumns) {
705
+ current = candidate;
706
+ continue;
707
+ }
708
+ if (current !== "") lines.push(current);
709
+ if (displayWidth(token) <= maxColumns) {
710
+ current = token;
711
+ continue;
498
712
  }
499
- columnWidths.push(maxWidth);
713
+ const pieces = hardBreak(token, maxColumns);
714
+ current = pieces.pop() ?? "";
715
+ lines.push(...pieces);
500
716
  }
501
- const createLine = (char, left, intersect, right) => {
502
- let line = left;
503
- columnWidths.forEach((width, index) => {
504
- line += char.repeat(width + 2);
505
- if (index < columnWidths.length - 1) line += intersect;
506
- else line += right;
507
- });
508
- line += "\n";
509
- return line;
717
+ if (current !== "" || lines.length === 0) lines.push(current);
718
+ return lines;
719
+ }
720
+
721
+ //#endregion
722
+ //#region src/strings/renderTable/cells.ts
723
+ const ELLIPSIS = "…";
724
+ function wrapFence(content) {
725
+ return `\`\`\`\n${content}\`\`\``;
726
+ }
727
+ function truncate(content, maxWidth) {
728
+ if (displayWidth(content) <= maxWidth) return content;
729
+ return takeWidth(content, maxWidth - displayWidth(ELLIPSIS)) + ELLIPSIS;
730
+ }
731
+ function isNumericColumn(grid, col, header) {
732
+ const cells = (header ? grid.slice(1) : grid).map((row) => row[col] ?? "").filter((cell) => cell !== "");
733
+ return cells.length > 0 && cells.every((cell) => !Number.isNaN(Number(cell)));
734
+ }
735
+ function padCell(content, columnWidth, align) {
736
+ const slack = columnWidth - displayWidth(content);
737
+ if (align === "right") return " ".repeat(slack) + content;
738
+ if (align === "center") {
739
+ const left = Math.floor(slack / 2);
740
+ return " ".repeat(left) + content + " ".repeat(slack - left);
741
+ }
742
+ return content + " ".repeat(slack);
743
+ }
744
+
745
+ //#endregion
746
+ //#region src/strings/renderTable/markdown.ts
747
+ function renderMarkdown(grid, columnCount, alignments, pad) {
748
+ const escapeCell = (value) => value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
749
+ const escaped = grid.map((row) => Array.from({ length: columnCount }, (_, col) => escapeCell(row[col] ?? "")));
750
+ const columnWidths = Array.from({ length: columnCount }, (_, col) => escaped.reduce((max, row) => Math.max(max, displayWidth(row[col] ?? "")), 0));
751
+ const cell = (content, col) => pad + padCell(content, columnWidths[col] ?? 0, alignments[col] ?? "left") + pad;
752
+ const row = (cells) => `|${columnWidths.map((_, col) => cell(cells[col] ?? "", col)).join("|")}|`;
753
+ const delimiterCell = (col) => {
754
+ const align = alignments[col] ?? "left";
755
+ if (align === "center") return " :---: ";
756
+ if (align === "right") return " ---: ";
757
+ return " --- ";
510
758
  };
511
- table += createLine("═", "╔", "╦", "╗");
512
- data.forEach((row, rowIndex) => {
513
- table += "║";
514
- row.forEach((cell, columnIndex) => {
515
- const columnWidth = columnWidths[columnIndex];
516
- if (columnWidth !== void 0) table += ` ${cell.padEnd(columnWidth)} ║`;
517
- });
518
- table += "\n";
519
- if (rowIndex < data.length - 1) table += createLine("─", "╠", "╬", "╣");
520
- else table += createLine("═", "╚", "╩", "╝");
759
+ const [head = [], ...body] = escaped;
760
+ const delimiter = `|${columnWidths.map((_, col) => delimiterCell(col)).join("|")}|`;
761
+ return `${[
762
+ row(head),
763
+ delimiter,
764
+ ...body.map(row)
765
+ ].join("\n")}\n`;
766
+ }
767
+
768
+ //#endregion
769
+ //#region src/strings/renderTable/renderSingle.ts
770
+ function renderSingle(data, options) {
771
+ if (data.length === 0) return "";
772
+ const { align, border = "rounded", header = true, padding = 1, emptyCell = "", numericAlign = false, maxWidth, overflow = "wrap", fence } = options ?? {};
773
+ const columnCount = data.reduce((max, row) => Math.max(max, row.length), 0);
774
+ if (columnCount === 0) return "";
775
+ const grid = data.map((row) => Array.from({ length: columnCount }, (_, i) => {
776
+ const cell = (row[i] ?? "").replace(/\r?\n/g, " ");
777
+ const filled = cell === "" ? emptyCell : cell;
778
+ if (maxWidth === void 0 || overflow !== "truncate") return filled;
779
+ return truncate(filled, maxWidth);
780
+ }));
781
+ const alignments = Array.from({ length: columnCount }, (_, col) => {
782
+ const explicit = typeof align === "string" ? align : align?.[col];
783
+ if (explicit) return explicit;
784
+ if (numericAlign && isNumericColumn(grid, col, header)) return "right";
785
+ return "left";
521
786
  });
522
- return table;
787
+ const pad = " ".repeat(padding);
788
+ if (border === "markdown") {
789
+ const md = renderMarkdown(grid, columnCount, alignments, pad);
790
+ return fence ? wrapFence(md) : md;
791
+ }
792
+ const wrap = maxWidth !== void 0 && overflow === "wrap";
793
+ const rows = grid.map((row) => row.map((cell) => wrap ? wrapText(cell, maxWidth) : [cell]));
794
+ const columnWidths = Array.from({ length: columnCount }, (_, col) => rows.reduce((max, row) => Math.max(max, (row[col] ?? []).reduce((w, line) => Math.max(w, displayWidth(line)), 0)), 0));
795
+ const chars = BORDERS[border];
796
+ function drawLine(part) {
797
+ const segments = columnWidths.map((width) => part.fill.repeat(width + padding * 2));
798
+ return part.left + segments.join(part.mid) + part.right;
799
+ }
800
+ function renderRow(row) {
801
+ const lineCount = row.reduce((max, cellLines) => Math.max(max, cellLines.length), 1);
802
+ return Array.from({ length: lineCount }, (_, line) => columnWidths.map((width, col) => pad + padCell(row[col]?.[line] ?? "", width, alignments[col] ?? "left") + pad).join(chars.vertical)).map((line) => chars.vertical + line + chars.vertical).join("\n");
803
+ }
804
+ const lines = [drawLine(chars.top)];
805
+ rows.forEach((row, rowIndex) => {
806
+ lines.push(renderRow(row));
807
+ if (rowIndex >= rows.length - 1) return;
808
+ lines.push(drawLine(header && rowIndex === 0 ? chars.headerSep : chars.sep));
809
+ });
810
+ lines.push(drawLine(chars.bottom));
811
+ const body = `${lines.join("\n")}\n`;
812
+ return fence ? wrapFence(body) : body;
813
+ }
814
+
815
+ //#endregion
816
+ //#region src/strings/renderTable/pagination.ts
817
+ const DEFAULT_BUDGET = 2e3;
818
+ function paginate(data, options) {
819
+ if (data.length === 0) return [];
820
+ const { budget = DEFAULT_BUDGET, ...tableOptions } = options;
821
+ const header = tableOptions.header === false ? void 0 : data[0];
822
+ const body = header ? data.slice(1) : data;
823
+ const render = (rows) => renderSingle(header ? [header, ...rows] : rows, tableOptions);
824
+ if (body.length === 0) return [render([])];
825
+ const pages = [];
826
+ let current = [];
827
+ for (const row of body) {
828
+ if (current.length === 0 || render([...current, row]).length <= budget) {
829
+ current.push(row);
830
+ continue;
831
+ }
832
+ pages.push(render(current));
833
+ current = [row];
834
+ }
835
+ pages.push(render(current));
836
+ return pages;
837
+ }
838
+
839
+ //#endregion
840
+ //#region src/strings/renderTable/renderTable.ts
841
+ function renderTable(data, options) {
842
+ if (options) validateOptions(options);
843
+ if (options && "budget" in options) return paginate(data, options);
844
+ return renderSingle(data, options);
845
+ }
846
+ function validateOptions(options) {
847
+ const { maxWidth, padding } = options;
848
+ if (maxWidth !== void 0 && (!Number.isInteger(maxWidth) || maxWidth < 1)) throw new RangeError(`renderTable maxWidth must be a positive integer, got ${maxWidth}`);
849
+ if (padding !== void 0 && (!Number.isInteger(padding) || padding < 0)) throw new RangeError(`renderTable padding must be a non-negative integer, got ${padding}`);
523
850
  }
524
851
 
525
852
  //#endregion
@@ -569,7 +896,7 @@ function prettyDifference(numBefore, numAfter) {
569
896
  //#endregion
570
897
  //#region src/index.ts
571
898
  /** Package version */
572
- const version = "0.6.1";
899
+ const version = "0.7.0-next.0";
573
900
 
574
901
  //#endregion
575
902
  exports.assertNever = assertNever;
@@ -578,7 +905,6 @@ exports.currentTime = currentTime;
578
905
  exports.filterCirculars = filterCirculars;
579
906
  exports.formatFilePath = formatFilePath;
580
907
  exports.fyShuffle = fyShuffle;
581
- exports.generateAsciiTable = generateAsciiTable;
582
908
  exports.generateCode = generateCode;
583
909
  exports.hasKeys = hasKeys;
584
910
  exports.isTsOrJsFile = isTsOrJsFile;
@@ -589,6 +915,7 @@ exports.parseDuration = parseDuration;
589
915
  exports.percentage = percentage;
590
916
  exports.prettify = prettify;
591
917
  exports.prettyDifference = prettyDifference;
918
+ exports.renderTable = renderTable;
592
919
  exports.round = round;
593
920
  exports.roundToDenomination = roundToDenomination;
594
921
  exports.toEpochSeconds = toEpochSeconds;