@no-k/hermes 0.1.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 +15 -0
- package/README.md +88 -0
- package/bin/hermes.mjs +159 -0
- package/catalog.json +1084 -0
- package/lib/catalog.mjs +51 -0
- package/package.json +28 -0
- package/templates/algorithm/geometry/ccw/ccw.ts +25 -0
- package/templates/algorithm/geometry/ccw/index.ts +1 -0
- package/templates/algorithm/geometry/convex-hull/convex-hull.ts +43 -0
- package/templates/algorithm/geometry/convex-hull/index.ts +1 -0
- package/templates/algorithm/geometry/polygon-area/index.ts +1 -0
- package/templates/algorithm/geometry/polygon-area/polygon-area.ts +13 -0
- package/templates/algorithm/graph/bellman-ford/bellman-ford.ts +46 -0
- package/templates/algorithm/graph/bellman-ford/index.ts +1 -0
- package/templates/algorithm/graph/bfs/bfs.ts +21 -0
- package/templates/algorithm/graph/bfs/index.ts +1 -0
- package/templates/algorithm/graph/dfs/dfs.ts +22 -0
- package/templates/algorithm/graph/dfs/index.ts +1 -0
- package/templates/algorithm/graph/dijkstra/dijkstra.ts +37 -0
- package/templates/algorithm/graph/dijkstra/index.ts +1 -0
- package/templates/algorithm/graph/floyd-warshall/floyd-warshall.ts +17 -0
- package/templates/algorithm/graph/floyd-warshall/index.ts +1 -0
- package/templates/algorithm/graph/kruskal/index.ts +1 -0
- package/templates/algorithm/graph/kruskal/kruskal.ts +29 -0
- package/templates/algorithm/graph/prim/index.ts +1 -0
- package/templates/algorithm/graph/prim/prim.ts +44 -0
- package/templates/algorithm/graph/topology-sort/index.ts +1 -0
- package/templates/algorithm/graph/topology-sort/topology-sort.ts +31 -0
- package/templates/algorithm/search/binary-search/binary-search.ts +74 -0
- package/templates/algorithm/search/binary-search/index.ts +1 -0
- package/templates/algorithm/string/kmp/index.ts +1 -0
- package/templates/algorithm/string/kmp/kmp.ts +46 -0
- package/templates/algorithm/string/z-algorithm/index.ts +1 -0
- package/templates/algorithm/string/z-algorithm/z-algorithm.ts +41 -0
- package/templates/algorithm/tree/avl-tree/avl-tree.ts +197 -0
- package/templates/algorithm/tree/avl-tree/index.ts +1 -0
- package/templates/algorithm/tree/binary-search-tree/binary-search-tree.ts +144 -0
- package/templates/algorithm/tree/binary-search-tree/index.ts +1 -0
- package/templates/algorithm/tree/lca/index.ts +1 -0
- package/templates/algorithm/tree/lca/lca.ts +119 -0
- package/templates/array/chunk/chunk.ts +13 -0
- package/templates/array/chunk/index.ts +1 -0
- package/templates/array/compact/compact.ts +8 -0
- package/templates/array/compact/index.ts +1 -0
- package/templates/array/count-by/count-by.ts +12 -0
- package/templates/array/count-by/index.ts +1 -0
- package/templates/array/difference/difference.ts +6 -0
- package/templates/array/difference/index.ts +1 -0
- package/templates/array/drop/drop.ts +7 -0
- package/templates/array/drop/index.ts +1 -0
- package/templates/array/group-by/group-by.ts +18 -0
- package/templates/array/group-by/index.ts +1 -0
- package/templates/array/intersection/index.ts +1 -0
- package/templates/array/intersection/intersection.ts +15 -0
- package/templates/array/key-by/index.ts +1 -0
- package/templates/array/key-by/key-by.ts +11 -0
- package/templates/array/max-by/index.ts +1 -0
- package/templates/array/max-by/max-by.ts +20 -0
- package/templates/array/min-by/index.ts +1 -0
- package/templates/array/min-by/min-by.ts +20 -0
- package/templates/array/partition/index.ts +1 -0
- package/templates/array/partition/partition.ts +16 -0
- package/templates/array/range/index.ts +1 -0
- package/templates/array/range/range.ts +22 -0
- package/templates/array/sample/index.ts +1 -0
- package/templates/array/sample/sample.ts +10 -0
- package/templates/array/shuffle/index.ts +1 -0
- package/templates/array/shuffle/shuffle.ts +15 -0
- package/templates/array/sort-by/index.ts +1 -0
- package/templates/array/sort-by/sort-by.ts +32 -0
- package/templates/array/sum-by/index.ts +1 -0
- package/templates/array/sum-by/sum-by.ts +9 -0
- package/templates/array/take/index.ts +1 -0
- package/templates/array/take/take.ts +7 -0
- package/templates/array/union/index.ts +1 -0
- package/templates/array/union/union.ts +5 -0
- package/templates/array/unique/index.ts +1 -0
- package/templates/array/unique/unique.ts +5 -0
- package/templates/array/unique-by/index.ts +1 -0
- package/templates/array/unique-by/unique-by.ts +19 -0
- package/templates/array/windowed/index.ts +1 -0
- package/templates/array/windowed/windowed.ts +15 -0
- package/templates/array/zip/index.ts +1 -0
- package/templates/array/zip/zip.ts +11 -0
- package/templates/boolean/parse-boolean/index.ts +1 -0
- package/templates/boolean/parse-boolean/parse-boolean.ts +17 -0
- package/templates/boolean/toggle/index.ts +1 -0
- package/templates/boolean/toggle/toggle.ts +2 -0
- package/templates/boolean/xor/index.ts +1 -0
- package/templates/boolean/xor/xor.ts +2 -0
- package/templates/data-structure/deque/deque.ts +79 -0
- package/templates/data-structure/deque/index.ts +1 -0
- package/templates/data-structure/fenwick-tree/fenwick-tree.ts +76 -0
- package/templates/data-structure/fenwick-tree/index.ts +1 -0
- package/templates/data-structure/heap/heap.ts +88 -0
- package/templates/data-structure/heap/index.ts +3 -0
- package/templates/data-structure/heap/max-heap.ts +7 -0
- package/templates/data-structure/heap/min-heap.ts +7 -0
- package/templates/data-structure/priority-queue/index.ts +1 -0
- package/templates/data-structure/priority-queue/priority-queue.ts +44 -0
- package/templates/data-structure/queue/index.ts +1 -0
- package/templates/data-structure/queue/queue.ts +66 -0
- package/templates/data-structure/segment-tree/index.ts +1 -0
- package/templates/data-structure/segment-tree/segment-tree.ts +79 -0
- package/templates/data-structure/trie/index.ts +1 -0
- package/templates/data-structure/trie/trie.ts +104 -0
- package/templates/data-structure/union-find/index.ts +1 -0
- package/templates/data-structure/union-find/union-find.ts +79 -0
- package/templates/dom/attribute/attribute.ts +24 -0
- package/templates/dom/attribute/index.ts +1 -0
- package/templates/dom/class-name/class-name.ts +48 -0
- package/templates/dom/class-name/index.ts +1 -0
- package/templates/dom/is-element/index.ts +1 -0
- package/templates/dom/is-element/is-element.ts +20 -0
- package/templates/dom/query/index.ts +1 -0
- package/templates/dom/query/query.ts +7 -0
- package/templates/http/content-type/content-type.ts +130 -0
- package/templates/http/content-type/index.ts +1 -0
- package/templates/http/header/header.ts +56 -0
- package/templates/http/header/index.ts +1 -0
- package/templates/http/method/index.ts +1 -0
- package/templates/http/method/method.ts +25 -0
- package/templates/http/status/index.ts +1 -0
- package/templates/http/status/status.ts +191 -0
- package/templates/number/clamp/clamp.ts +11 -0
- package/templates/number/clamp/index.ts +1 -0
- package/templates/number/factorial/factorial.ts +15 -0
- package/templates/number/factorial/index.ts +1 -0
- package/templates/number/format-thousands/format-thousands.ts +24 -0
- package/templates/number/format-thousands/index.ts +1 -0
- package/templates/number/gcd/gcd.ts +13 -0
- package/templates/number/gcd/index.ts +1 -0
- package/templates/number/in-range/in-range.ts +3 -0
- package/templates/number/in-range/index.ts +1 -0
- package/templates/number/is-even/index.ts +1 -0
- package/templates/number/is-even/is-even.ts +5 -0
- package/templates/number/is-odd/index.ts +1 -0
- package/templates/number/is-odd/is-odd.ts +7 -0
- package/templates/number/lcm/index.ts +1 -0
- package/templates/number/lcm/lcm.ts +12 -0
- package/templates/number/lerp/index.ts +1 -0
- package/templates/number/lerp/lerp.ts +3 -0
- package/templates/number/percentage/index.ts +1 -0
- package/templates/number/percentage/percentage.ts +7 -0
- package/templates/number/random/index.ts +1 -0
- package/templates/number/random/random.ts +9 -0
- package/templates/number/random-int/index.ts +1 -0
- package/templates/number/random-int/random-int.ts +9 -0
- package/templates/number/round/index.ts +1 -0
- package/templates/number/round/round.ts +22 -0
- package/templates/number/safe-parse-number/index.ts +1 -0
- package/templates/number/safe-parse-number/safe-parse-number.ts +29 -0
- package/templates/number/to-ordinal/index.ts +1 -0
- package/templates/number/to-ordinal/to-ordinal.ts +28 -0
- package/templates/object/deep-clone/deep-clone.ts +47 -0
- package/templates/object/deep-clone/index.ts +1 -0
- package/templates/object/deep-equal/deep-equal.ts +66 -0
- package/templates/object/deep-equal/index.ts +1 -0
- package/templates/object/deep-merge/deep-merge.ts +49 -0
- package/templates/object/deep-merge/index.ts +1 -0
- package/templates/object/defaults/defaults.ts +14 -0
- package/templates/object/defaults/index.ts +1 -0
- package/templates/object/filter-object/filter-object.ts +11 -0
- package/templates/object/filter-object/index.ts +1 -0
- package/templates/object/get/get.ts +75 -0
- package/templates/object/get/index.ts +1 -0
- package/templates/object/has/has.ts +60 -0
- package/templates/object/has/index.ts +1 -0
- package/templates/object/invert/index.ts +1 -0
- package/templates/object/invert/invert.ts +13 -0
- package/templates/object/map-keys/index.ts +1 -0
- package/templates/object/map-keys/map-keys.ts +16 -0
- package/templates/object/map-values/index.ts +1 -0
- package/templates/object/map-values/map-values.ts +14 -0
- package/templates/object/omit/index.ts +1 -0
- package/templates/object/omit/omit.ts +15 -0
- package/templates/object/omit-by/index.ts +1 -0
- package/templates/object/omit-by/omit-by.ts +19 -0
- package/templates/object/pick/index.ts +1 -0
- package/templates/object/pick/pick.ts +15 -0
- package/templates/object/pick-by/index.ts +1 -0
- package/templates/object/pick-by/pick-by.ts +19 -0
- package/templates/object/set/index.ts +1 -0
- package/templates/object/set/set.ts +118 -0
- package/templates/string/capitalize/capitalize.ts +4 -0
- package/templates/string/capitalize/index.ts +1 -0
- package/templates/string/change-case/change-case.ts +98 -0
- package/templates/string/change-case/index.ts +1 -0
- package/templates/string/char/char.ts +14 -0
- package/templates/string/char/index.ts +1 -0
- package/templates/string/count-occurrences/count-occurrences.ts +20 -0
- package/templates/string/count-occurrences/index.ts +1 -0
- package/templates/string/escape-html/escape-html.ts +16 -0
- package/templates/string/escape-html/index.ts +1 -0
- package/templates/string/get-decorated-string/get-decorated-string.ts +7 -0
- package/templates/string/get-decorated-string/index.ts +1 -0
- package/templates/string/is-blank/index.ts +1 -0
- package/templates/string/is-blank/is-blank.ts +6 -0
- package/templates/string/mask/index.ts +1 -0
- package/templates/string/mask/mask.ts +30 -0
- package/templates/string/pad/index.ts +1 -0
- package/templates/string/pad/pad.ts +17 -0
- package/templates/string/random-string/index.ts +1 -0
- package/templates/string/random-string/random-string.ts +20 -0
- package/templates/string/reverse/index.ts +1 -0
- package/templates/string/reverse/reverse.ts +6 -0
- package/templates/string/slugify/index.ts +1 -0
- package/templates/string/slugify/slugify.ts +11 -0
- package/templates/string/strip-tags/index.ts +1 -0
- package/templates/string/strip-tags/strip-tags.ts +8 -0
- package/templates/string/template/index.ts +1 -0
- package/templates/string/template/template.ts +14 -0
- package/templates/string/truncate/index.ts +1 -0
- package/templates/string/truncate/truncate.ts +17 -0
- package/templates/string/truncate-middle/index.ts +1 -0
- package/templates/string/truncate-middle/truncate-middle.ts +25 -0
- package/templates/string/uuid/index.ts +1 -0
- package/templates/string/uuid/uuid.ts +9 -0
- package/templates/tailwindcss/class-variant/class-variant.ts +35 -0
- package/templates/tailwindcss/class-variant/index.ts +1 -0
- package/templates/tailwindcss/cn/cn.ts +29 -0
- package/templates/tailwindcss/cn/index.ts +1 -0
- package/templates/tailwindcss/create-tailwind-css/create-tailwind-css.ts +16 -0
- package/templates/tailwindcss/create-tailwind-css/index.ts +1 -0
- package/templates/tailwindcss/tw-merge-config/index.ts +1 -0
- package/templates/tailwindcss/tw-merge-config/tw-merge-config.ts +14 -0
- package/templates/type/get-type/get-type.ts +27 -0
- package/templates/type/get-type/index.ts +1 -0
- package/templates/type/is-type-of/index.ts +1 -0
- package/templates/type/is-type-of/is-type-of.ts +26 -0
- package/templates/util/mutex/index.ts +1 -0
- package/templates/util/mutex/mutex.ts +109 -0
- package/templates/util/url-parser/index.ts +1 -0
- package/templates/util/url-parser/url-parser.ts +44 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface MaskOptions {
|
|
2
|
+
/** 앞에서 그대로 남길 글자 수. 기본 1. */
|
|
3
|
+
start?: number;
|
|
4
|
+
/** 뒤에서 그대로 남길 글자 수. 기본 1. */
|
|
5
|
+
end?: number;
|
|
6
|
+
/** 가운데를 가릴 문자. 기본 "*". */
|
|
7
|
+
char?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 앞 start글자와 뒤 end글자만 남기고 가운데를 char로 가린다. 입력은 변형하지 않는다.
|
|
11
|
+
// start + end가 길이 이상이면 가릴 가운데가 없으므로 원문을 그대로 반환한다.
|
|
12
|
+
//
|
|
13
|
+
// 길이 계산과 슬라이싱을 코드포인트 단위(`[...str]`)로 한다. str.length나 slice는
|
|
14
|
+
// 서로게이트 쌍(이모지 등)을 반쪽으로 세거나 쪼개므로, 사용자가 보는 "글자 수"와
|
|
15
|
+
// 어긋난다. 가운데 한 글자당 char 하나로 치환한다 (남는 글자 수만큼 char 반복).
|
|
16
|
+
export const mask = (str: string, options: MaskOptions = {}): string => {
|
|
17
|
+
const { start = 1, end = 1, char = "*" } = options;
|
|
18
|
+
|
|
19
|
+
const chars = [...str];
|
|
20
|
+
const hiddenCount = chars.length - start - end;
|
|
21
|
+
|
|
22
|
+
if (hiddenCount <= 0) {
|
|
23
|
+
return str;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const head = chars.slice(0, start).join("");
|
|
27
|
+
const tail = chars.slice(chars.length - end).join("");
|
|
28
|
+
|
|
29
|
+
return head + char.repeat(hiddenCount) + tail;
|
|
30
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./pad";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// 양쪽을 char로 균등하게 채워 가운데 정렬한다. length 이하면 그대로 둔다.
|
|
2
|
+
// 채울 총량을 좌우로 나누되, 홀수 잔여는 오른쪽에 더한다 (왼쪽 floor, 오른쪽 ceil).
|
|
3
|
+
// char가 여러 글자여도 되도록 padStart/padEnd로 채운 뒤 정확한 길이로 자른다.
|
|
4
|
+
// String.prototype.padStart/padEnd는 char가 빈 문자열이면 아무것도 채우지 않으므로
|
|
5
|
+
// 그 경우 원본이 그대로 반환된다.
|
|
6
|
+
export const pad = (str: string, length: number, char = " "): string => {
|
|
7
|
+
if (str.length >= length) {
|
|
8
|
+
return str;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const total = length - str.length;
|
|
12
|
+
const left = Math.floor(total / 2);
|
|
13
|
+
|
|
14
|
+
// 왼쪽을 left만큼 채운 길이까지 padStart, 그 결과를 다시 length까지 padEnd.
|
|
15
|
+
// 남은 오른쪽(ceil)은 length - (str.length + left)로 자동 결정된다.
|
|
16
|
+
return str.padStart(str.length + left, char).padEnd(length, char);
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./random-string";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// alphabet에서 length 글자를 런타임 난수로 뽑아 문자열을 만든다.
|
|
2
|
+
// 기본 alphabet은 영숫자(A-Z, a-z, 0-9). length <= 0이면 빈 문자열을 반환한다.
|
|
3
|
+
//
|
|
4
|
+
// 매번 새 문자열을 만들어 반환하며 인자는 변형하지 않는다. 난수원은 Math.random이라
|
|
5
|
+
// 암호학적으로 안전하지 않다 (식별자/토큰용 가벼운 무작위 문자열 생성 용도).
|
|
6
|
+
const DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
7
|
+
|
|
8
|
+
export const randomString = (length: number, alphabet: string = DEFAULT_ALPHABET): string => {
|
|
9
|
+
if (length <= 0 || alphabet.length === 0) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let result = "";
|
|
14
|
+
for (let index = 0; index < length; index += 1) {
|
|
15
|
+
const pick = Math.floor(Math.random() * alphabet.length);
|
|
16
|
+
result += alphabet[pick];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./reverse";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./slugify";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// URL/식별자용 슬러그를 만든다. 소문자로 내린 뒤 ASCII 영숫자가 아닌 문자가
|
|
2
|
+
// 이어진 구간을 "-" 하나로 합치고, 양끝의 "-"는 제거한다. 입력은 변형하지 않는다.
|
|
3
|
+
//
|
|
4
|
+
// `[^a-z0-9]+` 한 번의 치환으로 구분자 런을 단일 "-"로 접고, 다시 양끝 "-"만
|
|
5
|
+
// 잘라낸다. ASCII 기준이라 비ASCII 글자(한글 등)는 영숫자가 아니므로 구분자로 취급된다.
|
|
6
|
+
export const slugify = (str: string): string => {
|
|
7
|
+
return str
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
10
|
+
.replace(/^-+|-+$/g, "");
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./strip-tags";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// `<tag>`, `</tag>`, `<tag attr>` 같은 HTML 태그만 제거하고 사이 텍스트는 남긴다.
|
|
2
|
+
// 입력은 변형하지 않고 새 문자열을 반환한다.
|
|
3
|
+
//
|
|
4
|
+
// `<\/?[a-zA-Z][^>]*>` — `<` 또는 `</` 뒤에 글자로 시작하는 것만 태그로 본다.
|
|
5
|
+
// 덕분에 "1 < 2 and 3 > 2" 처럼 글자가 아닌 게 따라오는 단독 부등호는 그대로 남는다.
|
|
6
|
+
export const stripTags = (str: string): string => {
|
|
7
|
+
return str.replace(/<\/?[a-zA-Z][^>]*>/g, "");
|
|
8
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./template";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// str 안의 `{key}` 자리표시자를 data[key] 값으로 치환한다.
|
|
2
|
+
// data에 없는 key의 `{key}`는 그대로 둔다 (입력은 변형하지 않고 새 문자열을 반환).
|
|
3
|
+
//
|
|
4
|
+
// 정규식 `/\{(\w+)\}/g`로 한 번 훑으며 매칭만 교체한다. key는 영숫자/언더스코어(\w)만
|
|
5
|
+
// 받으므로 `{` 만 있고 닫히지 않은 텍스트나 `{ key }` 같은 공백 포함 형태는 매칭되지 않아
|
|
6
|
+
// 원문이 보존된다. 값이 number면 String()으로 문자열화한다.
|
|
7
|
+
const PLACEHOLDER = /\{(\w+)\}/g;
|
|
8
|
+
|
|
9
|
+
export const template = (str: string, data: Record<string, string | number>): string => {
|
|
10
|
+
return str.replace(PLACEHOLDER, (match, key: string) => {
|
|
11
|
+
const value = data[key];
|
|
12
|
+
return value === undefined ? match : String(value);
|
|
13
|
+
});
|
|
14
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./truncate";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// 문자열이 length 이하면 그대로 두고, 초과하면 뒤를 잘라 suffix를 붙인다.
|
|
2
|
+
// 핵심은 "결과 전체 길이"가 length가 되도록 맞추는 것이다. 그래서 잘라낼
|
|
3
|
+
// 본문 길이는 length - suffix.length가 된다 (suffix 자리를 미리 비워 둔다).
|
|
4
|
+
// length가 suffix보다 짧은 극단(예: length 1, suffix "…")에서는 본문이 음수로
|
|
5
|
+
// 가지 않게 막고, suffix 자체를 length까지만 잘라 돌려준다.
|
|
6
|
+
export const truncate = (str: string, length: number, suffix = "…"): string => {
|
|
7
|
+
if (str.length <= length) {
|
|
8
|
+
return str;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// suffix가 length를 다 차지하거나 넘으면 본문이 들어갈 자리가 없다.
|
|
12
|
+
if (suffix.length >= length) {
|
|
13
|
+
return suffix.slice(0, length);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./truncate-middle";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// 가운데를 잘라 양끝을 보존한다. 긴 파일명/경로를 "long…name.txt"처럼
|
|
2
|
+
// 줄일 때 쓴다. length 이하면 그대로 둔다.
|
|
3
|
+
// 결과 전체 길이가 length가 되도록 suffix 자리를 먼저 빼고, 남은 길이를
|
|
4
|
+
// 앞쪽(head)과 뒤쪽(tail)에 나눠 준다. 홀수 잔여는 head에 더해 앞을 더 보존한다.
|
|
5
|
+
// length가 suffix 이하인 극단에서는 본문 자리가 없으므로 suffix를 length까지만
|
|
6
|
+
// 잘라 돌려준다.
|
|
7
|
+
export const truncateMiddle = (str: string, length: number, suffix = "…"): string => {
|
|
8
|
+
if (str.length <= length) {
|
|
9
|
+
return str;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (suffix.length >= length) {
|
|
13
|
+
return suffix.slice(0, length);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const visible = length - suffix.length;
|
|
17
|
+
const headLength = Math.ceil(visible / 2);
|
|
18
|
+
const tailLength = visible - headLength;
|
|
19
|
+
|
|
20
|
+
const head = str.slice(0, headLength);
|
|
21
|
+
// tailLength가 0이면 빈 문자열이어야 한다. slice(-0)은 전체를 반환하므로 분기한다.
|
|
22
|
+
const tail = tailLength === 0 ? "" : str.slice(-tailLength);
|
|
23
|
+
|
|
24
|
+
return head + suffix + tail;
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./uuid";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type VariantProps, createTV } from "tailwind-variants";
|
|
2
|
+
import { type TWMergeConfig, twMergeConfig as defaultTwMergeConfig } from "../tw-merge-config/index";
|
|
3
|
+
|
|
4
|
+
export type { VariantProps };
|
|
5
|
+
|
|
6
|
+
export type ClassVariant = ReturnType<typeof createTV>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 지정한 tailwind-merge 설정을 사용하는 variant 기반 className 빌더를 만든다.
|
|
10
|
+
*/
|
|
11
|
+
export const createClassVariant = (twMergeConfig: TWMergeConfig = defaultTwMergeConfig): ClassVariant =>
|
|
12
|
+
createTV({ twMergeConfig });
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `tailwind-variants`의 `tv`를 감싸 variant 기반 className 빌더를 만든다.
|
|
16
|
+
*
|
|
17
|
+
* `cn`과 동일한 {@link twMergeConfig}를 공유하므로 variant 병합 시에도 같은
|
|
18
|
+
* Tailwind 충돌 규칙이 적용된다. 컴포넌트의 variant prop 타입은 `VariantProps`로
|
|
19
|
+
* 추론할 수 있다.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const button = classVariant({
|
|
23
|
+
* base: "rounded font-medium",
|
|
24
|
+
* variants: {
|
|
25
|
+
* intent: { primary: "bg-blue-500 text-white", ghost: "bg-transparent" },
|
|
26
|
+
* size: { sm: "px-2 py-1", lg: "px-4 py-2" },
|
|
27
|
+
* },
|
|
28
|
+
* defaultVariants: { intent: "primary", size: "sm" },
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* button({ intent: "ghost", size: "lg" });
|
|
32
|
+
*
|
|
33
|
+
* type ButtonVariants = VariantProps<typeof button>;
|
|
34
|
+
*/
|
|
35
|
+
export const classVariant = createClassVariant();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./class-variant";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from "clsx";
|
|
2
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
3
|
+
import { type TWMergeConfig, twMergeConfig as defaultTwMergeConfig } from "../tw-merge-config/index";
|
|
4
|
+
|
|
5
|
+
export type Cn = (...inputs: ClassValue[]) => string;
|
|
6
|
+
|
|
7
|
+
export type { ClassValue };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 지정한 tailwind-merge 설정을 사용하는 `cn` 함수를 만든다.
|
|
11
|
+
*/
|
|
12
|
+
export const createCn = (twMergeConfig: TWMergeConfig = defaultTwMergeConfig): Cn => {
|
|
13
|
+
const twMerge = extendTailwindMerge<string, string>(twMergeConfig);
|
|
14
|
+
|
|
15
|
+
return (...inputs) => twMerge(clsx(inputs));
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 조건부 className을 합치고 충돌하는 Tailwind 클래스를 정리한다.
|
|
20
|
+
*
|
|
21
|
+
* `clsx`로 문자열, 배열, 객체 형태의 입력을 하나의 문자열로 합친 뒤
|
|
22
|
+
* `tailwind-merge`로 같은 속성을 다투는 클래스 중 뒤에 온 값만 남긴다.
|
|
23
|
+
* 병합 동작은 {@link twMergeConfig}를 따르며 `classVariant`와 설정을 공유한다.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* cn("px-2 py-1", "px-4"); // "py-1 px-4"
|
|
27
|
+
* cn("text-sm", { "font-bold": isActive });
|
|
28
|
+
*/
|
|
29
|
+
export const cn = createCn();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./cn";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ClassVariant, createClassVariant } from "../class-variant/index";
|
|
2
|
+
import { type Cn, createCn } from "../cn/index";
|
|
3
|
+
import { type TWMergeConfig, twMergeConfig as defaultTwMergeConfig } from "../tw-merge-config/index";
|
|
4
|
+
|
|
5
|
+
export type TailwindCssHelpers = {
|
|
6
|
+
cn: Cn;
|
|
7
|
+
classVariant: ClassVariant;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 지정한 tailwind-merge 설정을 공유하는 className helper 세트를 만든다.
|
|
12
|
+
*/
|
|
13
|
+
export const createTailwindCss = (twMergeConfig: TWMergeConfig = defaultTwMergeConfig): TailwindCssHelpers => ({
|
|
14
|
+
cn: createCn(twMergeConfig),
|
|
15
|
+
classVariant: createClassVariant(twMergeConfig),
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./create-tailwind-css";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./tw-merge-config";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TWMergeConfig } from "tailwind-variants";
|
|
2
|
+
|
|
3
|
+
export type { TWMergeConfig };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `cn`과 `classVariant`가 함께 사용하는 tailwind-merge 설정.
|
|
7
|
+
*
|
|
8
|
+
* 프로젝트 고유 Tailwind 토큰(커스텀 색상, spacing, classGroup 등)을 여기에
|
|
9
|
+
* 추가하면 `cn`의 충돌 정리와 `classVariant`의 variant 머지에 동시에 반영된다.
|
|
10
|
+
* 기본값은 빈 설정이며 이 경우 tailwind-merge 기본 동작을 그대로 따른다.
|
|
11
|
+
*
|
|
12
|
+
* @see https://github.com/dcastil/tailwind-merge/blob/main/docs/configuration.md
|
|
13
|
+
*/
|
|
14
|
+
export const twMergeConfig: TWMergeConfig = {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type TypeofResult =
|
|
2
|
+
| "undefined"
|
|
3
|
+
| "object"
|
|
4
|
+
| "boolean"
|
|
5
|
+
| "number"
|
|
6
|
+
| "bigint"
|
|
7
|
+
| "string"
|
|
8
|
+
| "symbol"
|
|
9
|
+
| "function"
|
|
10
|
+
| "array"
|
|
11
|
+
| "null";
|
|
12
|
+
|
|
13
|
+
export const getType = (target: unknown): TypeofResult => {
|
|
14
|
+
if (typeof target !== "object") {
|
|
15
|
+
return typeof target;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (target === null) {
|
|
19
|
+
return "null";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (Array.isArray(target)) {
|
|
23
|
+
return "array";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return "object";
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./get-type";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./is-type-of";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type TypeofResult, getType } from "../get-type/index";
|
|
2
|
+
|
|
3
|
+
export const isTypeOf = (
|
|
4
|
+
target: unknown,
|
|
5
|
+
expectedType: TypeofResult,
|
|
6
|
+
options?: {
|
|
7
|
+
debug?: boolean;
|
|
8
|
+
onDebug?: (param: {
|
|
9
|
+
expectedType: TypeofResult;
|
|
10
|
+
nowType: TypeofResult;
|
|
11
|
+
}) => void;
|
|
12
|
+
},
|
|
13
|
+
) => {
|
|
14
|
+
const nowType = getType(target);
|
|
15
|
+
|
|
16
|
+
const result = nowType === expectedType;
|
|
17
|
+
|
|
18
|
+
if (options?.debug) {
|
|
19
|
+
if (!result) {
|
|
20
|
+
console.info({ expectedType, nowType });
|
|
21
|
+
options?.onDebug?.({ expectedType, nowType });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return nowType === expectedType;
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./mutex";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Queue } from "../../data-structure/queue/index";
|
|
2
|
+
|
|
3
|
+
const createTimeoutError = () => new Error("Mutex lock timeout");
|
|
4
|
+
const createAbortError = () => new Error("Mutex lock aborted");
|
|
5
|
+
|
|
6
|
+
type LockOptions = {
|
|
7
|
+
timeout?: number; // ms
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class Mutex<T> {
|
|
12
|
+
private waitOrderQueue = new Queue<(value?: unknown) => void>();
|
|
13
|
+
private locked = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* register promiseQueue
|
|
17
|
+
*/
|
|
18
|
+
private wait() {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
this.waitOrderQueue.enqueue(resolve);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 다음 작업 실행
|
|
26
|
+
*/
|
|
27
|
+
private next() {
|
|
28
|
+
const nextResolve = this.waitOrderQueue.dequeue();
|
|
29
|
+
if (nextResolve) {
|
|
30
|
+
nextResolve();
|
|
31
|
+
} else {
|
|
32
|
+
this.locked = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* run with locked: true
|
|
38
|
+
*/
|
|
39
|
+
public async lock<T>(callback: () => Promise<T>, { timeout, signal }: LockOptions = {}): Promise<T> {
|
|
40
|
+
if (this.locked) {
|
|
41
|
+
const wait = this.wait();
|
|
42
|
+
|
|
43
|
+
if (timeout != null || signal) {
|
|
44
|
+
await Promise.race([
|
|
45
|
+
wait,
|
|
46
|
+
...(timeout != null ? [this.createTimeout(timeout)] : []),
|
|
47
|
+
...(signal ? [this.createAbort(signal)] : []),
|
|
48
|
+
]);
|
|
49
|
+
} else {
|
|
50
|
+
await wait;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.locked = true;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
return await callback();
|
|
58
|
+
} finally {
|
|
59
|
+
this.next();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* locked true: return null
|
|
65
|
+
* locked false: locked + run callback
|
|
66
|
+
*/
|
|
67
|
+
public async spinLock<T>(callback: () => Promise<T>): Promise<T | null> {
|
|
68
|
+
if (this.locked) return null;
|
|
69
|
+
|
|
70
|
+
this.locked = true;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
return await callback();
|
|
74
|
+
} finally {
|
|
75
|
+
this.next();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* timeout helper
|
|
81
|
+
*/
|
|
82
|
+
private createTimeout(ms: number): Promise<never> {
|
|
83
|
+
return new Promise((_, reject) => {
|
|
84
|
+
const id = setTimeout(() => {
|
|
85
|
+
clearTimeout(id);
|
|
86
|
+
reject(createTimeoutError());
|
|
87
|
+
}, ms);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* abort helper
|
|
93
|
+
*/
|
|
94
|
+
private createAbort(signal: AbortSignal): Promise<never> {
|
|
95
|
+
return new Promise((_, reject) => {
|
|
96
|
+
if (signal.aborted) {
|
|
97
|
+
reject(createAbortError());
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const abort = () => {
|
|
102
|
+
signal.removeEventListener("abort", abort);
|
|
103
|
+
reject(createAbortError());
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
signal.addEventListener("abort", abort);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./url-parser";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
type URLQuery = Record<string, string | string[]>;
|
|
2
|
+
|
|
3
|
+
export class URLParser {
|
|
4
|
+
private toURL(url: string): URL {
|
|
5
|
+
try {
|
|
6
|
+
return new URL(url);
|
|
7
|
+
} catch {
|
|
8
|
+
return new URL(decodeURIComponent(url));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getQuery(url: string): URLQuery {
|
|
13
|
+
const result: URLQuery = {};
|
|
14
|
+
const parsedUrl = this.toURL(url);
|
|
15
|
+
|
|
16
|
+
parsedUrl.searchParams.forEach((value, key) => {
|
|
17
|
+
const previousValue = result[key];
|
|
18
|
+
|
|
19
|
+
if (Array.isArray(previousValue)) {
|
|
20
|
+
previousValue.push(value);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (previousValue !== undefined) {
|
|
25
|
+
result[key] = [previousValue, value];
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
result[key] = value;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getFragment(url: string): string {
|
|
36
|
+
const parsedUrl = this.toURL(url);
|
|
37
|
+
|
|
38
|
+
return decodeURIComponent(parsedUrl.hash.slice(1));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getOrigin(url: string): string {
|
|
42
|
+
return this.toURL(url).origin;
|
|
43
|
+
}
|
|
44
|
+
}
|