Add utility functions

This commit is contained in:
Zack Rauen 2022-09-29 22:33:54 -04:00
parent 51a4a0eb09
commit 73116fbd34
9 changed files with 237 additions and 1 deletions

View File

@ -0,0 +1,2 @@
type ObjectLiteral = {[index: string|number]: unknown};
type AnyFunction = (...args: unknown[]) => unknown;

View File

@ -0,0 +1,19 @@
export function getKeys(object: ObjectLiteral) {
const keys: Array<keyof typeof object> = [];
for (const key in object) keys.push(key);
return keys;
}
export default function cloneObject(target: ObjectLiteral, newObject: ObjectLiteral = {}, keys?: Array<keyof ObjectLiteral>) {
if (!Array.isArray(keys)) keys = getKeys(target);
return keys.reduce((clone, key) => {
if (typeof(target[key]) === "object" && !Array.isArray(target[key]) && target[key] !== null) clone[key] = cloneObject(target[key] as ObjectLiteral, {});
else if (typeof target[key] === "function") clone[key] = (<AnyFunction>target[key]).bind(target);
else clone[key] = target[key];
return clone;
}, newObject);
}

View File

@ -0,0 +1,16 @@
/**
* Returns a function, that, as long as it continues to be invoked, will not
* be triggered. The function will be called after it stops being called for
* N milliseconds.
*
*
* @param {function} executor
* @param {number} delay
*/
export default function debounce(executor: AnyFunction, delay: number) {
let timeout: NodeJS.Timeout;
return function(...args: unknown[]) {
clearTimeout(timeout);
timeout = setTimeout(() => executor(...args), delay);
};
}

View File

@ -0,0 +1,29 @@
/**
* Deep extends an object with a set of other objects. Objects later in the list
* of `extenders` have priority, that is to say if one sets a key to be a primitive,
* it will be overwritten with the next one with the same key. If it is an object,
* and the keys match, the object is extended. This happens recursively.
* @param {object} extendee - Object to be extended
* @param {...object} extenders - Objects to extend with
* @returns {object} - A reference to `extendee`
*/
export default function extend(extendee: ObjectLiteral, ...extenders: ObjectLiteral[]) {
for (let i = 0; i < extenders.length; i++) {
for (const key in extenders[i]) {
if (extenders[i].hasOwnProperty(key)) {
if (typeof extendee[key] === "object" && typeof extenders[i][key] === "object") {
extend(extendee[key] as ObjectLiteral, extenders[i][key] as ObjectLiteral);
}
else if (typeof extenders[i][key] === "object") {
extendee[key] = {};
extend(extendee[key] as ObjectLiteral, extenders[i][key] as ObjectLiteral);
}
else {
extendee[key] = extenders[i][key];
}
}
}
}
return extendee;
}

View File

@ -0,0 +1,37 @@
type TreeFilter = (o: unknown) => boolean
/**
* Finds a value, subobject, or array from a tree that matches a specific filter.
* @param {object} tree Tree that should be walked
* @param {callable} searchFilter Filter to check against each object and subobject
* @param {object} options Additional options to customize the search
* @param {Array<string>|null} [options.walkable=null] Array of strings to use as keys that are allowed to be walked on. Null value indicates all keys are walkable
* @param {Array<string>} [options.ignore=[]] Array of strings to use as keys to exclude from the search, most helpful when `walkable = null`.
*/
export default function findInTree(tree: ObjectLiteral | null, searchFilter: TreeFilter | string, {walkable = null, ignore = []}: {walkable?: string[] | null, ignore?: string[]} = {}): unknown {
if (typeof searchFilter === "string") {
if (tree?.hasOwnProperty(searchFilter)) return tree[searchFilter];
}
else if (searchFilter(tree)) {
return tree;
}
if (typeof tree !== "object" || tree == null) return undefined;
let tempReturn: unknown;
if (tree instanceof Array) {
for (const value of tree) {
tempReturn = findInTree(value, searchFilter, {walkable, ignore});
if (typeof tempReturn != "undefined") return tempReturn;
}
}
else {
const toWalk = walkable == null ? Object.keys(tree) : walkable;
for (const key of toWalk) {
if (typeof(tree[key]) == "undefined" || ignore.includes(key)) continue;
tempReturn = findInTree(tree[key] as ObjectLiteral, searchFilter, {walkable, ignore});
if (typeof tempReturn != "undefined") return tempReturn;
}
}
return tempReturn;
}

View File

@ -0,0 +1,17 @@
/**
* Format strings with placeholders (`{{placeholder}}`) into full strings.
* Quick example: `PluginUtilities.formatString("Hello, {{user}}", {user: "Zerebos"})`
* would return "Hello, Zerebos".
* @param {string} string - string to format
* @param {object} values - object literal of placeholders to replacements
* @returns {string} the properly formatted string
*/
export default function formatString(string: string, values: {[index: string]: string | object}) {
for (const val in values) {
let replacement = values[val];
if (Array.isArray(replacement)) replacement = JSON.stringify(replacement);
if (typeof(replacement) === "object" && replacement !== null) replacement = replacement.toString();
string = string.replace(new RegExp(`{{${val}}}`, "g"), replacement);
}
return string;
}

View File

@ -0,0 +1,85 @@
/* eslint-disable no-console */
/**
* List of logging types.
*/
enum LogTypes {
ERROR = "error",
DEBUG = "debug",
LOG = "log",
WARN = "warn",
INFO = "info"
};
export default class Logger {
/**
* Logs an error using a collapsed error group with stacktrace.
*
* @param {string} module - Name of the calling module.
* @param {string} message - Message or error to have logged.
* @param {Error} error - Error object to log with the message.
*/
static stacktrace(module: string, message: string, error: Error) {
console.error(`%c[${module}]%c ${message}\n\n%c`, "color: #3a71c1; font-weight: 700;", "color: red; font-weight: 700;", "color: red;", error);
}
/**
* Logs using error formatting. For logging an actual error object consider {@link module:Logger.stacktrace}
*
* @param {string} module - Name of the calling module.
* @param {string} message - Messages to have logged.
*/
static err(module: string, ...message: string[]) {Logger._log(module, message, LogTypes.ERROR);}
/**
* Alias for "err"
* @param {string} module NAme of the calling module
* @param {...any} message Messages to have logged.
*/
static error(module: string, ...message: string[]) {Logger._log(module, message, LogTypes.ERROR);}
/**
* Logs a warning message.
*
* @param {string} module - Name of the calling module.
* @param {...any} message - Messages to have logged.
*/
static warn(module: string, ...message: string[]) {Logger._log(module, message, LogTypes.WARN);}
/**
* Logs an informational message.
*
* @param {string} module - Name of the calling module.
* @param {...any} message - Messages to have logged.
*/
static info(module: string, ...message: string[]) {Logger._log(module, message, LogTypes.INFO);}
/**
* Logs used for debugging purposes.
*
* @param {string} module - Name of the calling module.
* @param {...any} message - Messages to have logged.
*/
static debug(module: string, ...message: string[]) {Logger._log(module, message, LogTypes.DEBUG);}
/**
* Logs used for basic loggin.
*
* @param {string} module - Name of the calling module.
* @param {...any} message - Messages to have logged.
*/
static log(module: string, ...message: string[]) {Logger._log(module, message);}
/**
* Logs strings using different console levels and a module label.
*
* @param {string} module - Name of the calling module.
* @param {any|Array<any>} message - Messages to have logged.
* @param {module:Logger.LogTypes} type - Type of log to use in console.
*/
static _log(module: string, message: string|string[], type: LogTypes = LogTypes.LOG) {
if (!Array.isArray(message)) message = [message];
console[type](`%c[BetterDiscord]%c [${module}]%c`, "color: #3E82E5; font-weight: 700;", "color: #3a71c1;", "", ...message);
}
}

View File

@ -0,0 +1,31 @@
/**
* Generates an automatically memoizing version of an object.
* @param {Object} object - object to memoize
* @returns {Proxy} the proxy to the object that memoizes properties
*/
export default function memoizeObject(object: ObjectLiteral) {
const proxy = new Proxy(object, {
get: function(obj, mod) {
if (typeof(mod) === "symbol") return null;
if (!obj.hasOwnProperty(mod)) return undefined;
if (Object.getOwnPropertyDescriptor(obj, mod)?.get) {
const value = obj[mod];
delete obj[mod];
obj[mod] = value;
}
return obj[mod];
},
set: function(obj, mod, value) {
if (typeof(mod) === "symbol") return false;
if (obj.hasOwnProperty(mod)) return false;
obj[mod] = value;
return true;
}
});
Object.defineProperty(proxy, "hasOwnProperty", {value: function(prop: string) {
return this[prop] !== undefined;
}});
return proxy;
}

View File

@ -41,5 +41,5 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["./src/types.ts"]
// "include": ["./src/types.ts"]
}