todo-txt-cli
Version:
A CLI for todo.txt files - http://todotxt.org/
85 lines (74 loc) • 2.17 kB
JavaScript
/**
* @typedef {(a: Record<string, any>, b: Record<string, any>) => number} ObjectSorter
*/
/**
* @typedef {(property: string) => ObjectSorter} Sorter
*/
const noneSorter = (_a, _b) => 0
const undef = (a, b) => (!a && !b ? 0 : !a ? 1 : !b ? -1 : undefined)
/**
* @param {string} p
* @returns {ObjectSorter}
*/
export const numSorter = (p) => (a, b) => undef(a[p], b[p]) ?? a[p] - b[p]
/**
* keeps sorting order if both object use property `p`
* @param {string} p
* @returns {ObjectSorter}
*/
export const boolSorter = (p) => (a, b) =>
a[p] && b[p] ? 1 : a[p] ? 1 : b[p] ? -1 : 0
/**
* @param {string} p
* @returns {ObjectSorter}
*/
export const stringSorter = (p) => (a, b) =>
undef(a[p], b[p]) ?? a[p].localeCompare(b[p])
export const sortSet = (set) =>
set ? [...set].sort((a, b) => a.localeCompare(b)) : []
/**
* set values are sorted, concatenated and compared
* @param {string} p
* @returns {ObjectSorter}
*/
const setSorter = (p) => (a, b) => {
const aset = sortSet(a[p]).join(',')
const bset = sortSet(b[p]).join(',')
return undef(aset, bset) ?? aset.localeCompare(bset)
}
export const sorting = {
due: ['isCompleted', 'dueAt', 'priority', 'projects', 'contexts', 'task'],
pri: ['isCompleted', 'priority', 'dueAt', 'task'],
prj: ['isCompleted', 'projects', 'priority', 'dueAt', 'contexts', 'task'],
con: ['isCompleted', 'contexts', 'priority', 'dueAt', 'projects', 'task'],
task: ['isCompleted', 'task']
}
/** @type {Record<string, ObjectSorter>} */
const _sorters = {
id: stringSorter('id'),
contexts: setSorter('contexts'),
createdAt: numSorter('createdAt'),
dueAt: numSorter('dueAt'),
isCompleted: boolSorter('isCompleted'),
priority: stringSorter('priority'),
projects: setSorter('projects'),
task: stringSorter('task')
}
/**
* @param {string[]} sortOrder
* @param {Record<string,ObjectSorter>} [sorters]
* @returns
*/
export const sorter =
(sortOrder, sorters = _sorters) =>
(a, b) => {
let result = 0
for (const prop of sortOrder) {
let fn = sorters[prop] || noneSorter
result = fn(a, b)
if (result !== 0) {
break
}
}
return result
}