@kwaeri/developer-tools
Version:
The kwaeri developer tools. A minimalist tooling for kwaeri application development.
259 lines • 11 kB
JavaScript
/**
* SPDX-PackageName: kwaeri/developer-tools
* SPDX-PackageVersion: 0.5.0
* SPDX-FileCopyrightText: © 2014 - 2022 Richard Winters <kirvedx@gmail.com> and contributors
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR MIT
*/
;
/**
* Class for implementing the Kwaeri Developer Tools (kdt)
*/
export class kdt {
/**
* A list of types for easing the implementation of our .type() method
*
* @var { object }
*/
classmap = {
"[object Boolean]": "boolean",
"[object Number]": "number",
"[object String]": "string",
"[object Function]": "function",
"[object Array]": "array",
"[object Date]": "date",
"[object RegExp]": "regexp",
"[object Object]": "object",
};
/**
* Class constructor
*/
constructor() {
}
/**
* Checks the type of any entity
*
* @param { any } query The query to be type checked
*
* @returns { string } String The lowercase short variant of the type the entity evaluated to.
*/
type(...args) {
// The working code below yields faster performance than the following snippet by almost 1.5 - 2 times the amount
// ( http://stackoverflow.com/a/12022491 ):
//
// return ( o === null ) ? String( o ) : _type[ toString.call( o ) ] || "object";
if (arguments[0] === null)
return String(arguments[0]);
else
return this.classmap[toString.call(arguments[0])] || "unknown";
}
/**
* Checks if an object is empty or not
*
* @param { object } The object which we check is empty
*
* @return { boolean } true if empty, false otherwise
*/
empty(...args) {
return !Object.keys(arguments[0]).length && arguments[0].constructor === Object;
}
/**
* An extend method that mimics jQuery and it's deep copy trait
*
* @param { object } a
* @param { object } b
* @param { object } x
*
* @return { object } a extended by b through x
*/
extend(...args) {
let returnable = arguments[0];
for (let i = 1; i < arguments.length; i++) {
for (let key in arguments[i]) {
if (arguments[i].hasOwnProperty(key)) {
if (returnable.hasOwnProperty(key) && typeof returnable[key] === 'object' && typeof arguments[i][key] === 'object')
returnable[key] = this.extend(returnable[key], arguments[i][key]);
else if (!(returnable.hasOwnProperty(key)))
returnable[key] = arguments[i][key];
}
}
}
return returnable;
}
/**
* Method to apply mix-in [partial] classes to a base class. Based entirely on the official documentation
* for the 'Alternate Version' - as described at:
* https://www.typescriptlang.org/docs/handbook/mixins.html#alternative-pattern
*
* @param { any } derivedCtor The derived class being extended. Must be the named interface of the class.
* @param { any } constructors The base classes to inherit from. Must be the named interfaces of the classes.
*
* @returns { void }
*/
compose(derivedCtor, constructors) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name));
});
});
}
;
/**
* Method for iterating over an object or list and performing an action for each index
*
* @param { object|array } iteratee The object or array being iterated over
* @param { function } applicator The action to apply to each index of the iteratee, can be suppieda key/value arguments
*
* @return { boolean } Returns true if all went well, otherwise false
*/
each(iteratee, applicator) {
// Ensure the second argument is a function
if (this.type(applicator) === "function") {
// Get the number of arguments expected
const argumentCount = applicator.length;
// Determine the type of the supplied object
switch (this.type(iteratee)) {
case "htmlcollection":
case "nodelist":
case "array":
{
// Iterate over the array
for (let i = 0; i < iteratee.length; i++) {
// Provide the index as the key to the provided function
if (argumentCount > 1) {
// Send the key and value
applicator(i, iteratee[i]);
}
else if (argumentCount === 1) {
// Send only the value
applicator(iteratee[i]);
}
else {
applicator();
}
}
}
break;
case "object":
{
// Iterate over the objects properties
for (let property in iteratee) {
// Supply the property name as the key to the provided function
if (argumentCount > 1)
applicator(property, iteratee[property]); // Send the key and value
else if (argumentCount === 1)
applicator(iteratee[property]); // Send only the value
else
applicator();
}
}
break;
default: // First argument invalid, must be an Object or an associative Array
console.log('Something went wrong with .each()', 'background: #000; color: #027;');
}
// Everything went well
return true;
}
// Second argument invalid, must be a function that takes 2 arguments
return false;
}
/**
* A specialized method for checking if an entity/variable is a number
*
* @param { any } entity
*
* @return { boolean } Returns true if the entity is a number, otherwise false
*/
isNumber(entity) {
return !isNaN(parseFloat(entity)) && isFinite(entity);
}
/**
* Helper method to determine if a property [chain] exists in an object
*
* @param { object } container The object for - and in - which the property chain is being checked.
* @param { string } propertyChain The property chain being tested for. Nested properties can be notated with periods (x.y)
* @returns True if the property chain exists, false if it does not.
*/
has(container, propertyChain) {
return propertyChain.split(".").every((property) => {
if (this.type(container) !== 'object' || container == null || !(property in container))
return false;
container = container[property];
return true;
});
}
/**
* Gets the specified value from the provided object if it exists, or returns the default value (or null)
*
* @param { Object } base The object a value is requested from
* @param { string } property The name of the property that should belong to the object a value is requested from
* @param { any } defaultValue A defaultvalue to return in the event either the provided object or requested property does not exist
*
* @returns { any }
*/
get(base = {}, property, defaultValue = null) {
if (!this.empty(base)) {
if (this.type(property) === 'string' || this.type(property) === 'number') {
if (base.hasOwnProperty(property))
return base[property];
else {
// Check if property string indicates a nested property:
const level = property.split(".");
// Do we need to be careful?
let returnable = base;
// Navigate the provided object until we've reached the
// level of recursion indicated by the provided property
// string:
for (let i = 0; i < level.length; i++) {
// If we reach an end prematurely, return the default value
// (or null, by default):
if (this.type(returnable) !== "object" || returnable == null || !(level[i] in returnable))
return defaultValue;
returnable = returnable[level[i]];
}
// Return the fetched property
return returnable;
}
}
}
else
return defaultValue;
}
/**
* Sets the specified value to the specified property on the provided object
*
* @param { Object|Array<any> } base The provided object for which the specified value is to be set for the specified property
* @param { string|number }property The property of the provided object for which to set the specified value
* @param { any } value The value to set for the specified property of the provided object
*
* @returns { any }
*/
set(base = {}, property = "", value = null) {
// Let's always return a new object:
let returnable = base;
if ((this.type(property) === 'string' && property !== "") || (this.type(property) === 'number' && property >= 0)) {
switch (this.type(base)) {
case 'object':
{
// Just in case, let's allow set to match get's allowable
// property semantics:
const level = property.split(".");
// Whether caller did pass a nested property indicator or not,
// for each level of recursion:
for (let i = 0; i < level.length; i++) { // If it's the last, set the value to the property,
// otherwise, just create an object to facilitate the recursion:
returnable[level[i]] = (i === (level.length - 1)) ? value : {};
}
}
break;
case 'array':
{
if (this.type(property) === 'number')
returnable[property] = value;
}
break;
}
}
return returnable;
}
}
//# sourceMappingURL=kdt.mjs.map