xshell 1.2.57 → 1.2.59
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/net.browser.d.ts +2 -27
- package/net.browser.js +2 -12
- package/net.common.d.ts +28 -0
- package/net.common.js +13 -0
- package/net.d.ts +3 -27
- package/net.js +2 -12
- package/package.json +3 -3
- package/prototype.browser.d.ts +1 -252
- package/prototype.browser.js +78 -643
- package/prototype.common.d.ts +253 -0
- package/prototype.common.js +583 -0
- package/prototype.d.ts +8 -252
- package/prototype.js +9 -584
- package/server.js +1 -1
- package/utils.browser.d.ts +2 -130
- package/utils.browser.js +3 -389
- package/utils.common.d.ts +141 -0
- package/utils.common.js +405 -0
- package/utils.d.ts +13 -145
- package/utils.js +28 -399
package/utils.browser.d.ts
CHANGED
|
@@ -1,38 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
export
|
|
3
|
-
/** 做参数校验,逻辑检查 */
|
|
4
|
-
export declare function check<T>(condition: T, message?: string): T;
|
|
5
|
-
/** 通过 console.log 打印对象并返回
|
|
6
|
-
@example
|
|
7
|
-
log('label', obj)
|
|
8
|
-
log(obj) */
|
|
9
|
-
export declare function log<TObj>(obj: TObj): TObj;
|
|
10
|
-
export declare function log<TObj>(label: string, obj: TObj): TObj;
|
|
11
|
-
/** 数组或 iterable 去重(可按 mapper 选择或计算某个值来去重),重复值保留最后出现的那个
|
|
12
|
-
- mapper?: 可以是 key (string, number, symbol) 或 (obj: any) => any */
|
|
13
|
-
export declare function unique<TObj>(iterable: TObj[] | Iterable<TObj>, mapper?: keyof TObj | Mapper<TObj>): TObj[];
|
|
14
|
-
/** 生成 0, 1, ..., n - 1 (不包括 n) 的数组,支持传入 generator 函数,通过 index 生成各个元素
|
|
15
|
-
@example seq(10, i => `item-${i}`) */
|
|
16
|
-
export declare function seq<T = number>(n: number, generator?: (index: number) => T): T[];
|
|
17
|
-
/** 将 keys, values 数组按对应的顺序组合成一个对象 */
|
|
18
|
-
export declare function zip_object<TValue>(keys: (string | number)[], values: TValue[]): Record<string, TValue>;
|
|
19
|
-
/** 映射对象中的 keys, 返回新对象
|
|
20
|
-
- obj: 对象
|
|
21
|
-
- mapper?: `to_snake_case` (key: string) => string 或者 Record<string, string> 一对一映射键
|
|
22
|
-
- overrider?: 添加一些键到返回的新对象上 */
|
|
23
|
-
export declare function map_keys<TReturn>(obj: any, mapper?: ((key: string) => string) | Record<string, string>, overrider?: (mapped: any) => Partial<TReturn>): TReturn;
|
|
24
|
-
/** 返回一个映射对象 keys 的函数,通常和 .map 函数一起使用 */
|
|
25
|
-
export declare function get_key_mapper<TReturn>(mapper?: ((key: string) => string) | Record<string, string>, overrider?: (mapped: any) => Partial<TReturn>): (obj: any) => TReturn;
|
|
26
|
-
/** 过滤对象中的 values, 返回新对象
|
|
27
|
-
- obj
|
|
28
|
-
- filter?: `not_empty` */
|
|
29
|
-
export declare function filter_values<TObj extends Record<string, any>>(obj: TObj, filter?: (value: TObj[string]) => any): TObj;
|
|
1
|
+
import './prototype.browser.ts';
|
|
2
|
+
export * from './utils.common.ts';
|
|
30
3
|
export declare function delay(milliseconds: number, { signal }?: {
|
|
31
4
|
signal?: AbortSignal;
|
|
32
5
|
}): Promise<void>;
|
|
33
|
-
export declare class TimeoutError extends Error {
|
|
34
|
-
name: "TimeoutError";
|
|
35
|
-
}
|
|
36
6
|
/** 在指定的时间 (milliseconds) 内运行某个任务,超时之后抛出错误或调用 on_timeout
|
|
37
7
|
- milliseconds: 限时毫秒数
|
|
38
8
|
- action?: 要等待运行的任务, async function 或 promise
|
|
@@ -41,109 +11,11 @@ export declare class TimeoutError extends Error {
|
|
|
41
11
|
- 如果没传入 on_timeout 参数: 抛出 TimeoutError
|
|
42
12
|
- print?: 打印已超时任务的错误 */
|
|
43
13
|
export declare function timeout<TReturn>(milliseconds: number, action: Promise<TReturn> | (() => Promise<TReturn>), on_timeout?: () => void | Promise<void>, print?: boolean): Promise<TReturn>;
|
|
44
|
-
/** https://stackoverflow.com/questions/63297164/how-to-only-accept-arraybuffer-as-parameter */
|
|
45
|
-
export type StrictArrayBuffer = ArrayBuffer & {
|
|
46
|
-
buffer?: undefined;
|
|
47
|
-
};
|
|
48
|
-
export interface Deferred<TValue> extends Promise<TValue> {
|
|
49
|
-
resolve(value: TValue | PromiseLike<TValue>): void;
|
|
50
|
-
reject(reason?: Error): void;
|
|
51
|
-
}
|
|
52
|
-
/** 创建一个 promise,后续可调用 promise.resolve, promise.reject 方法设置其状态和值
|
|
53
|
-
- initial?: `undefined` 传入非 undefined 值(包括 null)时直接设置为 resolved 状态
|
|
54
|
-
注: 下面的方法不能标记为 aysnc function, 否则会对返回值再做一层 Promise.resolve() 导致 reject, resolve 属性丢失 */
|
|
55
|
-
export declare function defer<TValue>(initial?: TValue): Deferred<TValue>;
|
|
56
|
-
export interface LockedAction<TResource, TResult> {
|
|
57
|
-
(resource: TResource): TResult | Promise<TResult>;
|
|
58
|
-
}
|
|
59
|
-
/** @example
|
|
60
|
-
let lock = new Lock(redis)
|
|
61
|
-
|
|
62
|
-
// 锁定资源后在 action 回调中操作资源,回调 promise 完成后自动释放资源,
|
|
63
|
-
// 三秒内未成功独占资源直接抛出 TimeoutError (开始执行后不受 signal abort 影响)
|
|
64
|
-
await lock.request(async redis => {
|
|
65
|
-
const value = await redis.get('key')
|
|
66
|
-
await redis.set('key', value * 2)
|
|
67
|
-
}, AbortSignal.timeout(3000))
|
|
68
|
-
|
|
69
|
-
参考:
|
|
70
|
-
https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
|
|
71
|
-
https://github.com/metarhia/web-locks
|
|
72
|
-
https://www.npmjs.com/package/async-lock */
|
|
73
|
-
export declare class Lock<TResource = void> {
|
|
74
|
-
/** 如果操作不需要独占资源,可以直接通过 lock.resource 访问,否则需要通过 await lock.request() 独占资源后再访问 */
|
|
75
|
-
resource: TResource;
|
|
76
|
-
/** 等待链,新的 await lock.request() 调用会等待当前等待链尾部的 promise 完成,并作为新的尾部 */
|
|
77
|
-
ptail: Deferred<void>;
|
|
78
|
-
/** 查询当前资源是否属于被锁定的状态,方便在资源闲置时做一些可选操作(操作前仍需锁定),或者做一些状态展示 */
|
|
79
|
-
locked: boolean;
|
|
80
|
-
/** 可以不传 resource,表示管理某个抽象或虚拟的资源 */
|
|
81
|
-
constructor(resource?: TResource);
|
|
82
|
-
/** 通过 await lock.request() 锁定资源以便独占访问
|
|
83
|
-
成功返回之后由调用方负责调用 release 方法释放资源
|
|
84
|
-
在 signal aborted 时抛出错误结束等待,且由内部实现自动释放资源 */
|
|
85
|
-
request<TResult>(action: LockedAction<TResource, TResult>, signal?: AbortSignal): Promise<TResult>;
|
|
86
|
-
}
|
|
87
14
|
export declare function pause(milliseconds?: number): Promise<void>;
|
|
88
15
|
/** 将字符串简单的编码为 utf-8 的 buffer (Uint8Array)。高频使用或者在流式处理时,考虑使用 TextEncoder 的 encodeInto 方法 */
|
|
89
16
|
export declare function encode(str: string): Uint8Array<ArrayBufferLike>;
|
|
90
|
-
export declare function encode_into(str: string, buf: Uint8Array): TextEncoderEncodeIntoResult;
|
|
91
|
-
/** 将 utf-8 buffer (Uint8Array) 简单的解码为 string。
|
|
92
|
-
在流式处理 (buffer 可能不完整) 时,应使用独立的 TextDecoder 实例调用 decode(buffer, { stream: true }) */
|
|
93
|
-
export declare function decode(buffer: Uint8Array): string;
|
|
94
|
-
/** 字符串字典序比较 */
|
|
95
|
-
export declare function strcmp(l: string, r: string): 0 | 1 | -1;
|
|
96
|
-
/** 比较 1.10.02 这种版本号
|
|
97
|
-
- l, r: 两个版本号字符串
|
|
98
|
-
- loose?: 宽松模式,允许两个版本号格式(位数)不一致 */
|
|
99
|
-
export declare function vercmp(l: string, r: string, loose?: boolean): number;
|
|
100
|
-
/** 过滤符合 pattern 的行 */
|
|
101
|
-
export declare function grep(str: string, pattern: string | RegExp): string;
|
|
102
|
-
/** 模糊过滤字符串列表或对象列表,常用于根据用户输入补全或搜索过滤
|
|
103
|
-
如果有完全匹配的,只返回那一项的数组
|
|
104
|
-
- query: 查询字符串,要求为全小写
|
|
105
|
-
- list: 要过滤的列表
|
|
106
|
-
- list_lower?: 要过滤的列表对应的全小写字符串列表形式,传入时可复用缓存,加快搜索速度 */
|
|
107
|
-
export declare function fuzzyfilter<TItem = string>(query: string, list: TItem[], mapper?: keyof TItem | Mapper<TItem>, list_lower?: string[], single_char_startswith?: boolean): TItem[];
|
|
108
|
-
export declare function get<TReturn = any>(obj: any, keypath: string): TReturn;
|
|
109
|
-
export declare function global_get<TReturn = any>(keypath: string): TReturn;
|
|
110
|
-
export declare function invoke<TReturn = any>(obj: any, funcpath: string, args: any[]): TReturn;
|
|
111
17
|
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
|
|
112
18
|
export declare function concat(arrays: ArrayBufferView[]): Uint8Array<ArrayBuffer>;
|
|
113
|
-
/** 时间间隔 (milliseconds) 格式化 */
|
|
114
|
-
export declare function delta2str(delta: number): string;
|
|
115
|
-
/** generate random id */
|
|
116
|
-
export declare function genid(): number;
|
|
117
|
-
/** 默认日期时间格式 */
|
|
118
|
-
export declare const datetime_format: "YYYY.MM.DD HH:mm:ss";
|
|
119
|
-
/** 默认日期格式 */
|
|
120
|
-
export declare const date_format: "YYYY.MM.DD";
|
|
121
|
-
/** 默认时间格式 */
|
|
122
|
-
export declare const time_format: "HH:mm:ss";
|
|
123
|
-
export declare class Timer {
|
|
124
|
-
started: number;
|
|
125
|
-
ended: number;
|
|
126
|
-
/** 停止秒表,保存读数 */
|
|
127
|
-
stop(): void;
|
|
128
|
-
/** 如果秒表未停止,获取当前秒表读数;
|
|
129
|
-
如果秒表已停止,获取停止时的秒表读数; */
|
|
130
|
-
get(): number;
|
|
131
|
-
/** 获取时间表示字符串,如 1.2 s
|
|
132
|
-
- parenthesis?: `true` 字符串前后加上括号,如 (1.2 s) */
|
|
133
|
-
getstr(parenthesis?: boolean): string;
|
|
134
|
-
print(): void;
|
|
135
|
-
/** 重置 started */
|
|
136
|
-
reset(): void;
|
|
137
|
-
get_and_reset(): number;
|
|
138
|
-
getstr_and_reset(parenthesis?: boolean): string;
|
|
139
|
-
}
|
|
140
|
-
export declare function lowercase_first_letter(str: string): string;
|
|
141
|
-
/** 大于 n 的最小的 2 的幂次 */
|
|
142
|
-
export declare function ceil2(n: number): number;
|
|
143
|
-
/** 节流,最多只在时间间隔末尾调用一次,首次调用也延后 */
|
|
144
|
-
export declare function throttle(duration: number, func: Function, delay_first?: boolean): (this: any, ...args: any[]) => void;
|
|
145
|
-
/** 防抖,间隔一段时间不再触发时调用 */
|
|
146
|
-
export declare function debounce(duration: number, func: Function): (this: any, ...args: any[]) => void;
|
|
147
19
|
/** 轮询尝试 action 共 times 次,每次间隔 duration
|
|
148
20
|
action 返回 trusy 值时认为成功,返回 action 的结果
|
|
149
21
|
如果次数用尽仍然失败,返回 null */
|
package/utils.browser.js
CHANGED
|
@@ -1,82 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
export
|
|
4
|
-
if (!assertion) {
|
|
5
|
-
debugger;
|
|
6
|
-
throw new Error(`${t('断言失败')}: ${message ? `${message}: ` : ''}`);
|
|
7
|
-
}
|
|
8
|
-
return assertion;
|
|
9
|
-
}
|
|
10
|
-
/** 做参数校验,逻辑检查 */
|
|
11
|
-
export function check(condition, message) {
|
|
12
|
-
if (!condition) {
|
|
13
|
-
debugger;
|
|
14
|
-
throw new Error(message || t('检查失败'));
|
|
15
|
-
}
|
|
16
|
-
return condition;
|
|
17
|
-
}
|
|
18
|
-
export function log(...args) {
|
|
19
|
-
if (args.length === 2) {
|
|
20
|
-
const [label, obj] = args;
|
|
21
|
-
console.log(label, obj);
|
|
22
|
-
return obj;
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
const obj = args[0];
|
|
26
|
-
console.log(obj);
|
|
27
|
-
return obj;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/** 数组或 iterable 去重(可按 mapper 选择或计算某个值来去重),重复值保留最后出现的那个
|
|
31
|
-
- mapper?: 可以是 key (string, number, symbol) 或 (obj: any) => any */
|
|
32
|
-
export function unique(iterable, mapper) {
|
|
33
|
-
if (!mapper)
|
|
34
|
-
return [...new Set(iterable)];
|
|
35
|
-
if (is_key_type(mapper))
|
|
36
|
-
mapper = select(mapper);
|
|
37
|
-
let map = new Map();
|
|
38
|
-
for (const x of iterable)
|
|
39
|
-
map.set(mapper(x), x);
|
|
40
|
-
return [...map.values()];
|
|
41
|
-
}
|
|
42
|
-
/** 生成 0, 1, ..., n - 1 (不包括 n) 的数组,支持传入 generator 函数,通过 index 生成各个元素
|
|
43
|
-
@example seq(10, i => `item-${i}`) */
|
|
44
|
-
export function seq(n, generator) {
|
|
45
|
-
let a = new Array(n);
|
|
46
|
-
for (let i = 0; i < n; i++)
|
|
47
|
-
a[i] = generator ? generator(i) : i;
|
|
48
|
-
return a;
|
|
49
|
-
}
|
|
50
|
-
/** 将 keys, values 数组按对应的顺序组合成一个对象 */
|
|
51
|
-
export function zip_object(keys, values) {
|
|
52
|
-
return keys.reduce((obj, key, i) => {
|
|
53
|
-
obj[key] = values[i];
|
|
54
|
-
return obj;
|
|
55
|
-
}, {});
|
|
56
|
-
}
|
|
57
|
-
/** 映射对象中的 keys, 返回新对象
|
|
58
|
-
- obj: 对象
|
|
59
|
-
- mapper?: `to_snake_case` (key: string) => string 或者 Record<string, string> 一对一映射键
|
|
60
|
-
- overrider?: 添加一些键到返回的新对象上 */
|
|
61
|
-
export function map_keys(obj, mapper = to_snake_case, overrider) {
|
|
62
|
-
const obj_ = Object.fromEntries(Object.entries(obj)
|
|
63
|
-
.map(typeof mapper === 'function' ?
|
|
64
|
-
([key, value]) => [mapper(key), value]
|
|
65
|
-
:
|
|
66
|
-
([key, value]) => [mapper[key] || key, value]));
|
|
67
|
-
return (overrider ? { ...obj_, ...overrider(obj_) } : obj_);
|
|
68
|
-
}
|
|
69
|
-
/** 返回一个映射对象 keys 的函数,通常和 .map 函数一起使用 */
|
|
70
|
-
export function get_key_mapper(mapper, overrider) {
|
|
71
|
-
return (obj) => map_keys(obj, mapper, overrider);
|
|
72
|
-
}
|
|
73
|
-
/** 过滤对象中的 values, 返回新对象
|
|
74
|
-
- obj
|
|
75
|
-
- filter?: `not_empty` */
|
|
76
|
-
export function filter_values(obj, filter = not_empty) {
|
|
77
|
-
return Object.fromEntries(Object.entries(obj)
|
|
78
|
-
.filter(([, value]) => filter(value)));
|
|
79
|
-
}
|
|
1
|
+
import "./prototype.browser.js";
|
|
2
|
+
import { encoder, TimeoutError } from "./utils.common.js";
|
|
3
|
+
export * from "./utils.common.js";
|
|
80
4
|
export async function delay(milliseconds, { signal } = {}) {
|
|
81
5
|
signal?.throwIfAborted();
|
|
82
6
|
return new Promise((resolve, reject) => {
|
|
@@ -91,9 +15,6 @@ export async function delay(milliseconds, { signal } = {}) {
|
|
|
91
15
|
}, milliseconds);
|
|
92
16
|
});
|
|
93
17
|
}
|
|
94
|
-
export class TimeoutError extends Error {
|
|
95
|
-
name = 'TimeoutError';
|
|
96
|
-
}
|
|
97
18
|
/** 在指定的时间 (milliseconds) 内运行某个任务,超时之后抛出错误或调用 on_timeout
|
|
98
19
|
- milliseconds: 限时毫秒数
|
|
99
20
|
- action?: 要等待运行的任务, async function 或 promise
|
|
@@ -147,195 +68,15 @@ export async function timeout(milliseconds, action, on_timeout, print = true) {
|
|
|
147
68
|
})();
|
|
148
69
|
});
|
|
149
70
|
}
|
|
150
|
-
/** 创建一个 promise,后续可调用 promise.resolve, promise.reject 方法设置其状态和值
|
|
151
|
-
- initial?: `undefined` 传入非 undefined 值(包括 null)时直接设置为 resolved 状态
|
|
152
|
-
注: 下面的方法不能标记为 aysnc function, 否则会对返回值再做一层 Promise.resolve() 导致 reject, resolve 属性丢失 */
|
|
153
|
-
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
154
|
-
export function defer(initial) {
|
|
155
|
-
if (initial === undefined) {
|
|
156
|
-
let resolve;
|
|
157
|
-
let reject;
|
|
158
|
-
let promise = new Promise((_resolve, _reject) => {
|
|
159
|
-
resolve = _resolve;
|
|
160
|
-
reject = _reject;
|
|
161
|
-
});
|
|
162
|
-
return Object.assign(promise, { resolve, reject });
|
|
163
|
-
}
|
|
164
|
-
else
|
|
165
|
-
return Object.assign(Promise.resolve(initial), {
|
|
166
|
-
resolve() { },
|
|
167
|
-
reject() { }
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
/** @example
|
|
171
|
-
let lock = new Lock(redis)
|
|
172
|
-
|
|
173
|
-
// 锁定资源后在 action 回调中操作资源,回调 promise 完成后自动释放资源,
|
|
174
|
-
// 三秒内未成功独占资源直接抛出 TimeoutError (开始执行后不受 signal abort 影响)
|
|
175
|
-
await lock.request(async redis => {
|
|
176
|
-
const value = await redis.get('key')
|
|
177
|
-
await redis.set('key', value * 2)
|
|
178
|
-
}, AbortSignal.timeout(3000))
|
|
179
|
-
|
|
180
|
-
参考:
|
|
181
|
-
https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
|
|
182
|
-
https://github.com/metarhia/web-locks
|
|
183
|
-
https://www.npmjs.com/package/async-lock */
|
|
184
|
-
export class Lock {
|
|
185
|
-
/** 如果操作不需要独占资源,可以直接通过 lock.resource 访问,否则需要通过 await lock.request() 独占资源后再访问 */
|
|
186
|
-
resource;
|
|
187
|
-
/** 等待链,新的 await lock.request() 调用会等待当前等待链尾部的 promise 完成,并作为新的尾部 */
|
|
188
|
-
ptail = defer(null);
|
|
189
|
-
/** 查询当前资源是否属于被锁定的状态,方便在资源闲置时做一些可选操作(操作前仍需锁定),或者做一些状态展示 */
|
|
190
|
-
locked = false;
|
|
191
|
-
/** 可以不传 resource,表示管理某个抽象或虚拟的资源 */
|
|
192
|
-
constructor(resource) {
|
|
193
|
-
this.resource = resource;
|
|
194
|
-
}
|
|
195
|
-
/** 通过 await lock.request() 锁定资源以便独占访问
|
|
196
|
-
成功返回之后由调用方负责调用 release 方法释放资源
|
|
197
|
-
在 signal aborted 时抛出错误结束等待,且由内部实现自动释放资源 */
|
|
198
|
-
// async request (action: (resource: TResource) => Promise<void>, signal?: AbortSignal): Promise<void>
|
|
199
|
-
// async request <TResult> (action: <TResult> (resource: TResource) => Promise<TResult>, signal?: AbortSignal): Promise<TResult>
|
|
200
|
-
async request(action, signal) {
|
|
201
|
-
signal?.throwIfAborted();
|
|
202
|
-
const ptail = this.ptail;
|
|
203
|
-
let pcurrent = this.ptail = defer();
|
|
204
|
-
this.locked = true;
|
|
205
|
-
return new Promise((resolve, reject) => {
|
|
206
|
-
// 下面两种情况,先发生的决定 request 返回的 promise 状态
|
|
207
|
-
// 不管是否 aborted, 都要等资源被前一次调用释放,先 aborted 只是将控制权交回给调用者,在锁定资源后不执行 action, 直接释放
|
|
208
|
-
/** 防止执行过程中 signal abort */
|
|
209
|
-
let executing = false;
|
|
210
|
-
signal?.addEventListener('abort', () => {
|
|
211
|
-
if (!executing)
|
|
212
|
-
// 这里不能释放锁,需要等 ptail resolve 后拿到资源再释放
|
|
213
|
-
reject(signal.reason);
|
|
214
|
-
}, { once: true });
|
|
215
|
-
ptail.then(async () => {
|
|
216
|
-
// 这里已经能保证独占访问资源
|
|
217
|
-
// 如果 aborted, 可以理解为独占资源失败,调用者不会使用资源,直接释放
|
|
218
|
-
if (signal?.aborted)
|
|
219
|
-
reject(signal.reason);
|
|
220
|
-
else
|
|
221
|
-
// 由调用者去操作资源
|
|
222
|
-
try {
|
|
223
|
-
executing = true;
|
|
224
|
-
resolve(await action(this.resource));
|
|
225
|
-
}
|
|
226
|
-
catch (error) {
|
|
227
|
-
reject(error);
|
|
228
|
-
}
|
|
229
|
-
// 下面开始释放锁
|
|
230
|
-
this.locked = false;
|
|
231
|
-
pcurrent.resolve();
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
71
|
export async function pause(milliseconds = 3000) {
|
|
237
72
|
await delay(milliseconds);
|
|
238
73
|
debugger;
|
|
239
74
|
}
|
|
240
75
|
globalThis.pause = pause;
|
|
241
|
-
// ------------ text
|
|
242
|
-
let encoder = new TextEncoder();
|
|
243
76
|
/** 将字符串简单的编码为 utf-8 的 buffer (Uint8Array)。高频使用或者在流式处理时,考虑使用 TextEncoder 的 encodeInto 方法 */
|
|
244
77
|
export function encode(str) {
|
|
245
78
|
return encoder.encode(str);
|
|
246
79
|
}
|
|
247
|
-
export function encode_into(str, buf) {
|
|
248
|
-
// 这个是直接用 v8 String::WriteUtf8 最高效的方法了
|
|
249
|
-
return encoder.encodeInto(str, buf);
|
|
250
|
-
}
|
|
251
|
-
let decoder = new TextDecoder();
|
|
252
|
-
/** 将 utf-8 buffer (Uint8Array) 简单的解码为 string。
|
|
253
|
-
在流式处理 (buffer 可能不完整) 时,应使用独立的 TextDecoder 实例调用 decode(buffer, { stream: true }) */
|
|
254
|
-
export function decode(buffer) {
|
|
255
|
-
return decoder.decode(buffer);
|
|
256
|
-
}
|
|
257
|
-
/** 字符串字典序比较 */
|
|
258
|
-
export function strcmp(l, r) {
|
|
259
|
-
if (l === r)
|
|
260
|
-
return 0;
|
|
261
|
-
if (l < r)
|
|
262
|
-
return -1;
|
|
263
|
-
return 1;
|
|
264
|
-
}
|
|
265
|
-
/** 比较 1.10.02 这种版本号
|
|
266
|
-
- l, r: 两个版本号字符串
|
|
267
|
-
- loose?: 宽松模式,允许两个版本号格式(位数)不一致 */
|
|
268
|
-
export function vercmp(l, r, loose = false) {
|
|
269
|
-
const lparts = l.split('.').map(x => Number(x));
|
|
270
|
-
const rparts = r.split('.').map(x => Number(x));
|
|
271
|
-
if (!loose && lparts.length !== rparts.length)
|
|
272
|
-
throw new Error('传入 vercmp 的两个版本号格式不一致');
|
|
273
|
-
let minlen = Math.min(lparts.length, rparts.length);
|
|
274
|
-
for (let i = 0; i < minlen; i++) {
|
|
275
|
-
const l = lparts[i];
|
|
276
|
-
const r = rparts[i];
|
|
277
|
-
assert(!isNaN(l) && !isNaN(r), '传入 vercmp 的版本非法');
|
|
278
|
-
if (l !== r)
|
|
279
|
-
return l - r;
|
|
280
|
-
}
|
|
281
|
-
// loose 下按短的优先,否则应该一样,为 0
|
|
282
|
-
return lparts.length - rparts.length;
|
|
283
|
-
}
|
|
284
|
-
/** 过滤符合 pattern 的行 */
|
|
285
|
-
export function grep(str, pattern) {
|
|
286
|
-
return str.split_lines()
|
|
287
|
-
.filter(typeof pattern === 'string'
|
|
288
|
-
? line => line.includes(pattern)
|
|
289
|
-
: line => pattern.test(line))
|
|
290
|
-
.join_lines();
|
|
291
|
-
}
|
|
292
|
-
/** 模糊过滤字符串列表或对象列表,常用于根据用户输入补全或搜索过滤
|
|
293
|
-
如果有完全匹配的,只返回那一项的数组
|
|
294
|
-
- query: 查询字符串,要求为全小写
|
|
295
|
-
- list: 要过滤的列表
|
|
296
|
-
- list_lower?: 要过滤的列表对应的全小写字符串列表形式,传入时可复用缓存,加快搜索速度 */
|
|
297
|
-
export function fuzzyfilter(query, list, mapper = ident, list_lower, single_char_startswith = false) {
|
|
298
|
-
if (!query)
|
|
299
|
-
return list;
|
|
300
|
-
if (is_key_type(mapper))
|
|
301
|
-
mapper = select(mapper);
|
|
302
|
-
mapper ??= ident;
|
|
303
|
-
list_lower ??= list.map(item => mapper(item).toLowerCase());
|
|
304
|
-
const query_lower = query.toLowerCase();
|
|
305
|
-
let ifullmatch;
|
|
306
|
-
if ((ifullmatch = list_lower.indexOf(query_lower)) !== -1)
|
|
307
|
-
return [list[ifullmatch]];
|
|
308
|
-
if (single_char_startswith && query.length === 1) {
|
|
309
|
-
const c = query[0];
|
|
310
|
-
return list.filter((_, i) => list_lower[i].startsWith(c));
|
|
311
|
-
}
|
|
312
|
-
return list.filter((_, i) => {
|
|
313
|
-
const str_lower = list_lower[i];
|
|
314
|
-
let j = 0;
|
|
315
|
-
for (const c of query_lower) {
|
|
316
|
-
j = str_lower.indexOf(c, j) + 1;
|
|
317
|
-
if (!j) // 找不到则 j === 0
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
return true;
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
export function get(obj, keypath) {
|
|
324
|
-
let obj_ = obj;
|
|
325
|
-
for (const key of keypath.split('.'))
|
|
326
|
-
obj_ = obj_[key];
|
|
327
|
-
return obj_;
|
|
328
|
-
}
|
|
329
|
-
export function global_get(keypath) {
|
|
330
|
-
return get(globalThis, keypath);
|
|
331
|
-
}
|
|
332
|
-
export function invoke(obj, funcpath, args) {
|
|
333
|
-
const paths = funcpath.split('.');
|
|
334
|
-
let obj_ = obj;
|
|
335
|
-
for (let i = 0; i < paths.length - 1; i++)
|
|
336
|
-
obj_ = obj_[paths[i]];
|
|
337
|
-
return obj_[paths.at(-1)].call(obj_, ...args);
|
|
338
|
-
}
|
|
339
80
|
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
|
|
340
81
|
export function concat(arrays) {
|
|
341
82
|
let length = 0;
|
|
@@ -350,133 +91,6 @@ export function concat(arrays) {
|
|
|
350
91
|
}
|
|
351
92
|
return buf;
|
|
352
93
|
}
|
|
353
|
-
/** 时间间隔 (milliseconds) 格式化 */
|
|
354
|
-
export function delta2str(delta) {
|
|
355
|
-
if (delta < 1)
|
|
356
|
-
return '0 ms';
|
|
357
|
-
// [1, 100) ms
|
|
358
|
-
if (delta < 100)
|
|
359
|
-
return `${delta.toFixed(0)} ms`;
|
|
360
|
-
// [100, 1000) ms
|
|
361
|
-
// 0.8 s
|
|
362
|
-
if (delta <= 950)
|
|
363
|
-
return `${(delta / 1000).toFixed(1)} s`;
|
|
364
|
-
// 3 s
|
|
365
|
-
if (delta < 1000 * 60)
|
|
366
|
-
return `${(delta / 1000).toFixed()} s`;
|
|
367
|
-
// 1 min 12 s [1 min 0s, 60 min)
|
|
368
|
-
const seconds = Math.trunc(delta / 1000);
|
|
369
|
-
if (seconds < 60 * 60)
|
|
370
|
-
return `${Math.trunc(seconds / 60)} min ${seconds % 60} s`;
|
|
371
|
-
const hour = Math.trunc(seconds / 3600);
|
|
372
|
-
return `${hour} h ${Math.trunc((seconds - 3600 * hour) / 60)} min ${seconds % 60} s`;
|
|
373
|
-
}
|
|
374
|
-
/** generate random id */
|
|
375
|
-
export function genid() {
|
|
376
|
-
return Math.random() * 2 ** 53;
|
|
377
|
-
}
|
|
378
|
-
/** 默认日期时间格式 */
|
|
379
|
-
export const datetime_format = 'YYYY.MM.DD HH:mm:ss';
|
|
380
|
-
/** 默认日期格式 */
|
|
381
|
-
export const date_format = 'YYYY.MM.DD';
|
|
382
|
-
/** 默认时间格式 */
|
|
383
|
-
export const time_format = 'HH:mm:ss';
|
|
384
|
-
export class Timer {
|
|
385
|
-
started = new Date().getTime();
|
|
386
|
-
ended;
|
|
387
|
-
/** 停止秒表,保存读数 */
|
|
388
|
-
stop() {
|
|
389
|
-
this.ended = new Date().getTime();
|
|
390
|
-
}
|
|
391
|
-
/** 如果秒表未停止,获取当前秒表读数;
|
|
392
|
-
如果秒表已停止,获取停止时的秒表读数; */
|
|
393
|
-
get() {
|
|
394
|
-
return (this.ended || new Date().getTime()) - this.started;
|
|
395
|
-
}
|
|
396
|
-
/** 获取时间表示字符串,如 1.2 s
|
|
397
|
-
- parenthesis?: `true` 字符串前后加上括号,如 (1.2 s) */
|
|
398
|
-
getstr(parenthesis = false) {
|
|
399
|
-
let s = delta2str(this.get());
|
|
400
|
-
if (parenthesis)
|
|
401
|
-
return s.bracket();
|
|
402
|
-
else
|
|
403
|
-
return s;
|
|
404
|
-
}
|
|
405
|
-
print() {
|
|
406
|
-
console.log(this.getstr(true));
|
|
407
|
-
}
|
|
408
|
-
/** 重置 started */
|
|
409
|
-
reset() {
|
|
410
|
-
this.started = new Date().getTime();
|
|
411
|
-
this.ended = null;
|
|
412
|
-
}
|
|
413
|
-
get_and_reset() {
|
|
414
|
-
const result = this.get();
|
|
415
|
-
this.reset();
|
|
416
|
-
return result;
|
|
417
|
-
}
|
|
418
|
-
getstr_and_reset(parenthesis) {
|
|
419
|
-
const result = this.getstr(parenthesis);
|
|
420
|
-
this.reset();
|
|
421
|
-
return result;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
export function lowercase_first_letter(str) {
|
|
425
|
-
return str[0].toLowerCase() + str.slice(1);
|
|
426
|
-
}
|
|
427
|
-
/** 大于 n 的最小的 2 的幂次 */
|
|
428
|
-
export function ceil2(n) {
|
|
429
|
-
let power = 1;
|
|
430
|
-
// 不能用 power <<= 1, 结果可能会超过 32 bit 整数导致位运算溢出为 0,死循环
|
|
431
|
-
for (; power <= n; power += power)
|
|
432
|
-
;
|
|
433
|
-
return power;
|
|
434
|
-
}
|
|
435
|
-
/** 节流,最多只在时间间隔末尾调用一次,首次调用也延后 */
|
|
436
|
-
export function throttle(duration, func, delay_first = false) {
|
|
437
|
-
let timeout = 0;
|
|
438
|
-
let last = 0;
|
|
439
|
-
let saved_this = null;
|
|
440
|
-
let saved_args = null;
|
|
441
|
-
return function throttled(...args) {
|
|
442
|
-
// 当前时间间隔已预定执行,本次调用仅更新调用参数
|
|
443
|
-
if (timeout) {
|
|
444
|
-
saved_this = this;
|
|
445
|
-
saved_args = args;
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
const now = Date.now();
|
|
449
|
-
if (last === 0 && delay_first)
|
|
450
|
-
last = now;
|
|
451
|
-
const ellapsed = now - last;
|
|
452
|
-
// 过了时间间隔末尾,直接执行
|
|
453
|
-
if (ellapsed >= duration) {
|
|
454
|
-
last = now;
|
|
455
|
-
func.apply(this, args);
|
|
456
|
-
}
|
|
457
|
-
else { // 预定在间隔末尾执行
|
|
458
|
-
saved_this = this;
|
|
459
|
-
saved_args = args;
|
|
460
|
-
timeout = setTimeout(() => {
|
|
461
|
-
timeout = null;
|
|
462
|
-
last = Date.now();
|
|
463
|
-
func.apply(saved_this, saved_args);
|
|
464
|
-
saved_this = null;
|
|
465
|
-
saved_args = null;
|
|
466
|
-
}, duration - ellapsed);
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
/** 防抖,间隔一段时间不再触发时调用 */
|
|
471
|
-
export function debounce(duration, func) {
|
|
472
|
-
let timeout = null;
|
|
473
|
-
return function debounced(...args) {
|
|
474
|
-
clearTimeout(timeout);
|
|
475
|
-
timeout = setTimeout(() => {
|
|
476
|
-
func.apply(this, args);
|
|
477
|
-
}, duration);
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
94
|
/** 轮询尝试 action 共 times 次,每次间隔 duration
|
|
481
95
|
action 返回 trusy 值时认为成功,返回 action 的结果
|
|
482
96
|
如果次数用尽仍然失败,返回 null */
|