192 lines
6.2 KiB
Plaintext
192 lines
6.2 KiB
Plaintext
var util = this
|
||
util[cell.DOC] = `
|
||
A collection of general-purpose utility functions for object manipulation, merging,
|
||
deep copying, safe property access, etc.
|
||
`
|
||
|
||
util.deepfreeze = function (obj) {
|
||
for (var key in obj) {
|
||
if (typeof obj[key] === "object") Object.deepfreeze(obj[key])
|
||
}
|
||
Object.freeze(obj)
|
||
}
|
||
util.deepfreeze[cell.DOC] = `
|
||
:param obj: The object to recursively freeze.
|
||
:return: None
|
||
Recursively freeze an object and all of its nested objects so they cannot be modified.
|
||
`
|
||
|
||
util.dainty_assign = function (target, source) {
|
||
Object.keys(source).forEach(function (k) {
|
||
if (typeof source[k] === "function") return
|
||
if (!(k in target)) return
|
||
if (Array.isArray(source[k])) target[k] = deep_copy(source[k])
|
||
else if (Object.isObject(source[k])) Object.dainty_assign(target[k], source[k])
|
||
else target[k] = source[k]
|
||
})
|
||
}
|
||
util.dainty_assign[cell.DOC] = `
|
||
:param target: The target object whose keys may be updated.
|
||
:param source: The source object containing new values.
|
||
:return: None
|
||
Copy non-function properties from source into matching keys of target without overwriting
|
||
keys that don't exist in target. Arrays are deep-copied, and objects are recursively assigned.
|
||
`
|
||
|
||
util.get = function (obj, path, defValue) {
|
||
if (!path) return undefined
|
||
var pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g)
|
||
var result = pathArray.reduce((prevObj, key) => prevObj && prevObj[key], obj)
|
||
return result === undefined ? defValue : result
|
||
}
|
||
util.get[cell.DOC] = `
|
||
:param obj: The object to traverse.
|
||
:param path: A string like "a.b.c" or an array of path segments.
|
||
:param defValue: The default value if the property is undefined.
|
||
:return: The nested property or defValue.
|
||
Safely retrieve a nested property from obj at path (array or dot-string).
|
||
Returns defValue if the property is undefined.
|
||
`
|
||
|
||
util.isEmpty = function(o) {
|
||
return Object.keys(o).length === 0
|
||
}
|
||
util.isEmpty[cell.DOC] = `
|
||
:param o: The object to check.
|
||
:return: Boolean indicating if the object is empty.
|
||
Return true if the object has no own properties, otherwise false.
|
||
`
|
||
|
||
util.dig = function (obj, path, def = {}) {
|
||
var pp = path.split(".")
|
||
for (var i = 0; i < pp.length - 1; i++) {
|
||
obj = obj[pp[i]] = obj[pp[i]] || {}
|
||
}
|
||
obj[pp[pp.length - 1]] = def
|
||
return def
|
||
}
|
||
util.dig[cell.DOC] = `
|
||
:param obj: The root object to modify.
|
||
:param path: A dot-string specifying nested objects to create.
|
||
:param def: The value to store in the final path component, default {}.
|
||
:return: The assigned final value.
|
||
Ensure a nested path of objects exists inside obj; create objects if missing, and set
|
||
the final path component to def.
|
||
`
|
||
|
||
util.access = function (obj, name) {
|
||
var dig = name.split(".")
|
||
for (var i of dig) {
|
||
obj = obj[i]
|
||
if (!obj) return undefined
|
||
}
|
||
return obj
|
||
}
|
||
util.access[cell.DOC] = `
|
||
:param obj: The object to traverse.
|
||
:param name: A dot-string path (e.g. "foo.bar.baz").
|
||
:return: The value at that path, or undefined if missing.
|
||
Traverse obj by dot-separated path name, returning the final value or undefined
|
||
if any step is missing.
|
||
`
|
||
|
||
util.mergekey = function (o1, o2, k) {
|
||
if (!o2) return
|
||
if (typeof o2[k] === "object") {
|
||
if (Array.isArray(o2[k])) o1[k] = deep_copy(o2[k])
|
||
else {
|
||
if (!o1[k]) o1[k] = {}
|
||
if (typeof o1[k] === "object") util.merge(o1[k], o2[k])
|
||
else o1[k] = o2[k]
|
||
}
|
||
} else o1[k] = o2[k]
|
||
}
|
||
util.mergekey[cell.DOC] = `
|
||
:param o1: The target object.
|
||
:param o2: The source object.
|
||
:param k: The key to merge.
|
||
:return: None
|
||
Helper for merge, updating key k from o2 into o1. Arrays are deep-copied and objects are
|
||
recursively merged.
|
||
`
|
||
|
||
util.merge = function (target, ...objs) {
|
||
for (var obj of objs) for (var key of Object.keys(obj)) util.mergekey(target, obj, key)
|
||
return target
|
||
}
|
||
util.merge[cell.DOC] = `
|
||
:param target: The target object.
|
||
:param objs: One or more objects to merge into target.
|
||
:return: The updated target object.
|
||
Merge all passed objects into target, copying or merging each key as needed.
|
||
Arrays are deep-copied, objects are recursively merged, etc.
|
||
`
|
||
|
||
util.copy = function (proto, ...objs) {
|
||
var c = Object.create(proto)
|
||
for (var obj of objs) Object.mixin(c, obj)
|
||
return c
|
||
}
|
||
util.copy[cell.DOC] = `
|
||
:param proto: The prototype object for the new object.
|
||
:param objs: One or more objects whose properties will be mixed in.
|
||
:return: The newly created object.
|
||
Create a new object with proto as its prototype, then mix in additional objects’ properties.
|
||
`
|
||
|
||
util.obj_lerp = function(a,b,t) {
|
||
if (a.lerp) return a.lerp(b, t)
|
||
var obj = {}
|
||
Object.keys(a).forEach(function (key) {
|
||
obj[key] = a[key].lerp(b[key], t)
|
||
})
|
||
return obj
|
||
}
|
||
util.obj_lerp[cell.DOC] = `
|
||
:param a: The start object (its properties must have .lerp()).
|
||
:param b: The end object (matching properties).
|
||
:param t: Interpolation factor (0..1).
|
||
:return: A new object with interpolated properties.
|
||
Linearly interpolate between two objects a and b by factor t, assuming each property
|
||
supports .lerp().
|
||
`
|
||
|
||
util.normalizeSpacing = function normalizeSpacing(spacing) {
|
||
if (typeof spacing === 'number') {
|
||
return {l: spacing, r: spacing, t: spacing, b: spacing}
|
||
} else if (Array.isArray(spacing)) {
|
||
if (spacing.length === 2) {
|
||
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
|
||
} else if (spacing.length === 4) {
|
||
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
|
||
}
|
||
} else if (typeof spacing === 'object') {
|
||
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
|
||
} else {
|
||
return {l:0, r:0, t:0, b:0}
|
||
}
|
||
}
|
||
util.normalizeSpacing[cell.DOC] = `
|
||
:param spacing: A number, an array of length 2 or 4, or an object with l/r/t/b.
|
||
:return: An object {l, r, t, b}.
|
||
Normalize any spacing input into a {l, r, t, b} object.
|
||
`
|
||
|
||
util.guid[cell.DOC] = `
|
||
:return: A random 32-character string (hex).
|
||
Return a random 32-character hexadecimal UUID-like string (not guaranteed RFC4122-compliant).
|
||
`
|
||
|
||
util.insertion_sort[cell.DOC] = `
|
||
:param arr: The array to be sorted in-place.
|
||
:param cmp: Comparison function cmp(a,b)->Number.
|
||
:return: The same array, sorted in-place.
|
||
In-place insertion sort of an array using cmp(a,b)->Number for ordering.
|
||
`
|
||
|
||
function deep_copy(from) {
|
||
return json.decode(json.encode(from))
|
||
}
|
||
|
||
return util
|