@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.
Files changed (234) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +88 -0
  3. package/bin/hermes.mjs +159 -0
  4. package/catalog.json +1084 -0
  5. package/lib/catalog.mjs +51 -0
  6. package/package.json +28 -0
  7. package/templates/algorithm/geometry/ccw/ccw.ts +25 -0
  8. package/templates/algorithm/geometry/ccw/index.ts +1 -0
  9. package/templates/algorithm/geometry/convex-hull/convex-hull.ts +43 -0
  10. package/templates/algorithm/geometry/convex-hull/index.ts +1 -0
  11. package/templates/algorithm/geometry/polygon-area/index.ts +1 -0
  12. package/templates/algorithm/geometry/polygon-area/polygon-area.ts +13 -0
  13. package/templates/algorithm/graph/bellman-ford/bellman-ford.ts +46 -0
  14. package/templates/algorithm/graph/bellman-ford/index.ts +1 -0
  15. package/templates/algorithm/graph/bfs/bfs.ts +21 -0
  16. package/templates/algorithm/graph/bfs/index.ts +1 -0
  17. package/templates/algorithm/graph/dfs/dfs.ts +22 -0
  18. package/templates/algorithm/graph/dfs/index.ts +1 -0
  19. package/templates/algorithm/graph/dijkstra/dijkstra.ts +37 -0
  20. package/templates/algorithm/graph/dijkstra/index.ts +1 -0
  21. package/templates/algorithm/graph/floyd-warshall/floyd-warshall.ts +17 -0
  22. package/templates/algorithm/graph/floyd-warshall/index.ts +1 -0
  23. package/templates/algorithm/graph/kruskal/index.ts +1 -0
  24. package/templates/algorithm/graph/kruskal/kruskal.ts +29 -0
  25. package/templates/algorithm/graph/prim/index.ts +1 -0
  26. package/templates/algorithm/graph/prim/prim.ts +44 -0
  27. package/templates/algorithm/graph/topology-sort/index.ts +1 -0
  28. package/templates/algorithm/graph/topology-sort/topology-sort.ts +31 -0
  29. package/templates/algorithm/search/binary-search/binary-search.ts +74 -0
  30. package/templates/algorithm/search/binary-search/index.ts +1 -0
  31. package/templates/algorithm/string/kmp/index.ts +1 -0
  32. package/templates/algorithm/string/kmp/kmp.ts +46 -0
  33. package/templates/algorithm/string/z-algorithm/index.ts +1 -0
  34. package/templates/algorithm/string/z-algorithm/z-algorithm.ts +41 -0
  35. package/templates/algorithm/tree/avl-tree/avl-tree.ts +197 -0
  36. package/templates/algorithm/tree/avl-tree/index.ts +1 -0
  37. package/templates/algorithm/tree/binary-search-tree/binary-search-tree.ts +144 -0
  38. package/templates/algorithm/tree/binary-search-tree/index.ts +1 -0
  39. package/templates/algorithm/tree/lca/index.ts +1 -0
  40. package/templates/algorithm/tree/lca/lca.ts +119 -0
  41. package/templates/array/chunk/chunk.ts +13 -0
  42. package/templates/array/chunk/index.ts +1 -0
  43. package/templates/array/compact/compact.ts +8 -0
  44. package/templates/array/compact/index.ts +1 -0
  45. package/templates/array/count-by/count-by.ts +12 -0
  46. package/templates/array/count-by/index.ts +1 -0
  47. package/templates/array/difference/difference.ts +6 -0
  48. package/templates/array/difference/index.ts +1 -0
  49. package/templates/array/drop/drop.ts +7 -0
  50. package/templates/array/drop/index.ts +1 -0
  51. package/templates/array/group-by/group-by.ts +18 -0
  52. package/templates/array/group-by/index.ts +1 -0
  53. package/templates/array/intersection/index.ts +1 -0
  54. package/templates/array/intersection/intersection.ts +15 -0
  55. package/templates/array/key-by/index.ts +1 -0
  56. package/templates/array/key-by/key-by.ts +11 -0
  57. package/templates/array/max-by/index.ts +1 -0
  58. package/templates/array/max-by/max-by.ts +20 -0
  59. package/templates/array/min-by/index.ts +1 -0
  60. package/templates/array/min-by/min-by.ts +20 -0
  61. package/templates/array/partition/index.ts +1 -0
  62. package/templates/array/partition/partition.ts +16 -0
  63. package/templates/array/range/index.ts +1 -0
  64. package/templates/array/range/range.ts +22 -0
  65. package/templates/array/sample/index.ts +1 -0
  66. package/templates/array/sample/sample.ts +10 -0
  67. package/templates/array/shuffle/index.ts +1 -0
  68. package/templates/array/shuffle/shuffle.ts +15 -0
  69. package/templates/array/sort-by/index.ts +1 -0
  70. package/templates/array/sort-by/sort-by.ts +32 -0
  71. package/templates/array/sum-by/index.ts +1 -0
  72. package/templates/array/sum-by/sum-by.ts +9 -0
  73. package/templates/array/take/index.ts +1 -0
  74. package/templates/array/take/take.ts +7 -0
  75. package/templates/array/union/index.ts +1 -0
  76. package/templates/array/union/union.ts +5 -0
  77. package/templates/array/unique/index.ts +1 -0
  78. package/templates/array/unique/unique.ts +5 -0
  79. package/templates/array/unique-by/index.ts +1 -0
  80. package/templates/array/unique-by/unique-by.ts +19 -0
  81. package/templates/array/windowed/index.ts +1 -0
  82. package/templates/array/windowed/windowed.ts +15 -0
  83. package/templates/array/zip/index.ts +1 -0
  84. package/templates/array/zip/zip.ts +11 -0
  85. package/templates/boolean/parse-boolean/index.ts +1 -0
  86. package/templates/boolean/parse-boolean/parse-boolean.ts +17 -0
  87. package/templates/boolean/toggle/index.ts +1 -0
  88. package/templates/boolean/toggle/toggle.ts +2 -0
  89. package/templates/boolean/xor/index.ts +1 -0
  90. package/templates/boolean/xor/xor.ts +2 -0
  91. package/templates/data-structure/deque/deque.ts +79 -0
  92. package/templates/data-structure/deque/index.ts +1 -0
  93. package/templates/data-structure/fenwick-tree/fenwick-tree.ts +76 -0
  94. package/templates/data-structure/fenwick-tree/index.ts +1 -0
  95. package/templates/data-structure/heap/heap.ts +88 -0
  96. package/templates/data-structure/heap/index.ts +3 -0
  97. package/templates/data-structure/heap/max-heap.ts +7 -0
  98. package/templates/data-structure/heap/min-heap.ts +7 -0
  99. package/templates/data-structure/priority-queue/index.ts +1 -0
  100. package/templates/data-structure/priority-queue/priority-queue.ts +44 -0
  101. package/templates/data-structure/queue/index.ts +1 -0
  102. package/templates/data-structure/queue/queue.ts +66 -0
  103. package/templates/data-structure/segment-tree/index.ts +1 -0
  104. package/templates/data-structure/segment-tree/segment-tree.ts +79 -0
  105. package/templates/data-structure/trie/index.ts +1 -0
  106. package/templates/data-structure/trie/trie.ts +104 -0
  107. package/templates/data-structure/union-find/index.ts +1 -0
  108. package/templates/data-structure/union-find/union-find.ts +79 -0
  109. package/templates/dom/attribute/attribute.ts +24 -0
  110. package/templates/dom/attribute/index.ts +1 -0
  111. package/templates/dom/class-name/class-name.ts +48 -0
  112. package/templates/dom/class-name/index.ts +1 -0
  113. package/templates/dom/is-element/index.ts +1 -0
  114. package/templates/dom/is-element/is-element.ts +20 -0
  115. package/templates/dom/query/index.ts +1 -0
  116. package/templates/dom/query/query.ts +7 -0
  117. package/templates/http/content-type/content-type.ts +130 -0
  118. package/templates/http/content-type/index.ts +1 -0
  119. package/templates/http/header/header.ts +56 -0
  120. package/templates/http/header/index.ts +1 -0
  121. package/templates/http/method/index.ts +1 -0
  122. package/templates/http/method/method.ts +25 -0
  123. package/templates/http/status/index.ts +1 -0
  124. package/templates/http/status/status.ts +191 -0
  125. package/templates/number/clamp/clamp.ts +11 -0
  126. package/templates/number/clamp/index.ts +1 -0
  127. package/templates/number/factorial/factorial.ts +15 -0
  128. package/templates/number/factorial/index.ts +1 -0
  129. package/templates/number/format-thousands/format-thousands.ts +24 -0
  130. package/templates/number/format-thousands/index.ts +1 -0
  131. package/templates/number/gcd/gcd.ts +13 -0
  132. package/templates/number/gcd/index.ts +1 -0
  133. package/templates/number/in-range/in-range.ts +3 -0
  134. package/templates/number/in-range/index.ts +1 -0
  135. package/templates/number/is-even/index.ts +1 -0
  136. package/templates/number/is-even/is-even.ts +5 -0
  137. package/templates/number/is-odd/index.ts +1 -0
  138. package/templates/number/is-odd/is-odd.ts +7 -0
  139. package/templates/number/lcm/index.ts +1 -0
  140. package/templates/number/lcm/lcm.ts +12 -0
  141. package/templates/number/lerp/index.ts +1 -0
  142. package/templates/number/lerp/lerp.ts +3 -0
  143. package/templates/number/percentage/index.ts +1 -0
  144. package/templates/number/percentage/percentage.ts +7 -0
  145. package/templates/number/random/index.ts +1 -0
  146. package/templates/number/random/random.ts +9 -0
  147. package/templates/number/random-int/index.ts +1 -0
  148. package/templates/number/random-int/random-int.ts +9 -0
  149. package/templates/number/round/index.ts +1 -0
  150. package/templates/number/round/round.ts +22 -0
  151. package/templates/number/safe-parse-number/index.ts +1 -0
  152. package/templates/number/safe-parse-number/safe-parse-number.ts +29 -0
  153. package/templates/number/to-ordinal/index.ts +1 -0
  154. package/templates/number/to-ordinal/to-ordinal.ts +28 -0
  155. package/templates/object/deep-clone/deep-clone.ts +47 -0
  156. package/templates/object/deep-clone/index.ts +1 -0
  157. package/templates/object/deep-equal/deep-equal.ts +66 -0
  158. package/templates/object/deep-equal/index.ts +1 -0
  159. package/templates/object/deep-merge/deep-merge.ts +49 -0
  160. package/templates/object/deep-merge/index.ts +1 -0
  161. package/templates/object/defaults/defaults.ts +14 -0
  162. package/templates/object/defaults/index.ts +1 -0
  163. package/templates/object/filter-object/filter-object.ts +11 -0
  164. package/templates/object/filter-object/index.ts +1 -0
  165. package/templates/object/get/get.ts +75 -0
  166. package/templates/object/get/index.ts +1 -0
  167. package/templates/object/has/has.ts +60 -0
  168. package/templates/object/has/index.ts +1 -0
  169. package/templates/object/invert/index.ts +1 -0
  170. package/templates/object/invert/invert.ts +13 -0
  171. package/templates/object/map-keys/index.ts +1 -0
  172. package/templates/object/map-keys/map-keys.ts +16 -0
  173. package/templates/object/map-values/index.ts +1 -0
  174. package/templates/object/map-values/map-values.ts +14 -0
  175. package/templates/object/omit/index.ts +1 -0
  176. package/templates/object/omit/omit.ts +15 -0
  177. package/templates/object/omit-by/index.ts +1 -0
  178. package/templates/object/omit-by/omit-by.ts +19 -0
  179. package/templates/object/pick/index.ts +1 -0
  180. package/templates/object/pick/pick.ts +15 -0
  181. package/templates/object/pick-by/index.ts +1 -0
  182. package/templates/object/pick-by/pick-by.ts +19 -0
  183. package/templates/object/set/index.ts +1 -0
  184. package/templates/object/set/set.ts +118 -0
  185. package/templates/string/capitalize/capitalize.ts +4 -0
  186. package/templates/string/capitalize/index.ts +1 -0
  187. package/templates/string/change-case/change-case.ts +98 -0
  188. package/templates/string/change-case/index.ts +1 -0
  189. package/templates/string/char/char.ts +14 -0
  190. package/templates/string/char/index.ts +1 -0
  191. package/templates/string/count-occurrences/count-occurrences.ts +20 -0
  192. package/templates/string/count-occurrences/index.ts +1 -0
  193. package/templates/string/escape-html/escape-html.ts +16 -0
  194. package/templates/string/escape-html/index.ts +1 -0
  195. package/templates/string/get-decorated-string/get-decorated-string.ts +7 -0
  196. package/templates/string/get-decorated-string/index.ts +1 -0
  197. package/templates/string/is-blank/index.ts +1 -0
  198. package/templates/string/is-blank/is-blank.ts +6 -0
  199. package/templates/string/mask/index.ts +1 -0
  200. package/templates/string/mask/mask.ts +30 -0
  201. package/templates/string/pad/index.ts +1 -0
  202. package/templates/string/pad/pad.ts +17 -0
  203. package/templates/string/random-string/index.ts +1 -0
  204. package/templates/string/random-string/random-string.ts +20 -0
  205. package/templates/string/reverse/index.ts +1 -0
  206. package/templates/string/reverse/reverse.ts +6 -0
  207. package/templates/string/slugify/index.ts +1 -0
  208. package/templates/string/slugify/slugify.ts +11 -0
  209. package/templates/string/strip-tags/index.ts +1 -0
  210. package/templates/string/strip-tags/strip-tags.ts +8 -0
  211. package/templates/string/template/index.ts +1 -0
  212. package/templates/string/template/template.ts +14 -0
  213. package/templates/string/truncate/index.ts +1 -0
  214. package/templates/string/truncate/truncate.ts +17 -0
  215. package/templates/string/truncate-middle/index.ts +1 -0
  216. package/templates/string/truncate-middle/truncate-middle.ts +25 -0
  217. package/templates/string/uuid/index.ts +1 -0
  218. package/templates/string/uuid/uuid.ts +9 -0
  219. package/templates/tailwindcss/class-variant/class-variant.ts +35 -0
  220. package/templates/tailwindcss/class-variant/index.ts +1 -0
  221. package/templates/tailwindcss/cn/cn.ts +29 -0
  222. package/templates/tailwindcss/cn/index.ts +1 -0
  223. package/templates/tailwindcss/create-tailwind-css/create-tailwind-css.ts +16 -0
  224. package/templates/tailwindcss/create-tailwind-css/index.ts +1 -0
  225. package/templates/tailwindcss/tw-merge-config/index.ts +1 -0
  226. package/templates/tailwindcss/tw-merge-config/tw-merge-config.ts +14 -0
  227. package/templates/type/get-type/get-type.ts +27 -0
  228. package/templates/type/get-type/index.ts +1 -0
  229. package/templates/type/is-type-of/index.ts +1 -0
  230. package/templates/type/is-type-of/is-type-of.ts +26 -0
  231. package/templates/util/mutex/index.ts +1 -0
  232. package/templates/util/mutex/mutex.ts +109 -0
  233. package/templates/util/url-parser/index.ts +1 -0
  234. package/templates/util/url-parser/url-parser.ts +44 -0
@@ -0,0 +1 @@
1
+ export * from "./defaults";
@@ -0,0 +1,11 @@
1
+ import { type TypeofResult, getType } from "../../type/get-type/index";
2
+
3
+ export const filterObject = (obj: Record<string, unknown>, filterTypes: Set<TypeofResult>) => {
4
+ const entries = Object.entries(obj).filter(([key, value]) => {
5
+ const type = getType(value);
6
+
7
+ return !filterTypes.has(type);
8
+ });
9
+
10
+ return Object.fromEntries(entries);
11
+ };
@@ -0,0 +1 @@
1
+ export * from "./filter-object";
@@ -0,0 +1,75 @@
1
+ // 경로 문자열로 중첩 객체/배열에 안전 접근하는 유틸.
2
+ // "a.b[0].c" 같은 경로를 키 목록 ["a", "b", "0", "c"]로 쪼갠 뒤 한 단계씩 내려간다.
3
+ // 중간에 null/undefined나 인덱싱 불가능한 값을 만나면 즉시 defaultValue로 빠진다.
4
+
5
+ // "a.b[0].c" -> ["a", "b", "0", "c"]
6
+ // 점 구분과 대괄호 인덱스를 모두 받는다. 대괄호 안 키(숫자/문자 모두)는 한 세그먼트로 본다.
7
+ // 빈 세그먼트(예: 선행 점, 연속 점)는 버려서 "" 키로 잘못 내려가지 않게 한다.
8
+ const parsePath = (path: string): string[] => {
9
+ const segments: string[] = [];
10
+ let current = "";
11
+
12
+ // [start, end) 구간에 모인 키를 확정한다. 빈 문자열은 버린다.
13
+ const flush = (): void => {
14
+ if (current.length > 0) {
15
+ segments.push(current);
16
+ current = "";
17
+ }
18
+ };
19
+
20
+ for (let i = 0; i < path.length; i += 1) {
21
+ const char = path[i];
22
+
23
+ if (char === ".") {
24
+ flush();
25
+ continue;
26
+ }
27
+
28
+ // 대괄호 시작: 직전까지의 키를 끊고, 닫는 괄호 전까지를 다음 키로 모은다.
29
+ if (char === "[") {
30
+ flush();
31
+ continue;
32
+ }
33
+
34
+ if (char === "]") {
35
+ flush();
36
+ continue;
37
+ }
38
+
39
+ current += char;
40
+ }
41
+
42
+ flush();
43
+
44
+ return segments;
45
+ };
46
+
47
+ // 값이 키로 인덱싱 가능한 객체/배열인지 좁힌다. null도 typeof로는 "object"라 따로 거른다.
48
+ const isIndexable = (value: unknown): value is Record<string, unknown> => {
49
+ return value !== null && (typeof value === "object" || typeof value === "function");
50
+ };
51
+
52
+ /**
53
+ * "a.b[0].c" 경로로 중첩 값을 안전하게 읽는다.
54
+ * 경로가 끊기면(중간이 null/undefined이거나 키가 없으면) defaultValue를 반환하고,
55
+ * defaultValue가 없으면 undefined를 반환한다.
56
+ *
57
+ * 입력 obj는 unknown으로 받아 어떤 값이든 던지지 않고 처리한다.
58
+ * 반환 타입은 호출부가 지정한 T로 단언한다(런타임 보장은 경로 존재 여부까지다).
59
+ */
60
+ export const get = <T = unknown>(obj: unknown, path: string, defaultValue?: T): T => {
61
+ const segments = parsePath(path);
62
+
63
+ let cursor: unknown = obj;
64
+ for (const key of segments) {
65
+ if (!isIndexable(cursor)) {
66
+ return defaultValue as T;
67
+ }
68
+
69
+ cursor = cursor[key];
70
+ }
71
+
72
+ // 경로 끝까지 내려갔는데 값이 undefined면 "없음"으로 보고 defaultValue로 대체한다.
73
+ // (null은 명시적으로 들어있던 값이므로 그대로 돌려준다.)
74
+ return cursor === undefined ? (defaultValue as T) : (cursor as T);
75
+ };
@@ -0,0 +1 @@
1
+ export * from "./get";
@@ -0,0 +1,60 @@
1
+ // 경로 문자열로 중첩 객체/배열에 해당 키가 "존재하는지"만 판정하는 유틸.
2
+ // get과 달리 값 자체(undefined/null 여부)는 보지 않고, 키 소유 여부만 본다.
3
+ // 즉 { a: undefined }에서 has(obj, "a")는 true다.
4
+
5
+ // "a.b[0].c" -> ["a", "b", "0", "c"]
6
+ // 점 구분과 대괄호 인덱스를 모두 받는다. 빈 세그먼트는 버린다.
7
+ const parsePath = (path: string): string[] => {
8
+ const segments: string[] = [];
9
+ let current = "";
10
+
11
+ const flush = (): void => {
12
+ if (current.length > 0) {
13
+ segments.push(current);
14
+ current = "";
15
+ }
16
+ };
17
+
18
+ for (let i = 0; i < path.length; i += 1) {
19
+ const char = path[i];
20
+
21
+ if (char === "." || char === "[" || char === "]") {
22
+ flush();
23
+ continue;
24
+ }
25
+
26
+ current += char;
27
+ }
28
+
29
+ flush();
30
+
31
+ return segments;
32
+ };
33
+
34
+ // 값이 키 소유 여부를 물어볼 수 있는 객체/배열인지 좁힌다. null은 거른다.
35
+ const isIndexable = (value: unknown): value is Record<string, unknown> => {
36
+ return value !== null && (typeof value === "object" || typeof value === "function");
37
+ };
38
+
39
+ /**
40
+ * "a.b[0]" 경로가 존재하는지 판정한다.
41
+ * 각 단계에서 현재 값이 인덱싱 가능한 객체이고 해당 키를 소유하면(Object.hasOwn) 다음으로 내려간다.
42
+ * 중간이라도 키가 없거나 인덱싱 불가능한 값을 만나면 false.
43
+ *
44
+ * 키 존재만 보므로 값이 undefined여도(예: { a: undefined }) 키를 소유하면 true다.
45
+ * 빈 경로("")는 세그먼트가 없어 항상 true(루트 자신은 존재).
46
+ */
47
+ export const has = (obj: unknown, path: string): boolean => {
48
+ const segments = parsePath(path);
49
+
50
+ let cursor: unknown = obj;
51
+ for (const key of segments) {
52
+ if (!isIndexable(cursor) || !Object.hasOwn(cursor, key)) {
53
+ return false;
54
+ }
55
+
56
+ cursor = cursor[key];
57
+ }
58
+
59
+ return true;
60
+ };
@@ -0,0 +1 @@
1
+ export * from "./has";
@@ -0,0 +1 @@
1
+ export * from "./invert";
@@ -0,0 +1,13 @@
1
+ // 객체의 키와 값을 뒤집은 새 객체를 만든다. 원래 값이 새 키가 되고,
2
+ // 원래 키가 새 값이 된다. 객체 키는 항상 문자열로 강제되므로 결과는
3
+ // Record<string, string>이다. 서로 다른 키가 같은 값을 가리키면(값 충돌)
4
+ // 등장 순서상 마지막 키가 우선한다. 입력 객체는 변형하지 않는다.
5
+ export const invert = <T extends Record<PropertyKey, PropertyKey>>(obj: T): Record<string, string> => {
6
+ const result: Record<string, string> = {};
7
+
8
+ for (const key of Object.keys(obj)) {
9
+ result[String(obj[key])] = key;
10
+ }
11
+
12
+ return result;
13
+ };
@@ -0,0 +1 @@
1
+ export * from "./map-keys";
@@ -0,0 +1,16 @@
1
+ // 객체의 각 키를 keyFn으로 변환한 새 객체를 만든다. 값은 그대로 옮겨진다.
2
+ // keyFn이 같은 키를 여러 번 돌려주면 나중 항목이 우선한다.
3
+ // 자기 소유(own) 열거 가능 키만 다루며, 입력 객체는 변형하지 않는다.
4
+ export const mapKeys = <T extends object, K extends PropertyKey>(
5
+ obj: T,
6
+ keyFn: (key: keyof T, value: T[keyof T]) => K,
7
+ ): Record<K, T[keyof T]> => {
8
+ const result = {} as Record<K, T[keyof T]>;
9
+
10
+ for (const key of Object.keys(obj) as (keyof T)[]) {
11
+ const value = obj[key];
12
+ result[keyFn(key, value)] = value;
13
+ }
14
+
15
+ return result;
16
+ };
@@ -0,0 +1 @@
1
+ export * from "./map-values";
@@ -0,0 +1,14 @@
1
+ // 객체의 각 값을 valueFn으로 변환한 새 객체를 만든다. 키는 그대로 보존된다.
2
+ // 자기 소유(own) 열거 가능 키만 다루며, 입력 객체는 변형하지 않는다.
3
+ export const mapValues = <T extends object, V>(
4
+ obj: T,
5
+ valueFn: (value: T[keyof T], key: keyof T) => V,
6
+ ): Record<keyof T, V> => {
7
+ const result = {} as Record<keyof T, V>;
8
+
9
+ for (const key of Object.keys(obj) as (keyof T)[]) {
10
+ result[key] = valueFn(obj[key], key);
11
+ }
12
+
13
+ return result;
14
+ };
@@ -0,0 +1 @@
1
+ export * from "./omit";
@@ -0,0 +1,15 @@
1
+ // 객체에서 지정한 키를 제외한 새 객체를 만드는 유틸.
2
+ // 원본을 변형하지 않고, 제외할 키를 Set으로 모아 나머지 엔트리만 얕게 복사한다.
3
+ // 자기 자신의 열거 가능 키만 순회하므로 상속 속성은 결과에 섞이지 않는다.
4
+ export const omit = <T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> => {
5
+ const excluded = new Set<PropertyKey>(keys);
6
+ const result = {} as Record<PropertyKey, unknown>;
7
+
8
+ for (const key of Object.keys(obj) as (keyof T)[]) {
9
+ if (!excluded.has(key)) {
10
+ result[key] = obj[key];
11
+ }
12
+ }
13
+
14
+ return result as Omit<T, K>;
15
+ };
@@ -0,0 +1 @@
1
+ export * from "./omit-by";
@@ -0,0 +1,19 @@
1
+ // predicate를 통과한 엔트리를 제외한 새 객체를 만드는 유틸.
2
+ // `pickBy`의 반대 동작이다: predicate가 참인 엔트리를 버리고 나머지를 남긴다.
3
+ // 키가 아니라 값/키 조건으로 거르므로 반환 타입은 Partial<T>다.
4
+ // 원본은 변형하지 않고 남은 엔트리만 얕게 복사한다.
5
+ export const omitBy = <T extends object>(
6
+ obj: T,
7
+ predicate: (value: T[keyof T], key: keyof T) => boolean,
8
+ ): Partial<T> => {
9
+ const result: Partial<T> = {};
10
+
11
+ for (const key of Object.keys(obj) as (keyof T)[]) {
12
+ const value = obj[key];
13
+ if (!predicate(value, key)) {
14
+ result[key] = value;
15
+ }
16
+ }
17
+
18
+ return result;
19
+ };
@@ -0,0 +1 @@
1
+ export * from "./pick";
@@ -0,0 +1,15 @@
1
+ // 객체에서 지정한 키만 골라 새 객체를 만드는 유틸.
2
+ // 원본을 변형하지 않고 얕은 복사로 선택된 엔트리만 담는다.
3
+ // keys에 들어온 키라도 객체에 실제로 없으면(상속 속성 등) 건너뛰어,
4
+ // 결과에 undefined 값 엔트리가 새로 생기지 않게 한다.
5
+ export const pick = <T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> => {
6
+ const result = {} as Pick<T, K>;
7
+
8
+ for (const key of keys) {
9
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
10
+ result[key] = obj[key];
11
+ }
12
+ }
13
+
14
+ return result;
15
+ };
@@ -0,0 +1 @@
1
+ export * from "./pick-by";
@@ -0,0 +1,19 @@
1
+ // predicate를 통과한 엔트리만 골라 새 객체를 만드는 유틸.
2
+ // 키가 아니라 값/키 조건으로 거르므로 어떤 키가 남을지 정적으로 알 수 없다.
3
+ // 그래서 반환 타입은 Partial<T>다 (모든 키가 빠질 수 있다).
4
+ // 원본은 변형하지 않고 통과한 엔트리만 얕게 복사한다.
5
+ export const pickBy = <T extends object>(
6
+ obj: T,
7
+ predicate: (value: T[keyof T], key: keyof T) => boolean,
8
+ ): Partial<T> => {
9
+ const result: Partial<T> = {};
10
+
11
+ for (const key of Object.keys(obj) as (keyof T)[]) {
12
+ const value = obj[key];
13
+ if (predicate(value, key)) {
14
+ result[key] = value;
15
+ }
16
+ }
17
+
18
+ return result;
19
+ };
@@ -0,0 +1 @@
1
+ export * from "./set";
@@ -0,0 +1,118 @@
1
+ // 경로 문자열 위치에 값을 설정한 "새" 객체를 반환하는 불변(immutable) 유틸.
2
+ // 입력 obj는 변형하지 않고, 경로상에 놓인 객체/배열만 얕은 복사로 갈아끼운다(경로 밖 가지는 공유).
3
+ // 중간 경로가 없으면 만들어 채우는데, 다음 세그먼트가 배열 인덱스면 배열을, 아니면 객체를 만든다.
4
+
5
+ // 경로 세그먼트. 키 문자열과 "이 위치가 배열 인덱스인지"를 함께 들고 다닌다.
6
+ // 인덱스 여부로 (1) 비어있는 중간 경로를 배열로 만들지 객체로 만들지, (2) 인덱스를 number로 쓸지를 정한다.
7
+ interface Segment {
8
+ readonly key: string;
9
+ readonly isIndex: boolean;
10
+ }
11
+
12
+ // "a.b[0].c" -> [a, b, 0(index), c]
13
+ // 점 뒤 키는 일반 키, 대괄호 안 키는 인덱스로 표시한다.
14
+ // 점 표기라도 순수 숫자 문자열이면 인덱스로 본다(예: "a.0.b"의 0).
15
+ const parsePath = (path: string): Segment[] => {
16
+ const segments: Segment[] = [];
17
+ let current = "";
18
+ // 지금 모으는 키가 대괄호 안에서 시작했는지. 그러면 무조건 인덱스로 본다.
19
+ let inBracket = false;
20
+
21
+ // 모인 키를 세그먼트로 확정한다. 빈 키는 버린다.
22
+ const flush = (): void => {
23
+ if (current.length > 0) {
24
+ // 대괄호 안이었거나, 점 표기라도 순수 정수 문자열이면 인덱스로 취급한다.
25
+ const isIndex = inBracket || /^\d+$/.test(current);
26
+ segments.push({ key: current, isIndex });
27
+ current = "";
28
+ }
29
+ inBracket = false;
30
+ };
31
+
32
+ for (let i = 0; i < path.length; i += 1) {
33
+ const char = path[i];
34
+
35
+ if (char === ".") {
36
+ flush();
37
+ continue;
38
+ }
39
+
40
+ if (char === "[") {
41
+ flush();
42
+ inBracket = true;
43
+ continue;
44
+ }
45
+
46
+ if (char === "]") {
47
+ flush();
48
+ continue;
49
+ }
50
+
51
+ current += char;
52
+ }
53
+
54
+ flush();
55
+
56
+ return segments;
57
+ };
58
+
59
+ // 값이 우리가 얕은 복사로 갈아끼울 수 있는 평범한 객체/배열인지 좁힌다. null은 거른다.
60
+ const isContainer = (value: unknown): value is Record<string, unknown> | unknown[] => {
61
+ return value !== null && typeof value === "object";
62
+ };
63
+
64
+ // 다음 세그먼트의 인덱스 여부에 맞춰, 자리에 둘 빈 컨테이너를 고른다.
65
+ const emptyContainerFor = (segment: Segment): Record<string, unknown> | unknown[] => {
66
+ return segment.isIndex ? [] : {};
67
+ };
68
+
69
+ // 컨테이너를 한 단계 얕은 복사한다. 배열은 배열로, 객체는 객체로 보존한다.
70
+ const shallowClone = (value: Record<string, unknown> | unknown[]): Record<string, unknown> | unknown[] => {
71
+ return Array.isArray(value) ? value.slice() : { ...value };
72
+ };
73
+
74
+ // segments[depth]부터 재귀로 내려가며 복사본을 만들고, 마지막 위치에 value를 박는다.
75
+ // node는 "현재 깊이에서 들어있던 값"(없을 수도 있어 unknown). 항상 새 컨테이너를 반환한다.
76
+ const setIn = (
77
+ node: unknown,
78
+ segments: Segment[],
79
+ depth: number,
80
+ value: unknown,
81
+ ): Record<string, unknown> | unknown[] => {
82
+ const segment = segments[depth];
83
+ const isLast = depth === segments.length - 1;
84
+
85
+ // 현재 자리에 컨테이너가 있으면 복사해 쓰고, 없으면 다음 세그먼트 종류에 맞는 빈 컨테이너를 만든다.
86
+ // (배열 자리에 객체가 있는 식의 타입 불일치도 새 컨테이너로 교체해 일관성을 맞춘다.)
87
+ const base =
88
+ isContainer(node) && Array.isArray(node) === segment.isIndex ? shallowClone(node) : emptyContainerFor(segment);
89
+
90
+ // 배열이면 number 인덱스로, 객체면 string 키로 쓴다.
91
+ // (Array를 string 키로 인덱싱해도 동작하지만, 인덱스는 number로 다뤄 길이/희소 배열 의미를 살린다.)
92
+ if (Array.isArray(base)) {
93
+ const index = Number(segment.key);
94
+ base[index] = isLast ? value : setIn(base[index], segments, depth + 1, value);
95
+ return base;
96
+ }
97
+
98
+ base[segment.key] = isLast ? value : setIn(base[segment.key], segments, depth + 1, value);
99
+ return base;
100
+ };
101
+
102
+ /**
103
+ * "a.b[0].c" 경로에 value를 설정한 새 객체를 반환한다(입력 obj 불변).
104
+ * 경로상의 컨테이너만 얕은 복사로 교체하고, 경로 밖 가지는 원본과 그대로 공유한다.
105
+ * 중간 경로가 없으면 만들며, 다음 세그먼트가 인덱스면 배열을, 아니면 객체를 생성한다.
106
+ *
107
+ * 빈 경로("")면 설정할 위치가 없으므로 입력을 얕은 복사해 그대로 돌려준다.
108
+ * 반환 타입은 입력과 같은 T로 단언한다(경로 생성으로 구조가 넓어질 수 있음은 호출부 책임).
109
+ */
110
+ export const set = <T extends object>(obj: T, path: string, value: unknown): T => {
111
+ const segments = parsePath(path);
112
+
113
+ if (segments.length === 0) {
114
+ return shallowClone(obj as Record<string, unknown> | unknown[]) as T;
115
+ }
116
+
117
+ return setIn(obj, segments, 0, value) as T;
118
+ };
@@ -0,0 +1,4 @@
1
+ // 첫 글자만 대문자로 만들고 나머지는 그대로 둔다. 빈 문자열은 빈 문자열 그대로.
2
+ export const capitalize = (word: string): string => {
3
+ return word.charAt(0).toUpperCase() + word.slice(1);
4
+ };
@@ -0,0 +1 @@
1
+ export * from "./capitalize";
@@ -0,0 +1,98 @@
1
+ import { capitalize } from "../capitalize/index";
2
+ import { isAsciiDigit, isAsciiLower, isAsciiUpper } from "../char/index";
3
+
4
+ // 케이스 변환 유틸. splitWords가 어떤 케이스든 소문자 단어 배열로 쪼개고,
5
+ // toXxxCase 조립기가 타깃 케이스로 다시 합친다.
6
+ // 이 구조라 케이스 간 변환이 N×N 조합 없이 전부 동작한다 (kebab->Pascal, snake->camel, ...).
7
+
8
+ // 단어를 끊는 구분자. 공백류와 `.` `_` `-`. (원본 정규식의 `[\s._-]`와 동일한 문자 집합)
9
+ // 비ASCII 글자는 여기 안 걸리므로 단어 안에 그대로 보존된다 (글자를 흘리지 않는다).
10
+ const SEPARATOR = /[\s._-]/;
11
+
12
+ /**
13
+ * 어떤 케이스의 문자열이든 소문자 단어 배열로 쪼갠다.
14
+ * 모든 toXxxCase가 이 결과를 조립하므로, 이 함수의 분할 규칙이 곧 변환 전체의 동작을 정한다.
15
+ *
16
+ * 정규식 체인(replace x2 -> split -> filter -> map) 대신 문자 단위 단일 패스로 구현했다.
17
+ * core 라이브러리라 길거나 신뢰할 수 없는 입력이 들어올 수 있는데,
18
+ * 정규식 `([A-Z]+)([A-Z][a-z])`는 긴 대문자 런에서 O(n^2) 백트래킹이 난다.
19
+ * 단일 패스는 O(n)이고 중간 문자열/배열 할당도 없다.
20
+ *
21
+ * 새 단어가 시작되는 경계는 둘 중 하나다 (대문자 A-Z에서만 판정):
22
+ * 1. 소문자/숫자 다음의 대문자 : "fooBar" -> foo | Bar
23
+ * 2. 대문자 런의 끝(= 다음이 소문자) : "HTMLParser" -> HTML | Parser
24
+ * 구분자(SEPARATOR)는 단어를 끊고 결과에서 빠진다.
25
+ * 머리글자 묶음은 단어로 쪼갠 뒤 소문자로 정규화된다 ("HTMLParser" -> ["html", "parser"]).
26
+ */
27
+ export const splitWords = (input: string): string[] => {
28
+ const words: string[] = [];
29
+ // 현재 모으는 중인 단어의 시작 인덱스. 단어 밖이면 -1.
30
+ let start = -1;
31
+
32
+ // [start, end) 구간을 한 단어로 확정한다.
33
+ const flush = (end: number): void => {
34
+ if (start >= 0) {
35
+ words.push(input.slice(start, end).toLowerCase());
36
+ start = -1;
37
+ }
38
+ };
39
+
40
+ for (let i = 0; i < input.length; i += 1) {
41
+ const char = input[i];
42
+
43
+ // 구분자: 진행 중이던 단어를 끊고 이 문자 자체는 버린다.
44
+ if (SEPARATOR.test(char)) {
45
+ flush(i);
46
+ continue;
47
+ }
48
+
49
+ // 단어의 첫 글자: 시작점만 잡고 넘어간다 (경계 판정 대상 아님).
50
+ if (start < 0) {
51
+ start = i;
52
+ continue;
53
+ }
54
+
55
+ // 단어 중간의 대문자만 새 단어의 경계가 될 수 있다.
56
+ if (isAsciiUpper(char)) {
57
+ const prev = input[i - 1];
58
+ const next = input[i + 1];
59
+ // 규칙 1: "fooBar" 의 B — 소문자/숫자 바로 뒤의 대문자
60
+ const afterLowerOrDigit = isAsciiLower(prev) || isAsciiDigit(prev);
61
+ // 규칙 2: "HTMLParser" 의 P — 대문자 런의 마지막(다음 글자가 소문자)
62
+ const acronymThenWord = isAsciiUpper(prev) && isAsciiLower(next);
63
+ if (afterLowerOrDigit || acronymThenWord) {
64
+ flush(i);
65
+ start = i;
66
+ }
67
+ }
68
+ }
69
+
70
+ // 마지막 단어 확정.
71
+ flush(input.length);
72
+
73
+ return words;
74
+ };
75
+
76
+ export const toCamelCase = (input: string): string => {
77
+ return splitWords(input)
78
+ .map((word, index) => (index === 0 ? word : capitalize(word)))
79
+ .join("");
80
+ };
81
+
82
+ export const toPascalCase = (input: string): string => {
83
+ return splitWords(input).map(capitalize).join("");
84
+ };
85
+
86
+ export const toKebabCase = (input: string): string => {
87
+ return splitWords(input).join("-");
88
+ };
89
+
90
+ export const toSnakeCase = (input: string): string => {
91
+ return splitWords(input).join("_");
92
+ };
93
+
94
+ export const toConstantCase = (input: string): string => {
95
+ return splitWords(input)
96
+ .map((word) => word.toUpperCase())
97
+ .join("_");
98
+ };
@@ -0,0 +1 @@
1
+ export * from "./change-case";
@@ -0,0 +1,14 @@
1
+ // ASCII 문자 분류 술어. 단일 문자를 받아 종류를 판별한다.
2
+ // 로캘/유니코드 영향 없이 항상 같은 결과를 주는 결정론적 분류용이라,
3
+ // 파싱·토크나이즈 같은 곳에서 정규식보다 가볍게 재사용한다.
4
+ // (빈 자리나 범위 밖 문자에는 false를 돌려줘 경계에서 안전하다.)
5
+
6
+ export const isAsciiUpper = (char: string): boolean => char >= "A" && char <= "Z";
7
+
8
+ export const isAsciiLower = (char: string): boolean => char >= "a" && char <= "z";
9
+
10
+ export const isAsciiDigit = (char: string): boolean => char >= "0" && char <= "9";
11
+
12
+ export const isAsciiLetter = (char: string): boolean => isAsciiUpper(char) || isAsciiLower(char);
13
+
14
+ export const isAsciiAlphanumeric = (char: string): boolean => isAsciiLetter(char) || isAsciiDigit(char);
@@ -0,0 +1 @@
1
+ export * from "./char";
@@ -0,0 +1,20 @@
1
+ // str 안에서 search가 겹치지 않게 등장하는 횟수를 센다. 입력은 변형하지 않는다.
2
+ // search가 ""이면 0을 반환한다 (빈 문자열은 모든 위치에 무한히 매칭되므로 정의상 제외).
3
+ //
4
+ // 매칭 후 인덱스를 search.length만큼 건너뛰어 겹침을 막는다.
5
+ // 예: countOccurrences("aaaa", "aa")는 [0..2)와 [2..4) 두 번 -> 2 (1,2,3에서 다시 세지 않음).
6
+ export const countOccurrences = (str: string, search: string): number => {
7
+ if (search === "") {
8
+ return 0;
9
+ }
10
+
11
+ let count = 0;
12
+ let index = str.indexOf(search);
13
+
14
+ while (index !== -1) {
15
+ count += 1;
16
+ index = str.indexOf(search, index + search.length);
17
+ }
18
+
19
+ return count;
20
+ };
@@ -0,0 +1 @@
1
+ export * from "./count-occurrences";
@@ -0,0 +1,16 @@
1
+ // HTML 특수문자를 엔티티로 치환한다. 텍스트를 마크업에 안전하게 끼워 넣을 때 쓴다.
2
+ // 입력은 변형하지 않고 새 문자열을 반환한다.
3
+ //
4
+ // `&`를 가장 먼저 치환해야 한다. 뒤에 치환하면 이미 만든 엔티티의 `&`까지
5
+ // 다시 `&amp;`로 이중 치환된다(`<` -> `&lt;` -> `&amp;lt;`). 그래서 순서가 중요하다.
6
+ const HTML_ENTITIES: Record<string, string> = {
7
+ "&": "&amp;",
8
+ "<": "&lt;",
9
+ ">": "&gt;",
10
+ '"': "&quot;",
11
+ "'": "&#39;",
12
+ };
13
+
14
+ export const escapeHtml = (str: string): string => {
15
+ return str.replace(/[&<>"']/g, (char) => HTML_ENTITIES[char]);
16
+ };
@@ -0,0 +1 @@
1
+ export * from "./escape-html";
@@ -0,0 +1,7 @@
1
+ const selection = "___FORMAT___";
2
+
3
+ export const getDecoratedString = (target: string, toBe: string) => {
4
+ return toBe.replace(new RegExp(selection, "g"), target);
5
+ };
6
+
7
+ getDecoratedString.selection = selection;
@@ -0,0 +1 @@
1
+ export * from "./get-decorated-string";
@@ -0,0 +1 @@
1
+ export * from "./is-blank";
@@ -0,0 +1,6 @@
1
+ // 빈 문자열이거나 공백 문자만 있으면 true. 입력은 변형하지 않는다.
2
+ // `trim()`이 양 끝의 공백류(스페이스, 탭, 개행, NBSP 등 유니코드 공백)를 모두
3
+ // 제거하므로, 전부 공백이면 결과가 ""가 된다. 따라서 trim 결과의 빈 여부 한 번으로 판정된다.
4
+ export const isBlank = (str: string): boolean => {
5
+ return str.trim() === "";
6
+ };
@@ -0,0 +1 @@
1
+ export * from "./mask";