UNPKG

todo-txt-cli

Version:

A CLI for todo.txt files - http://todotxt.org/

205 lines (188 loc) 4.41 kB
/** * @template T * @param {T[]} arr * @returns {T[]} */ export const uniq = (arr) => [...new Set(arr)] /** * @returns {Date} */ export const today = () => { const now = new Date() now.setHours(0, 0, 0, 0) return now } export const isDate = (date) => !isNaN(new Date(date).getTime()) /** * Checks if `value` is an integer * @param {any} value The `value` to check * @returns {boolean} Returns `true` if `value` is an integer, otherwise `false` */ export const isInteger = (value) => Number.isSafeInteger(Number(value)) /** * @param {number} val * @param {number} [max=2] * @returns {string} */ const pad = (val, max = 2) => String(val).padStart(max, '0') /** * @param {Date|undefined} date * @returns {string} */ export const formatDate = (date) => date ? [ pad(date.getFullYear(), 4), pad(date.getMonth() + 1), pad(date.getDate()) ].join('-') : '' /** * @param {Date} date * @param {string|number} count * @param {string} [unit] * @returns {Date} */ export const changeDate = (date, count, unit) => { const _date = new Date(date) const num = Number(count) switch (unit) { // year case 'y': { const year = _date.getFullYear() _date.setFullYear(year + num) break } // month case 'm': { const month = _date.getMonth() _date.setMonth(month + num) break } // week case 'w': { const day = _date.getDate() _date.setDate(day + 7 * num) break } // day default: { const day = _date.getDate() _date.setDate(day + num) break } } return _date } const RE_MOVE_DATE = /([-+]?\d+)(y|m|w|d)/ /** * @param {Date} date * @param {string} val * @returns {Date|undefined} */ export const moveDate = (date, val) => { if (!date || !val || typeof val !== 'string') { return } const units = val.split(RE_MOVE_DATE) units.splice(-1 * (units.length % 3)) if (!units.length) { return } for (let i = 0; i < units.length; i += 3) { const [count, unit] = units.slice(i + 1, i + 3) date = changeDate(date, count, unit) } return date } const RE_ISO_DATE = /^\d{4}-\d{2}-\d{2}$/ const RE_MM_DD_DATE = /^(\d{1,2})-(\d{1,2})$/ const RE_DD_MM_DATE = /^(\d{1,2})[\.\/](\d{1,2})\.?$/ /** * @param {string} month * @param {string} day * @param {Date} now * @returns {Date|undefined} */ const nonYearDate = (month, day, now) => { const _month = Number(month) const _day = Number(day) if (!isInteger(_month) || _month > 12 || !isInteger(_day) || _day > 31) { return } const year = now.getFullYear() const date = new Date(year, _month - 1, _day) if (date >= now) { return date } return new Date(year + 1, _month - 1, _day) } /** * @param {string} val * @param {Date} [now] * @returns {Date|undefined} */ export const isoOrRelativeDate = (val, now = today()) => { if (!val || val === 'del') { return } if (RE_ISO_DATE.test(val)) { return new Date(val) } if (RE_MM_DD_DATE.test(val)) { const [_, month, day] = RE_MM_DD_DATE.exec(val) || [] return nonYearDate(month, day, now) } if (RE_DD_MM_DATE.test(val)) { const [_, day, month] = RE_DD_MM_DATE.exec(val) || [] return nonYearDate(month, day, now) } return moveDate(now, val) } /** * escape regular expression; does not escape chars `|^$()` * @param {*} string * @returns */ export const escapeRegExp = (string) => string.replace(/[\\{}[\]+*?.]/g, '\\$&') /** * @param {string} prio * @returns {RegExp|undefined} */ export const priorityToRegExp = (prio) => { if (/^[A-Z.]$/i.test(prio)) { return new RegExp(prio.toUpperCase()) } if (/^[A-Z]-[A-Z]$/i.test(prio)) { const [l, h] = prio.toUpperCase().split('-').sort() return new RegExp(`[${l}-${h}]`) } } export const digits = (length) => Math.ceil(Math.log10(length + 1)) const RE_RANGE = /^(\d+)-(\d+)$/ /** * @param {string[]|[]|undefined} ids * @returns {string[]|[]} */ export const range = (ids) => { if (!ids?.length) { return [] } const aIds = [] for (const id of ids) { const m = RE_RANGE.exec(id) if (!m) { aIds.push(id) continue } const [low, high] = m.slice(1).map((n) => Number(n)) if (low > high) { // ignore wrong order continue } for (let i = low; i <= high; i += 1) { aIds.push('' + i) } } return aIds }