-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjstracetoix.ts
331 lines (307 loc) · 11.6 KB
/
jstracetoix.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// --------------------------------------------------------------------
// Copyright (c) 2024 Alexandre Bento Freire. All rights reserved.
// Licensed under the MIT license
// --------------------------------------------------------------------
import {
NodeBrowserStream, getMultithreading, getThreadId, setMultithreading,
acquireLock, releaseLock, setStream, writeToStream
} from './externals';
/**
* Format Type -- Defines how result and input values will be formatted.
*
* @property {string} result - The format of the result value to be displayed.
* Defaults to `'{name}: `{value}`'`.
* @property {string} input - The format of the input value to be displayed.
* Defaults to `'{name}: `{value}`'`.
* @property {string} thread - The format of the thread ID to be displayed.
* Defaults to `'{id}: '`.
* @property {string} sep - The separator text between each input and the result.
* Defaults to `' | '`.
* @property {boolean} new_line - If `true`, it will add a new line at the end of the output.
*/
export interface Format {
result: string,
input: string,
thread: string,
sep: string,
new_line: boolean
};
/**
* AllowResult -- If it's a boolean it will allow or disallow capture or display result.
* Any other type, it will override the value being captured or displayed.
*/
export type AllowResult = boolean | any;
export type AllowCallback = (data: Record<string, any>) => AllowResult;
export type EventCallback = (data: Record<string, any>) => boolean;
export const DEFAULT_FORMAT: Format = {
result: '{name}:`{value}`',
input: '{name}:`{value}`',
thread: '{id}: ',
sep: ' | ',
new_line: true
};
let _format: Format = DEFAULT_FORMAT;
let _inputsPerThreads: Record<number, Record<string, any>[]> = {};
let _threadNames: Record<number, string> = {};
let _enabled: boolean = true;
/**
* Initializes global settings of the tracing tool.
*
* @param {Object} params - Parameters for initialization.
* @param {NodeBrowserStream} [params.stream=process.stdout] - The output stream to write
* the output lines.
* Default `process.stdout` for node or component and `console.debug` for browser.
* @param {boolean} [params.multithreading=false] - If `true`, it prefixes the output with
* `thread_id:`.
* @param {Format} [params.format=DEFAULT_FORMAT] - Format object defining the output format.
* Defaults to `DEFAULT_FORMAT`.
* @param {boolean} [params.enabled=true] - If `false`, it disables processing
* `t__`, `c__` and `d__`. Defaults to `true`.
*/
export const init__ = ({
stream = undefined,
multithreading = false,
format = DEFAULT_FORMAT,
enabled = true,
}: {
stream?: NodeBrowserStream,
multithreading?: boolean,
format?: Format,
enabled?: boolean,
} = {}): void => {
acquireLock();
setStream(stream);
setMultithreading(multithreading);
_format = format;
_inputsPerThreads = {};
_threadNames = {};
_enabled = enabled;
releaseLock();
};
/**
* Assigns a name to a thread.
*
* If no **name** is provided, it generates a name based on the number of threads.
*
* If no threadIdParam is provided, it uses the current thread ID.
*
* @param {string} [name] - The name for the thread.
* Defaults to `t%d` where %d is the number of threads.
* @param {number} [threadIdParam] - The ID of the thread. Defaults to the current thread ID.
*/
export const t__ = (
name: string | undefined = undefined,
threadIdParam: number | undefined = undefined
): void => {
if (_enabled) {
acquireLock();
_threadNames[getThreadId(threadIdParam)] = name || `t${Object.keys(_threadNames).length}`;
releaseLock();
}
};
/**
* Captures the input value for the current thread.
*
* If no name is provided, it generates a default name.
*
* @param {any} value The input value to store.
* @param {Object} params Optional parameters object
* @param {string | ((index: number, allowIndex: number, value: any) => string) } params.name The
* name of the input.
* Defaults to `i%d` where %d is the number of inputs for the thread.
* @param {boolean | ((index: number, name: string, value: any) => AllowResult} params.allow A
* function or value to allow tracing the input. **allow** is called before **name**.
* If it returns a boolean, it will allow or disallow respectively.
* Otherwise it will display the allow result instead of the input value.
* @param {number} params.level The level number to be used when there is more than one **d__**
* within the same expression or function.
* Defaults to `0`.
*
* @returns The input value
*
* @example
* c__(x);
*
* c__(x, { name: "var-name" });
* c__(x, { name: (index, allowIndex, value) => `${index}` });
*
* [1, 2, 3, 4, 5].map(i => c__(i, { allow: (index, name, value) => index > 2 }));
* [10, 20, 30].map(x => c__(x, { allow: (index, name, value) => value === 20 }));
*
* const z = d__(() => c__(outside_1) + y * c__(outside_2) + d__(() => k * c__(inside(5),
* { level: 1 })));
*/
export const c__ = (
value: any,
params?: {
name?: string | ((index: number, allowIndex: number, value: any) => string);
allow?: boolean | ((index: number, name: string, value: any) => AllowResult);
level?: number;
}
): any => {
if (!_enabled) {
return value;
}
const { name = undefined, allow = undefined, level = 0 } = params || {};
acquireLock();
const _threadId = getThreadId();
if (!_inputsPerThreads[_threadId]) {
_inputsPerThreads[_threadId] = [{ index__: 0, meta__: ['meta__', 'index__'] }];
}
while (_inputsPerThreads[_threadId].length <= level) {
_inputsPerThreads[_threadId].push({ index__: 0, meta__: ['meta__', 'index__'] });
}
const inputs = _inputsPerThreads[_threadId][level];
const index = inputs.index__;
const metaCount = inputs.meta__.length;
let displayName = typeof name === 'function' ? name(index, Object.keys(inputs).length -
metaCount, value) : name || `i${Object.keys(inputs).length - metaCount}`;
let displayValue = value;
let allowResult = allow;
try {
if (typeof allow === 'function') {
allowResult = allow(index, displayName, value);
if (typeof allowResult !== 'boolean') {
displayValue = allowResult;
allowResult = true;
}
}
} finally {
if (allowResult === undefined || allowResult) {
inputs[displayName] = displayValue;
}
inputs.index__ = index + 1;
releaseLock();
}
return value;
};
/**
* Displays formatted result and inputs for the current thread using a given format.
*
* Optionally calls `allow`, `before`, and `after` functions with the data.
*
* `allow`, `before`, and `after` will receive a parameter `data` with the allowed inputs.
* The following meta values will also be available:
*
* - `meta__`: List of meta keys including the name key.
* - `thread_id__`: ID of the thread being executed.
* - `allow_input_count__`: Total number of inputs that are allowed.
* - `input_count__`: Total number of inputs being captured.
* - `allow__`: If `false` it was allowed. Use this for the `after` callback.
* - `output__`: Text passed to `before` without `new_line`.
* - `name`: The `value` parameter.
*
* @param {any} value - The result to trace.
* @param {Object} params - The named parameters.
* @param {string} [params.name='_'] - The name of the function being traced.
* @param {boolean | AllowCallback} [params.allow] - A function to call to allow tracing.
* If it returns `false`, tracing is skipped but `after` is still called.
* If it returns a non-boolean value, it will display the allow result instead of the value.
* @param {EventCallback} [params.before] - A function to call before displaying the output.
* If it returns `false`, tracing is skipped.
* @param {EventCallback} [params.after] - A function to call after displaying the output.
* `after` is always called even if not allowed.
* @param {Record<string, any>} [params.inputs] - Dictionary of additional inputs.
* @param {Format} [params.format] - Alternative output format.
* @returns {any} The traced value.
*
* @example
* d__(x);
* d__(c__(x) + c__(y));
*
* d__(c__(x) + c__(y), { name: "output" });
*
* d__(c__(x) + c__(y), { allow: data => data.input_count__ === 2 });
* d__(c__(x) + c__(y), { allow: data => data.i0 === 10.0 });
* d__(c__(x, { allow: (index, name, value) => value > 10 }) + c__(y),
* { allow: data => data.allow_input_count__ === 2 });
*
* d__([c__(x) for x in ['10', '20']], { before: data => '10' in data.output__ });
*
* d__([c__(x) for x in ['1', '2']], {
* allow: data => data.allow_input_count__ === 2,
* after: data => call_after(data) if (data.allow__) else ""
* });
*/
export const d__ = (
value: any,
params: {
name?: string,
allow?: boolean | AllowCallback,
before?: EventCallback,
after?: EventCallback,
inputs?: Record<string, any>,
format?: Format
} = {}
): any => {
if (!_enabled) {
return value;
}
let {
name = '_',
allow = undefined,
before = undefined,
after = undefined,
inputs = undefined,
format = undefined
} = params || {};
acquireLock();
const _threadId = getThreadId();
const threadInputs = _inputsPerThreads[_threadId] || [{}];
const data = { ...threadInputs[threadInputs.length - 1], ...(inputs || {}) };
data.thread_id__ = _threadId;
data.input_count__ = data.index__ || 0;
data.allow__ = true;
data.meta__ = [...(data.meta__ || ['meta__']), 'allow__', 'allow_input_count__',
'input_count__', 'thread_id__', name];
data[name] = value;
delete data.index__;
data.meta__ = data.meta__.filter((item: string) => item !== 'index__');
data.allow_input_count__ = Object.keys(data).length - data.meta__.length + 1
try {
if (typeof allow === 'function') {
allow = allow(data);
if (typeof allow !== 'boolean') {
data[name] = allow;
allow = true;
}
}
if (allow !== false) {
format = format || _format;
let output = '';
if (getMultithreading() && format.thread) {
output += format.thread.replace('{id}', _threadNames[_threadId] || `${_threadId}`);
}
const replaceMacro = (_format: string, _name: string, _value: any) =>
_format.replace('{name}', _name).replace('{value}',
typeof _value === 'object' ? JSON.stringify(_value) : _value);
if (format.input) {
for (const key in data) {
if (!data.meta__.includes(key)) {
output += replaceMacro(format.input, key, data[key]) + (format.sep || '');
}
}
}
if (format.result) {
output += replaceMacro(format.result, name, data[name]);
}
data.meta__ += ['output__'];
data.output__ = output;
if (before === undefined || before(data)) {
writeToStream(data.output__ + (format.new_line ? '\n' : ''));
}
} else {
data.allow__ = false;
}
after && after(data);
} finally {
if (_inputsPerThreads[_threadId]) {
_inputsPerThreads[_threadId].pop();
if (_inputsPerThreads[_threadId].length === 0) {
delete _inputsPerThreads[_threadId];
}
}
releaseLock();
}
return value;
};