@planet-matrix/mobius-model 0.3.0 → 0.4.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/CHANGELOG.md +7 -0
- package/README.md +4 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +18 -3
- package/package.json +3 -3
- package/scripts/build.ts +4 -4
- package/src/basic/README.md +143 -0
- package/src/basic/array.ts +872 -0
- package/src/basic/bigint.ts +114 -0
- package/src/basic/boolean.ts +180 -0
- package/src/basic/error.ts +51 -0
- package/src/basic/function.ts +453 -0
- package/src/basic/helper.ts +276 -0
- package/src/basic/index.ts +15 -0
- package/src/basic/is.ts +320 -0
- package/src/basic/number.ts +178 -0
- package/src/basic/object.ts +58 -0
- package/src/basic/promise.ts +464 -0
- package/src/basic/regexp.ts +7 -0
- package/src/basic/stream.ts +140 -0
- package/src/basic/string.ts +308 -0
- package/src/basic/symbol.ts +164 -0
- package/src/basic/temporal.ts +224 -0
- package/src/index.ts +2 -0
- package/src/type/README.md +330 -0
- package/src/type/array.ts +5 -0
- package/src/type/boolean.ts +471 -0
- package/src/type/class.ts +419 -0
- package/src/type/function.ts +1519 -0
- package/src/type/helper.ts +135 -0
- package/src/type/index.ts +14 -0
- package/src/type/intersection.ts +93 -0
- package/src/type/is.ts +247 -0
- package/src/type/iteration.ts +233 -0
- package/src/type/number.ts +732 -0
- package/src/type/object.ts +788 -0
- package/src/type/path.ts +73 -0
- package/src/type/string.ts +1004 -0
- package/src/type/tuple.ts +2424 -0
- package/src/type/union.ts +108 -0
- package/tests/unit/basic/array.spec.ts +290 -0
- package/tests/unit/basic/bigint.spec.ts +50 -0
- package/tests/unit/basic/boolean.spec.ts +74 -0
- package/tests/unit/basic/error.spec.ts +32 -0
- package/tests/unit/basic/function.spec.ts +175 -0
- package/tests/unit/basic/helper.spec.ts +118 -0
- package/tests/unit/basic/number.spec.ts +74 -0
- package/tests/unit/basic/object.spec.ts +15 -0
- package/tests/unit/basic/promise.spec.ts +232 -0
- package/tests/unit/basic/regexp.spec.ts +11 -0
- package/tests/unit/basic/stream.spec.ts +120 -0
- package/tests/unit/basic/string.spec.ts +74 -0
- package/tests/unit/basic/symbol.spec.ts +72 -0
- package/tests/unit/basic/temporal.spec.ts +78 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/reactor/index.d.ts +0 -3
- package/dist/reactor/index.d.ts.map +0 -1
- package/dist/reactor/reactor-core/flags.d.ts +0 -99
- package/dist/reactor/reactor-core/flags.d.ts.map +0 -1
- package/dist/reactor/reactor-core/index.d.ts +0 -4
- package/dist/reactor/reactor-core/index.d.ts.map +0 -1
- package/dist/reactor/reactor-core/primitive.d.ts +0 -276
- package/dist/reactor/reactor-core/primitive.d.ts.map +0 -1
- package/dist/reactor/reactor-core/reactive-system.d.ts +0 -241
- package/dist/reactor/reactor-core/reactive-system.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/branch.d.ts +0 -19
- package/dist/reactor/reactor-operators/branch.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/convert.d.ts +0 -30
- package/dist/reactor/reactor-operators/convert.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/create.d.ts +0 -26
- package/dist/reactor/reactor-operators/create.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/filter.d.ts +0 -269
- package/dist/reactor/reactor-operators/filter.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/index.d.ts +0 -8
- package/dist/reactor/reactor-operators/index.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/join.d.ts +0 -48
- package/dist/reactor/reactor-operators/join.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/map.d.ts +0 -165
- package/dist/reactor/reactor-operators/map.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/utility.d.ts +0 -48
- package/dist/reactor/reactor-operators/utility.d.ts.map +0 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { ReadableStreamController, ReadableStreamReadResult } from "node:stream/web"
|
|
2
|
+
|
|
3
|
+
import { ReadableStream } from "node:stream/web"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 从数组创建一个 ReadableStream。
|
|
7
|
+
*/
|
|
8
|
+
export const streamFromArray = <T>(values: T[]): ReadableStream<T> => {
|
|
9
|
+
const stream = new ReadableStream<T>({
|
|
10
|
+
start(controller): void {
|
|
11
|
+
for (const value of values) {
|
|
12
|
+
controller.enqueue(value)
|
|
13
|
+
}
|
|
14
|
+
controller.close()
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
return stream
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface StreamConsumeInSyncMacroTaskOptions<T> {
|
|
21
|
+
readableStream: ReadableStream<T>
|
|
22
|
+
onValue?: ((chunk: T) => (void | Promise<void>)) | undefined
|
|
23
|
+
onDone?: (() => (void | Promise<void>)) | undefined
|
|
24
|
+
onError?: ((error: Error) => (void | Promise<void>)) | undefined
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 在同步宏任务中完成 ReadableStream 的消费。
|
|
28
|
+
*/
|
|
29
|
+
export const streamConsumeInSyncMacroTask = async <T>(
|
|
30
|
+
options: StreamConsumeInSyncMacroTaskOptions<T>,
|
|
31
|
+
): Promise<void> => {
|
|
32
|
+
const { readableStream, onValue, onDone, onError } = options
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
for await (const chunk of readableStream) {
|
|
36
|
+
await onValue?.(chunk)
|
|
37
|
+
}
|
|
38
|
+
await onDone?.()
|
|
39
|
+
}
|
|
40
|
+
catch (exception) {
|
|
41
|
+
console.error(`Error reading stream: ${String(exception)}`)
|
|
42
|
+
await onError?.(new Error(`Error reading stream: ${String(exception)}`))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface StreamConsumeInAsyncMacroTaskOptions<T> {
|
|
47
|
+
readableStream: ReadableStream<T>
|
|
48
|
+
onValue?: ((chunk: T) => (void | Promise<void>)) | undefined
|
|
49
|
+
onDone?: (() => (void | Promise<void>)) | undefined
|
|
50
|
+
onError?: ((error: Error) => (void | Promise<void>)) | undefined
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 在异步宏任务中完成 ReadableStream 的消费。
|
|
54
|
+
*/
|
|
55
|
+
export const streamConsumeInAsyncMacroTask = <T>(
|
|
56
|
+
options: StreamConsumeInAsyncMacroTaskOptions<T>,
|
|
57
|
+
): void => {
|
|
58
|
+
const { readableStream, onValue, onDone, onError } = options
|
|
59
|
+
|
|
60
|
+
const reader = readableStream.getReader()
|
|
61
|
+
const read = (): void => {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
void reader.read()
|
|
64
|
+
.then(async (chunk) => {
|
|
65
|
+
const { done, value } = chunk
|
|
66
|
+
if (done === true) {
|
|
67
|
+
await onDone?.()
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
await onValue?.(value)
|
|
71
|
+
read()
|
|
72
|
+
})
|
|
73
|
+
.catch(async (reason: unknown) => {
|
|
74
|
+
await onError?.(new Error(String(reason)))
|
|
75
|
+
})
|
|
76
|
+
}, 0)
|
|
77
|
+
}
|
|
78
|
+
read()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface StreamTransformInAsyncMacroTaskOptions<T, U> {
|
|
82
|
+
readableStream?: ReadableStream<T> | undefined
|
|
83
|
+
reader?: ReadableStreamDefaultReader<T> | undefined
|
|
84
|
+
onChunk?: ((chunk: ReadableStreamReadResult<T>, controller: ReadableStreamDefaultController<U>) => (void | Promise<void>)) | undefined
|
|
85
|
+
onError?: ((error: Error) => (void | Error | Promise<void | Error>)) | undefined
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 在宏任务队列中完成 ReadableStream 到另一个 ReadableStream 的转换。
|
|
89
|
+
*/
|
|
90
|
+
export const streamTransformInAsyncMacroTask = <T, U>(
|
|
91
|
+
options: StreamTransformInAsyncMacroTaskOptions<T, U>,
|
|
92
|
+
): ReadableStream<U> => {
|
|
93
|
+
const { readableStream, reader, onChunk, onError } = options
|
|
94
|
+
|
|
95
|
+
if (reader === undefined && readableStream === undefined) {
|
|
96
|
+
throw new Error("Either readableStream or reader must be provided")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const preparedReader = reader ?? readableStream!.getReader()
|
|
100
|
+
const stream = new ReadableStream<U>({
|
|
101
|
+
start(controller): void {
|
|
102
|
+
const read = (): void => {
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
void preparedReader.read()
|
|
105
|
+
.then(async (chunk) => {
|
|
106
|
+
let shouldContinue = true
|
|
107
|
+
const proxyController: ReadableStreamController<U> = {
|
|
108
|
+
// oxlint-disable-next-line no-misused-spread
|
|
109
|
+
...controller,
|
|
110
|
+
enqueue: (chunk: U): void => {
|
|
111
|
+
controller.enqueue(chunk)
|
|
112
|
+
shouldContinue = true
|
|
113
|
+
},
|
|
114
|
+
close: (): void => {
|
|
115
|
+
controller.close()
|
|
116
|
+
shouldContinue = false
|
|
117
|
+
},
|
|
118
|
+
error: (error: Error): void => {
|
|
119
|
+
controller.error(error)
|
|
120
|
+
shouldContinue = false
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
await onChunk?.(chunk, proxyController)
|
|
124
|
+
if (shouldContinue === true) {
|
|
125
|
+
read()
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
.catch(async (reason: unknown) => {
|
|
129
|
+
const error = new Error(String(reason))
|
|
130
|
+
const refinedError = await onError?.(error)
|
|
131
|
+
controller.error(refinedError ?? error)
|
|
132
|
+
})
|
|
133
|
+
}, 0)
|
|
134
|
+
}
|
|
135
|
+
read()
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
return stream
|
|
140
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
const internalRandomPacket: Record<number, string[]> = {}
|
|
2
|
+
/**
|
|
3
|
+
* Generate a random string with a fixed length and optional character set.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```
|
|
7
|
+
* // Expect: 8
|
|
8
|
+
* const example1 = stringRandom(8).length
|
|
9
|
+
* // Expect: true
|
|
10
|
+
* const example2 = stringRandom(4, "ab").split("").every(char => char === "a" || char === "b")
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export const stringRandom = (length: number, chars?: string | undefined): string => {
|
|
14
|
+
let result = ""
|
|
15
|
+
const preparedChars = chars ?? "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
16
|
+
|
|
17
|
+
Array.from({ length }).forEach(() => {
|
|
18
|
+
result = result + preparedChars[Math.floor(Math.random() * preparedChars.length)]!
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
internalRandomPacket[length] = internalRandomPacket[length] ?? []
|
|
22
|
+
const packet = internalRandomPacket[length]
|
|
23
|
+
if (packet.includes(result)) {
|
|
24
|
+
return stringRandom(length, preparedChars)
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
packet.push(result)
|
|
28
|
+
return result
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Convert a camelCase string into kebab-case.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```
|
|
37
|
+
* // Expect: "hello-world"
|
|
38
|
+
* const example1 = stringCamelCaseToKebabCase("helloWorld")
|
|
39
|
+
* // Expect: "ab2-cd"
|
|
40
|
+
* const example2 = stringCamelCaseToKebabCase("ab2Cd")
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export const stringCamelCaseToKebabCase = (camelCase: string): string => {
|
|
44
|
+
return camelCase.replaceAll(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Convert a kebab-case string into camelCase.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```
|
|
52
|
+
* // Expect: "helloWorld"
|
|
53
|
+
* const example1 = stringKebabCaseToCamelCase("hello-world")
|
|
54
|
+
* // Expect: "ab2Cd"
|
|
55
|
+
* const example2 = stringKebabCaseToCamelCase("ab2-cd")
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export const stringKebabCaseToCamelCase = (kebabCase: string): string => {
|
|
59
|
+
return kebabCase.replaceAll(/-./g, x => x[1]!.toUpperCase())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get a greeting based on the current local hour.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```
|
|
67
|
+
* // Expect: "你好" | "早上好" | "晚上好" | ...
|
|
68
|
+
* const example1 = stringHelloWord()
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export const stringHelloWord = (): string => {
|
|
72
|
+
const currentHour = new Date().getHours()
|
|
73
|
+
if (currentHour < 5) {
|
|
74
|
+
return "凌晨好"
|
|
75
|
+
}
|
|
76
|
+
if (currentHour < 8) {
|
|
77
|
+
return "早上好"
|
|
78
|
+
}
|
|
79
|
+
if (currentHour < 12) {
|
|
80
|
+
return "上午好"
|
|
81
|
+
}
|
|
82
|
+
if (currentHour < 14) {
|
|
83
|
+
return "中午好"
|
|
84
|
+
}
|
|
85
|
+
if (currentHour < 17) {
|
|
86
|
+
return "下午好"
|
|
87
|
+
}
|
|
88
|
+
if (currentHour < 19) {
|
|
89
|
+
return "傍晚好"
|
|
90
|
+
}
|
|
91
|
+
if (currentHour < 24) {
|
|
92
|
+
return "晚上好"
|
|
93
|
+
}
|
|
94
|
+
return "你好"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Calculate the total unit length of a string.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```
|
|
102
|
+
* // Expect: 1.5
|
|
103
|
+
* const example1 = stringCalculateUnits("a中")
|
|
104
|
+
* // Expect: 2
|
|
105
|
+
* const example2 = stringCalculateUnits("中文")
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export const stringCalculateUnits = (text: string): number => {
|
|
109
|
+
let units = 0
|
|
110
|
+
|
|
111
|
+
for (const char of text) {
|
|
112
|
+
if (/[\u0000-\u007F]/.test(char)) {
|
|
113
|
+
// Half-width character (e.g., ASCII)
|
|
114
|
+
units = units + 0.5
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// Full-width character (e.g., CJK)
|
|
118
|
+
units = units + 1
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return units
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Truncate a string by the unit limit and keep whole characters.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```
|
|
130
|
+
* // Expect: "a中"
|
|
131
|
+
* const example1 = stringTruncateByUnits("a中文", 1.5)
|
|
132
|
+
* // Expect: "abc"
|
|
133
|
+
* const example2 = stringTruncateByUnits("abc", 2)
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export const stringTruncateByUnits = (text: string, maxUnits: number): string => {
|
|
137
|
+
let truncated = ""
|
|
138
|
+
let units = 0
|
|
139
|
+
|
|
140
|
+
for (const char of text) {
|
|
141
|
+
const charUnits = /[\u0000-\u007F]/.test(char) ? 0.5 : 1
|
|
142
|
+
if (units + charUnits > maxUnits) { break }
|
|
143
|
+
|
|
144
|
+
truncated = truncated + char
|
|
145
|
+
units = units + charUnits
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return truncated
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Slice a string by unit indices.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```
|
|
156
|
+
* // Expect: "a中"
|
|
157
|
+
* const example1 = stringSliceByUnits("a中文", 0, 1.5)
|
|
158
|
+
* // Expect: "文"
|
|
159
|
+
* const example2 = stringSliceByUnits("中文ab", 1, 2)
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export const stringSliceByUnits = (text: string, start: number, end: number): string => {
|
|
163
|
+
let units = 0
|
|
164
|
+
let result = ""
|
|
165
|
+
for (const char of text) {
|
|
166
|
+
const charUnits = /[\u0000-\u007F]/.test(char) ? 0.5 : 1
|
|
167
|
+
if (units >= end) { break }
|
|
168
|
+
|
|
169
|
+
if (units >= start) {
|
|
170
|
+
result = result + char
|
|
171
|
+
}
|
|
172
|
+
units = units + charUnits
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface StringSplitOptions {
|
|
179
|
+
input: string
|
|
180
|
+
chunkSize: number
|
|
181
|
+
chunkOverlap: number
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Split a long string into fixed-size chunks with optional overlap.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```
|
|
189
|
+
* // Expect: ["hello", "lo wo", "world"]
|
|
190
|
+
* const example1 = stringSplit({ input: "hello world", chunkSize: 5, chunkOverlap: 2 })
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
export const stringSplit = (options: StringSplitOptions): string[] => {
|
|
194
|
+
const { input, chunkSize = 1_500, chunkOverlap: overlap = 20 } = options
|
|
195
|
+
const safeChunkSize = Math.max(1, Math.trunc(chunkSize))
|
|
196
|
+
const safeOverlap = Math.max(0, Math.min(Math.trunc(overlap), safeChunkSize - 1))
|
|
197
|
+
|
|
198
|
+
// 去除换行符和其他特殊符号
|
|
199
|
+
const sanitizedInput = input.replaceAll(/[\t\n\r]/g, " ")
|
|
200
|
+
|
|
201
|
+
// 分割结果数组
|
|
202
|
+
const result: string[] = []
|
|
203
|
+
|
|
204
|
+
// 当前处理位置
|
|
205
|
+
let currentPosition = 0
|
|
206
|
+
|
|
207
|
+
// 循环处理整个输入字符串
|
|
208
|
+
while (currentPosition < sanitizedInput.length) {
|
|
209
|
+
// 计算当前片段的结束位置
|
|
210
|
+
const endPosition = Math.min(currentPosition + safeChunkSize, sanitizedInput.length)
|
|
211
|
+
|
|
212
|
+
// 获取当前片段
|
|
213
|
+
const segment = sanitizedInput.slice(currentPosition, endPosition)
|
|
214
|
+
|
|
215
|
+
// 将当前片段添加到结果数组中
|
|
216
|
+
result.push(segment)
|
|
217
|
+
|
|
218
|
+
// 如果已经到达文本末尾,退出循环
|
|
219
|
+
if (endPosition >= sanitizedInput.length) {
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 更新处理位置,有重叠部分
|
|
224
|
+
currentPosition = endPosition - safeOverlap
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return result
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Split a string into chunks while trying to keep natural line breaks.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```
|
|
235
|
+
* // Expect: ["a\\n", "b\\n", "c"]
|
|
236
|
+
* const example1 = stringSmartSplit("a\nb\nc", 2)
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
export const stringSmartSplit = (text: string, maxLength: number): string[] => {
|
|
240
|
+
let _text = text
|
|
241
|
+
if (_text.length <= maxLength) {
|
|
242
|
+
return [_text]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const conservativeMaxLength = maxLength * 0.9
|
|
246
|
+
|
|
247
|
+
const threshold = maxLength * 0.2
|
|
248
|
+
const texts: string[] = []
|
|
249
|
+
while (_text.length > conservativeMaxLength) {
|
|
250
|
+
texts.push(_text.slice(0, conservativeMaxLength))
|
|
251
|
+
_text = _text.slice(conservativeMaxLength)
|
|
252
|
+
}
|
|
253
|
+
if (_text.length > threshold) {
|
|
254
|
+
texts.push(_text)
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
texts[texts.length - 1] = texts.at(-1)! + _text
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (texts.length === 1) {
|
|
261
|
+
return texts
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (let i = 1; i < texts.length; i = i + 1) {
|
|
265
|
+
const previousText = texts[i - 1]!
|
|
266
|
+
const currentText = texts[i]!
|
|
267
|
+
const lastIndexOfNewLineOfPreviousText = previousText.lastIndexOf("\n")
|
|
268
|
+
const firstIndexOfNewLineOfCurrentText = currentText.indexOf("\n")
|
|
269
|
+
const endOfPreviousText = previousText.slice(lastIndexOfNewLineOfPreviousText + 1)
|
|
270
|
+
const startOfCurrentText = currentText.slice(0, firstIndexOfNewLineOfCurrentText)
|
|
271
|
+
if (endOfPreviousText.length >= startOfCurrentText.length) {
|
|
272
|
+
const newPreviousText = previousText + startOfCurrentText
|
|
273
|
+
const newCurrentText = currentText.slice(firstIndexOfNewLineOfCurrentText + 1)
|
|
274
|
+
texts[i - 1] = newPreviousText
|
|
275
|
+
texts[i] = newCurrentText
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const newPreviousText = previousText.slice(0, lastIndexOfNewLineOfPreviousText)
|
|
279
|
+
const newCurrentText = endOfPreviousText + currentText
|
|
280
|
+
texts[i - 1] = newPreviousText
|
|
281
|
+
texts[i] = newCurrentText
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const trimmedTexts = texts.map(text => text.trim())
|
|
286
|
+
|
|
287
|
+
return trimmedTexts
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Truncate a string to a maximum length with an ellipsis.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```
|
|
295
|
+
* // Expect: "hello..."
|
|
296
|
+
* const example1 = stringTruncate("hello world", 5)
|
|
297
|
+
* // Expect: "hi"
|
|
298
|
+
* const example2 = stringTruncate("hi", 5)
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
export const stringTruncate = (str: string, n: number): string => {
|
|
302
|
+
if (str.length <= n) {
|
|
303
|
+
return str
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
return `${str.slice(0, n)}...`
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { isSymbol } from "./is.ts"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a new unique symbol with an optional description.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```
|
|
8
|
+
* // Expect: true
|
|
9
|
+
* const example1 = typeof symbolCreateLocal("demo") === "symbol"
|
|
10
|
+
* // Expect: false
|
|
11
|
+
* const example2 = symbolCreateLocal("demo") === symbolCreateLocal("demo")
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export const symbolCreateLocal = (description?: string): symbol => {
|
|
15
|
+
return Symbol(description)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get or create a global symbol for the given key.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```
|
|
23
|
+
* // Expect: true
|
|
24
|
+
* const example1 = symbolCreateGlobal("demo") === Symbol.for("demo")
|
|
25
|
+
* // Expect: true
|
|
26
|
+
* const example2 = symbolCreateGlobal("demo") === symbolCreateGlobal("demo")
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const symbolCreateGlobal = (key: string): symbol => Symbol.for(key)
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the key for a global symbol, or undefined if not global.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```
|
|
36
|
+
* // Expect: "demo"
|
|
37
|
+
* const example1 = symbolGetKey(Symbol.for("demo"))
|
|
38
|
+
* // Expect: undefined
|
|
39
|
+
* const example2 = symbolGetKey(Symbol("demo"))
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const symbolGetKey = (value: unknown): string | undefined => {
|
|
43
|
+
return isSymbol(value) ? Symbol.keyFor(value) : undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check whether a symbol is from the global registry.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```
|
|
51
|
+
* // Expect: true
|
|
52
|
+
* const example1 = symbolIsGlobal(Symbol.for("demo"))
|
|
53
|
+
* // Expect: false
|
|
54
|
+
* const example2 = symbolIsGlobal(Symbol("demo"))
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export const symbolIsGlobal = (value: unknown): value is symbol => {
|
|
58
|
+
return isSymbol(value) && Symbol.keyFor(value) !== undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check whether a symbol is not from the global registry.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```
|
|
66
|
+
* // Expect: false
|
|
67
|
+
* const example1 = symbolIsLocal(Symbol.for("demo"))
|
|
68
|
+
* // Expect: true
|
|
69
|
+
* const example2 = symbolIsLocal(Symbol("demo"))
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export const symbolIsLocal = (value: unknown): value is symbol => {
|
|
73
|
+
return isSymbol(value) && Symbol.keyFor(value) === undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check whether a symbol has a description.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```
|
|
81
|
+
* // Expect: true
|
|
82
|
+
* const example1 = symbolHasDescription(Symbol("demo"))
|
|
83
|
+
* // Expect: false
|
|
84
|
+
* const example2 = symbolHasDescription(Symbol())
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export const symbolHasDescription = (value: unknown): value is symbol => {
|
|
88
|
+
return isSymbol(value) && value.description !== undefined
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the description of a symbol, or undefined for non-symbols.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```
|
|
96
|
+
* // Expect: "demo"
|
|
97
|
+
* const example1 = symbolGetDescription(Symbol("demo"))
|
|
98
|
+
* // Expect: undefined
|
|
99
|
+
* const example2 = symbolGetDescription(Symbol())
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export const symbolGetDescription = (value: unknown): string | undefined => {
|
|
103
|
+
return isSymbol(value) ? value.description : undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check whether a symbol is anonymous (no description).
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```
|
|
111
|
+
* // Expect: false
|
|
112
|
+
* const example1 = symbolIsAnonymous(Symbol("demo"))
|
|
113
|
+
* // Expect: true
|
|
114
|
+
* const example2 = symbolIsAnonymous(Symbol())
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export const symbolIsAnonymous = (value: unknown): value is symbol => {
|
|
118
|
+
return isSymbol(value) && value.description === undefined
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const internalWellKnownSymbols = new Set<symbol>([
|
|
122
|
+
Symbol.asyncIterator,
|
|
123
|
+
Symbol.hasInstance,
|
|
124
|
+
Symbol.isConcatSpreadable,
|
|
125
|
+
Symbol.iterator,
|
|
126
|
+
Symbol.match,
|
|
127
|
+
Symbol.matchAll,
|
|
128
|
+
Symbol.replace,
|
|
129
|
+
Symbol.search,
|
|
130
|
+
Symbol.species,
|
|
131
|
+
Symbol.split,
|
|
132
|
+
Symbol.toPrimitive,
|
|
133
|
+
Symbol.toStringTag,
|
|
134
|
+
Symbol.unscopables,
|
|
135
|
+
])
|
|
136
|
+
/**
|
|
137
|
+
* Check whether a symbol is one of the well-known symbols.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```
|
|
141
|
+
* // Expect: true
|
|
142
|
+
* const example1 = symbolIsWellKnown(Symbol.iterator)
|
|
143
|
+
* // Expect: false
|
|
144
|
+
* const example2 = symbolIsWellKnown(Symbol("iterator"))
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export const symbolIsWellKnown = (value: unknown): value is symbol => {
|
|
148
|
+
return isSymbol(value) && internalWellKnownSymbols.has(value)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Convert a symbol to its string representation.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```
|
|
156
|
+
* // Expect: "Symbol(demo)"
|
|
157
|
+
* const example1 = symbolToString(Symbol("demo"))
|
|
158
|
+
* // Expect: undefined
|
|
159
|
+
* const example2 = symbolToString("demo")
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export const symbolToString = (value: unknown): string | undefined => {
|
|
163
|
+
return isSymbol(value) ? value.toString() : undefined
|
|
164
|
+
}
|