UNPKG

todo-txt-cli

Version:

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

438 lines (393 loc) 12.9 kB
const BOLD = 'bold' const UNDERLINE = 'underline' /** * @typedef {object} Token * @property {string} text * @property {'bold'|'underline'} [mod] modifier */ /** * @param {string} [c] * @returns {boolean} */ const isSpace = (c = '') => /^[\s.,:.?!(){}[\]'"`]?$/.test(c) /** * @param {string} txt * @returns {Token[]|[]} */ export const tokenize = (txt) => { let text = '' let mod = '' const tokens = [] const switchModifier = (_mod_, prev, char, next) => { if (!mod && isSpace(prev)) { if (text) tokens.push({ text }) mod = _mod_ text = '' } else if (isSpace(next)) { tokens.push({ text, mod }) mod = '' text = '' } else { text += char } } let prev = '' for (let i = 0; i < txt.length; i++) { const char = txt[i] const next = txt[i + 1] switch (char) { case '\\': text += next i++ break case '_': switchModifier(UNDERLINE, prev, char, next) break case '*': switchModifier(BOLD, prev, char, next) break default: text += char } prev = char } if (text) { tokens.push({ text }) } return tokens } /** * @param {number} num * @returns {string} */ const spaces = (num = 0) => new Array(num).fill(' ').join('') /** * * @param {string} line * @param {{ * range?: number * indent?: number * firstIndent?: number * }} param1 * @returns */ export const lineBreak = ( line, { range = 80, indent = 8, firstIndent } = {} ) => { const words = line.split(/(\s+)/) let first = true let cnt = 0 let out = '' for (let i = 0; i < words.length; i++) { const word = words[i] const len = word.length if (cnt + len > range) { // NOTE: ensure that word.length < range - indent cnt = 0 out += '\n' i-- continue } else if (cnt === 0) { const isSpace = /^\s+$/.test(word) const _indent = first ? (firstIndent ?? indent) : indent first = false out += spaces(_indent) cnt += _indent if (isSpace) continue } out += word cnt += len } return out } const buildOptions = (options) => options.map((option) => help.optionFlags[option]) /** * @param {string} [action] * @returns {string} */ export const buildHelp = (action) => { if (action === 'help') { // build full help return [ help.header(), help.optionsHeader, ...buildOptions(Object.keys(help.optionFlags)), help.actionHeader, ...Object.values(help.actions) ].join('') } else if (!help.actions[action]) { action = help.alt[action] || 'help' } // build small help return [ help.header(action), help.optionsHeader, ...buildOptions(help.options[action]), help.actionHeader, help.actions[action] ].join('') } /** * @param {import('#colors.js').Colors} colors * @param {string} action */ export const renderHelp = (colors, action) => { const raw = buildHelp(action) const tokens = tokenize(raw) let out = '' for (const { text, mod } of tokens) { out += mod && colors[mod] ? colors[mod](text) : text } return out } // ---- export const help = { options: {}, actions: {}, alt: {} } help.header = (action = '[_action_]') => ` *SYNOPSIS* *todo* [_options_] ${action} ` help.optionsHeader = ` *OPTIONS* ` help.optionFlags = { '-@': ' *-@, --no-contexts* Hide context names in list output.\n', '-+': ' *-+, --no-projects* Hide project names in list output.\n', '-c': ' *-c, --color* Turn on color mode.\n', '-C': ' *-C, --no-color* Turn off color mode.\n', '-d': ' *-d, --dir* [_directory_] Use "todo.txt" in _directory_.\n' + ' -d without arg uses the current directory\n', '-h': ' *-h, --help* Displays this help.\n', '-l': ' *-l, --limit* [_limit_] Limit output to *limit* lines.\n' + ' -l without arg uses 20 lines as default.\n', '-r': ' *-r, --reverse* Reverse the order of list output.\n', '-s': ' *-s, --sort* _sorter_ Sort by "due" (due-date), "pri" (priorities),\n' + ' "prj" (projects) or "con" (contexts).\n', '-t': ' *-t, --timestamp* List tasks with createdAt timestamp.\n', '-T': ' *-T, --no-timestamp* Do not add timestamps.\n', '-v': ' *-v, --version* Display version info.\n' } const optsAlways = ['-c', '-C', '-d'] help.actionHeader = ` *ACTIONS* ` help.options.add = [...optsAlways, '-t', '-T'] help.actions.add = ` *add* "_task-description_" *a* "_task-description_" Adds a task with _task-description_ on its own line. add "thing i need to do +project @context" a "thing i need to do +project @context" ` help.options.append = optsAlways help.actions.append = ` *append* _line#_ _text-to-append_ *app* _line#_ _text-to-append_ Add _text-to-append_ to the end of the task on line _line#_. Quotes are optional. ` help.options.archive = optsAlways help.actions.archive = ` *archive* Moves all done tasks from "todo.txt" to "done.txt" and removes blank lines. ` help.options.del = optsAlways help.alt.rm = 'del' help.actions.del = ` *del* _line#_ [_term_] *rm* _line#_ [_term_] Deletes the task on line _line#_ in "todo.txt". If _term_ is specified, deletes only _term_ from the task. ` help.options.depri = optsAlways help.alt.dp = 'depri' help.actions.depri = ` *depri* _line#_ [_line#_ ...] *dp* _line#_ [_line#_ ...] De-prioritize (removes the priority) from the task(s) on line(s) _line#_ in "todo.txt". ` help.options.done = optsAlways help.alt.d = 'done' help.actions.done = ` *done* _line#_ [_line#_ ...] *d* _line#_ [_line#_ ...] Marks task(s) on line(s) _line#_ as done in "todo.txt". ` help.options.due = optsAlways help.actions.due = ` *due* _line#_ [_line#_ ...] _iso-date_ | _relative-date_ | del Add a due date on task in _line#_ 10 and 12 using _iso-date_ 2025-02-01: todo due 10 12 2025-10-02 If a due date is already present, it is moved to that date. Alternatively use "MM-DD" or "DD.MM". Here the current year is used. todo due 10 12 10-02 todo due 10 12 2.10 To apply a _relative-date_ of 3 months minus 1 week on task _line#_ 9 starting with today, use: todo due 9 3months-1week If a due date is present, also today is used as start date. All combinations of year, month, week, day (short form: y, m, w, d) are possible. You can also use a short syntax: todo due 9 3m-1w To delete a due date on task _line#_ 9 and 12 use: todo due 9 12 del ` help.options.edit = ['-d', '-h', '-v'] help.actions.edit = ` *edit* [_editor_] Open "todo.txt" in _editor_. Default is TextEdit on macOS, NotePad on windows, $EDITOR on linux. ` const optsList = [...optsAlways, '-@', '-+', '-l', '-r', '-t'] help.options.list = optsList help.alt.ls = 'list' help.actions.list = ` *list* [_term_ ...] *ls* [_term_ ...] Displays all open tasks that contain _term_(s) sorted by priority with line numbers. Each task must match all _term_(s) (logical AND); to display tasks that contain any _term_ (logical OR), use *'TERM1|TERM2'* (with quotes), or "TERM1\\|TERM2" (unquoted). Hides all tasks that contain TERM(s) preceded by a minus sign (i.e. "-TERM"). _term_(s) are grep-style basic regular expressions; for literal matching, put a single backslash before any [ ] \\ $ \\* . ^ and enclose the entire _term_ in single quotes, or use double backslashes and extra shell-quoting. If no _term_ specified, lists entire "todo.txt". ls "TERM1|TERM2" ls TERM1\\|TERM2 ls -TERM1 ` help.options.listall = optsList help.alt.lsa = 'listall' help.actions.listall = ` *listall* [_term_ ...] *lsa* [_term_ ...] Displays all open tasks (also done but not archived) that contain _term_(s) sorted by priority with line numbers. ` help.options.listcon = optsList help.alt.lst = 'listcon' help.actions.listcon = ` *listcon* [_term_ ...] *lsc* [_term_ ...] Lists all the task contexts that start with the @ sign in "todo.txt". If _term_ specified, considers only tasks that contain _term_(s). ` help.options.listpri = optsList help.alt.lsp = 'listpri' help.actions.listpri = ` *listpri* [_priorities_] \[_term_ ...] *lsp* [_priorities_] \[_term_ ...] Displays all tasks prioritized _priorities_. _priorities_ can be a single one "A" or a range "A-C". If no _priorities_ specified, lists all prioritized tasks. If _term_ specified, lists only prioritized tasks that contain _term_(s). Hides all tasks that contain _term_(s) preceded by a minus sign (i.e. "-TERM"). ` help.options.listproj = optsList help.alt.lsprj = 'listproj' help.actions.listproj = ` *listproj* [_term_ ...] *lsprj* [_term_ ...] Lists all the projects (terms that start with a + sign) in "todo.txt". If _term_ specified, considers only tasks that contain _term_(s). ` help.options.prepend = optsAlways help.alt.pre = 'prepend' help.actions.prepend = ` *prepend* _line#_ _text-to-prepend_ *pre* _line#_ _text-to-prepend_ Adds _text-to-prepend_ to the beginning of the task on line _line#_. Quotes are optional. ` help.options.pri = optsAlways help.alt.p = 'pri' help.actions.pri = ` *pri* _line#_ _priority_ *p* _line#_ _priority_ Adds _priority_ to task on line _line#_. If the task is already prioritized, replaces current priority with new _priority_. _priority_ must be a letter between A and Z. ` help.options.overdue = optsAlways help.actions.overdue = ` *overdue* [_iso-date_ | _relative-date_ | del] Moves due-date on all overdue tasks to today (no parameter) to a date using _iso-date_ or _relative-date_ (start date is today). When using "del" all due-dates are removed from all overdue tasks. ` help.options.replace = optsAlways help.actions.replace = ` *replace* _line#_ _text-to-replace_ Replaces task on line _line#_ with _text-to-replace_. ` help.options.set = optsAlways help.actions.set = ` *set* Display current configuration. *set* _key_ _value_ Set _key_ _value_ in todo config. Possible keys - editor "_args_" set default editor arguments - todoDir _pathname_ set default dir in "global" config only - useColor true|false use color mode - limit _limit_ set default limit for -l - color.priA _color_ set priority A color - color.priB _color_ set priority B color - color.priC _color_ set priority C color - color.priD _color_ set priority D color - color.project _color_ set project color - color.context _color_ set context color - color.date _color_ set date color - color.done _color_ set done color - color.error _color_ set error color Possible colors are: black, red, green, yellow, blue, magenta, cyan, white, gray, bgBlack, bgRed, bgGreen, bgYellow, bgBlue, bgMagenta, bgCyan, bgWhite, blackBright, redBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, whiteBright, bgBlackBright, bgRedBright, bgGreenBright, bgYellowBright, bgBlueBright, bgMagentaBright, bgCyanBright, bgWhiteBright, Possible modifiers include: bold, light, underline. E.g. for "underlined light green" use "lightGreenUnderline" for "bold light bgRed" use "lightBgRedBold" ` help.options.sort = optsAlways help.actions.sort = ` *sort* [_sorter_] Sorts file with _sorter_, default is "due": - "due": sort by due-date, priority, projects and contexts - "pri": sort by priority, due-date - "prj": sort by projects, priority, due-date - "con": sort by context, priority, due-date ` help.options.undo = optsAlways help.actions.undo = ` *undo* _line#_ [_line#_ ...] Undo done task(s) on line _line#_. Must not have been archived into "done.txt". ` help.options.version = [...optsAlways, '-v'] help.actions.version = ` *version* Display version info. ` // must be last item here! help.options.help = [...optsAlways, '-h'] help.actions.help = ` *help* [_action_] Display help about options and actions. Possible _action_s are: ${lineBreak([...Object.keys(help.actions), ...Object.keys(help.alt)].sort().join(', '))} `