mauss
Version:
lightweight, modular, type-safe utilities
143 lines (142 loc) • 4.82 kB
JavaScript
/** Compares nullish values, sorting `null` and `undefined` to the end */
export function undefined(x, y) {
if (x == null && y == null)
return 0;
return (x == null && 1) || (y == null && -1) || 0;
}
/** Compares boolean values, prioritizing `true` over `false` */
export function boolean(x, y) {
return +y - +x;
}
/**
* Put `(x, y)` for bigger number first, and `(y, x)` for smaller number first
* @returns `y - x` which defaults to descending order
*/
export function number(x, y) {
return y - x;
}
/** Compares bigint values, defaults to ascending order */
export function bigint(x, y) {
return x < y ? -1 : x > y ? 1 : 0;
}
/** Compares symbols using its string values */
export function symbol(x, y) {
return x.toString().localeCompare(y.toString());
}
/** Compares string values using `.localeCompare` */
export function string(x, y) {
for (const [pattern, exp] of Object.entries(patterns)) {
const fn = { date, time }[pattern.split(':')[0]];
if (exp.test(x) && exp.test(y) && fn)
return fn(x, y);
}
return x.localeCompare(y);
}
/** Compares generic object values using {@link inspect} */
export function object(x, y) {
if (x === null)
return 1;
if (y === null)
return -1;
return inspect(x, y);
}
const primitives = { string, number, bigint, boolean, symbol, undefined };
/** Compares anything with anything */
export function wildcard(x, y) {
if (x == null)
return 1;
if (y == null)
return -1;
const [xt, yt] = [typeof x, typeof y];
if (xt === 'function')
return 0;
if (xt !== yt || xt === 'object') {
const xs = JSON.stringify(x);
const ys = JSON.stringify(y);
return string(xs, ys);
}
return primitives[xt](x, y);
}
/**
* Recursively compares common object properties until the first difference is found
* @returns `0` if both objects are identical or completely different, otherwise their respective primitive difference
*/
export function inspect(x, y) {
const common = [...new Set([...Object.keys(x), ...Object.keys(y)])].filter((k) => k in x && k in y && typeof x[k] === typeof y[k] && x[k] !== y[k]);
for (let i = 0, key = common[i], data = typeof x[key]; i < common.length && x[key] !== null && y[key] !== null; key = common[++i], data = typeof x[key]) {
if (data === 'function')
continue;
if (data === 'object')
return inspect(x[key], y[key]);
const constrained = primitives[data];
if (data in primitives)
return constrained(x[key], y[key]);
}
return 0;
}
// ---- patterned ----
const patterns = {
'date:complete': /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,
'date:time': /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)/,
date: /\d{4}-[01]\d-[0-3]\d/,
time: /[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)/,
};
/** Compares date or date-like string values */
export function date(x, y) {
return new Date(y).getTime() - new Date(x).getTime();
}
/** Compares time or time-like string values */
export function time(x, y) {
return Date.parse(`2017/08/28 ${y}`) - Date.parse(`2017/08/28 ${x}`);
}
/**
* A higher-order function that accepts a string as an identifier and an optional comparator function, it breaks up the identifier described by the dot (`.`) character and returns a curried function that accepts `(x, y)` with an object defined by the identifier.
*
* The optional comparator can be used when you have an existing custom sort function, e.g. in combination with `compare.order` to sort a set of string.
*
* @example
*
* ```javascript
* import * as compare from 'mauss/compare';
*
* const posts = [
* { date: { month: 'March' } },
* { date: { month: 'June' } },
* { date: { month: 'May' } },
* { date: { month: 'April' } },
* { date: { month: 'January' } },
* { date: { month: 'June' } },
* { date: { month: 'February' } },
* ];
*
* const months = [
* 'January',
* 'February',
* 'March',
* 'April',
* 'May',
* 'June',
* 'July',
* 'August',
* 'September',
* 'October',
* 'November',
* 'December',
* ];
*
* posts.sort(compare.key('date.month', compare.order(months)));
* ```
*/
export function key(identifier, comparator) {
const trail = identifier.split('.');
const drill = (o) => trail.reduce((ret, prop) => ret[prop], o);
return (x, y) => (comparator || wildcard)(drill(x), drill(y));
}
/**
* A higher-order function that accepts an array of strings and returns a comparator function that sorts the strings in the order they appear in the array.
*/
export function order(weights) {
const m = {};
weights.forEach((v, i) => (m[v] = i));
return (x, y) => m[x] - m[y];
}