Files
red-valley/artifacts/citizen/scripting/v8/console.js
2026-03-29 21:41:17 +03:00

363 lines
10 KiB
JavaScript

// Global console
(function (global) {
const monitorMode = (GetConvar('monitormode', 'false') == 'true') && IsDuplicityVersion();
const percent = '%'.charCodeAt(0);
const stringSpecifier = 's'.charCodeAt(0);
const decimalSpecifier = 'd'.charCodeAt(0);
const floatSpecifier = 'f'.charCodeAt(0);
const oSmallSpecifier = 'o'.charCodeAt(0);
const oBigSpecifier = 'O'.charCodeAt(0);
const typedArrays = [
Uint8Array,
Uint16Array,
Uint32Array,
Uint8ClampedArray,
Int8Array,
Int16Array,
Int32Array,
Float32Array,
Float64Array
];
class AssertionError extends Error {
constructor(options) {
if (typeof options !== 'object' || options === null) {
throw new TypeError('ERR_INVALID_ARG_TYPE', 'options', 'object');
}
if (options.message) {
super(options.message);
} else {
super(
`${format(options.actual).slice(0, 128)} ` +
`${options.operator} ${format(options.expected).slice(0, 128)}`
);
}
this.generatedMessage = !options.message;
this.name = 'AssertionError [ERR_ASSERTION]';
this.code = 'ERR_ASSERTION';
this.actual = options.actual;
this.expected = options.expected;
this.operator = options.operator;
Error.captureStackTrace(this, options.stackStartFunction);
}
}
function indentNewLines(str, indent) {
return str.split('\n').map(s => indent + s).join('\n');
}
function formatMap(map) {
const lines = ['Map {'];
for (const [key, value] of map) {
const keyFormatted = format(key);
const valueFormatted = format(value);
lines[lines.length] = indentNewLines(`${keyFormatted} => ${valueFormatted}`, ' ');
}
return lines.concat('}').join('\n');
}
function formatSet(set) {
const lines = ['Set {'];
for (const value of set) {
lines[lines.length] = indentNewLines(format(value), ' ');
}
return lines.concat('}').join('\n');
}
function isTypedArray(array) {
for (const type of typedArrays) {
if (array instanceof type) {
return true;
}
}
return false;
}
function formatTypedArray(array) {
return `${array.constructor.name}[ ${array.join(' ')} ]`;
}
function formatValue(arg) {
switch (true) {
case arg === null:
return 'null';
case arg === undefined:
return 'undefined'
case arg instanceof WeakMap:
return 'WeakMap {}';
case arg instanceof WeakSet:
return 'WeakSet {}';
case arg instanceof Map:
return formatMap(arg);
case arg instanceof Set:
return formatSet(arg);
case isTypedArray(arg):
return formatTypedArray(arg);
case Array.isArray(arg):
case arg.toString() === "[object Object]":
let cache = [];
let out = JSON.stringify(arg, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) return;
cache.push(value);
}
return value;
}, 2);
cache = null;
return out;
case arg.toString() === "[object Error]" || arg instanceof Error:
return arg.stack || Error.prototype.toString.call(arg);
default:
return arg.toString();
}
}
function formatSpecifier(specifier, value) {
switch (specifier) {
case stringSpecifier:
return '' + value;
case decimalSpecifier:
return parseInt(value, 10).toString();
case floatSpecifier:
return parseFloat(value).toString();
case oSmallSpecifier:
case oBigSpecifier:
return formatValue(value);
default:
return false;
}
}
/**
* WHATWG Spec compliant formatter
*
* @see https://console.spec.whatwg.org/#formatter
* @param {*} message
* @param {*} args
*/
function format(message = undefined, ...args) {
const totalArgs = args.length;
if (totalArgs === 0) {
return formatValue(message);
}
const result = [];
let usedArgs = 0;
if (typeof message !== "string") {
result[result.length] = formatValue(message);
} else {
const messageLastIndex = message.length - 1;
let formattedMessage = '';
let i = 0;
for (; i < messageLastIndex; i++) {
const char = message.charAt(i);
if (char.charCodeAt(0) === percent && totalArgs > usedArgs) {
const result = formatSpecifier(message.charCodeAt(i + 1), args[usedArgs]);
if (result !== false) {
formattedMessage += result;
i++;
usedArgs++;
continue;
}
}
formattedMessage += char;
}
if (i === messageLastIndex) {
formattedMessage += message[messageLastIndex];
}
result[result.length] = formattedMessage;
}
return result.concat(args.slice(usedArgs).map(formatValue)).join(' ');
}
class Console {
constructor() {
this._trace = global['Citizen'].trace || global['print'];
this._timers = new Map();
this._counters = new Map();
// TODO: Improve the sync console output.
this.log = (monitorMode) ? this.logSync.bind(this) : this.log.bind(this);
this.info = this.info.bind(this);
this.warn = this.warn.bind(this);
this.time = this.time.bind(this);
this.clear = this.clear.bind(this);
this.group = this.group.bind(this);
this.error = this.error.bind(this);
this.trace = this.trace.bind(this);
this.table = this.table.bind(this);
this.dirxml = this.dirxml.bind(this);
this.select = this.select.bind(this);
this.assert = this.assert.bind(this);
this.timeEnd = this.timeEnd.bind(this);
this.groupEnd = this.groupEnd.bind(this);
this.groupCollapsed = this.groupCollapsed.bind(this);
this.msIsIndependentlyComposed = this.msIsIndependentlyComposed.bind(this);
}
table() {
this.error('console.table is not implemented yet');
}
dirxml() {
this.error('console.dirxml is not implemented yet');
}
clear() {
this.error('console.clear is not implemented yet');
}
group() {
this.error('console.group is not implemented yet');
}
groupCollapsed() {
this.error('console.groupCollapsed is not implemented yet');
}
groupEnd() {
this.error('console.groupEnd is not implemented yet');
}
msIsIndependentlyComposed() {
this.error('console.msIsIndependentlyComposed is not implemented yet');
}
select() {
this.error('console.select is not implemented yet');
}
log(message = undefined, ...optionalParams) {
this._trace(format(message, ...optionalParams));
}
logSync(message = undefined, ...optionalParams) {
process.stdout.write(format(message, ...optionalParams) + "\n");
}
debug(message = undefined, ...optionalParams) {
this._trace('Debug: ' + format(message, ...optionalParams));
}
info(message = undefined, ...optionalParams) {
this._trace('Info: ' + format(message, ...optionalParams));
}
warn(message = undefined, ...optionalParams) {
this._trace('^3Warning: ' + format(message, ...optionalParams) + '^7');
}
error(message = undefined, ...optionalParams) {
this._trace('^1Error: ' + format(message, ...optionalParams) + '^7');
}
trace(message = undefined, ...optionalParams) {
const err = {
name: 'Trace',
message: format(message, ...optionalParams)
};
Error.captureStackTrace(err, this.trace);
this._trace(err.stack);
}
dir(item, options) {
this._trace('Dir: ', formatValue(item));
}
count(label) {
if (label === undefined) {
label = 'default'
}
const counter = this._counters.get(label) ? this._counters.get(label) + 1 : 1;
this._counters.set(label, counter);
this.log(`${label}: ${counter}`);
}
countReset(label) {
if (label === undefined) {
label = 'default'
}
if (this._counters.get(label) === undefined) {
this.warn(`Counter "${label}" doesn't exist.`);
return
}
this._counters.set(label, undefined);
this.log(`${label}: 0`);
}
time(label) {
this._timers.set(label, Citizen.getTickCount());
}
timeEnd(label) {
if (!this._timers.has(label)) {
this.warn(`No such label ${label} for console.timeEnd()`);
return;
}
const duration = Citizen.getTickCount() - this._timers.get(label);
this.log(`${label}: ${duration} ms`);
this._timers.delete(label);
}
assert(expression, ...args) {
if (!expression) {
throw new AssertionError({
actual: expression,
expected: true,
message: format.apply(null, args),
operator: '==',
stackStartFunction: this.assert
});
}
}
}
global.console = new Console();
})(this || globalThis);