import std/[os, strutils, strtabs, sequtils, osproc, parseopt] import soon/[config, agenda, todos, tui, archive] const VERSION = "0.1" proc printHelp(badCmd: string) = if badCmd != "": echo badCmd & " is not a valid option\n" echo """ Usage: soon [options] Options: -a print agenda -c print reference calendar (using 'cal') -t print todos -d print deadlines -e open default calendar file in $EDITOR -s open soon.conf in $EDITOR -i open TUI in interactive mode to schedule/archive events -w, -m, -y print agenda for upcoming week, month, or year -j print the modified julian day -p, --past=DAYS days in past to print agenda (default: 1) -f, --future=DAYS days in future to print agenda (default: 14) --config use alternate config file -h, --help show this help -v, --version print version Check the docs at https://codeberg.org/historia/soon """ quit() proc printVersion() = echo "soon " & VERSION quit() proc createDefaultCalendarFile(file: string) = if not fileExists(file): writeFile(file, "# This is your first calendar file. Put events and todos in here.") proc getFileLines(file: string): seq[string] = let f = open(file) for line in f.lines: if line == "": continue if line.strip[0] == '#': continue result.add(line) f.close() proc readAllSoonFiles(path: string): seq[string] = var soonFileExists = false for file in os.walkDirRec(path): if file.toLower().endsWith(".soon"): soonFileExists = true result = result.concat(getFileLines(file)) if not soonFileExists: echo "No .soon files exist at " & path echo "Check your paths in ~/.config/soon/soon.conf" quit() proc parseArgs(config: StringTableRef): tuple[commands: seq[string], interactive: bool] = var skipNext = false for kind, key, val in getopt(): case kind of cmdEnd: break of cmdLongOption, cmdShortOption: case key of "past", "p": config["past"] = val of "future", "f": config["future"] = val of "help", "h": printHelp("") of "version", "v": printVersion() of "i": result.interactive = true of "config": if val == "": skipNext = true else: result.commands.add(key) of cmdArgument: if skipNext: skipNext = false else: printHelp(key) let printTodos = config.getOrDefault("printtodos", "true") == "true" if result.commands.len == 0: let defaultCmd = config.getOrDefault("defaultCommand", "atd") if result.interactive: for c in defaultCmd: if c == 't' or c == 'd': if printTodos: result.commands.add($c) elif c != ' ': result.commands.add($c) else: for c in defaultCmd: if c == 't' or c == 'd': if printTodos: result.commands.add($c) elif c != ' ': result.commands.add($c) proc printReference() = discard execCmd("cal -3") proc sanitize(str: string): string = result = "" for ch in str: if ch.isAlphaNumeric() or ch in {'/', '~', '.'}: result.add(ch) proc openFileInEditor(conf: StringTableRef, filePath: string) = var editor = conf.getOrDefault("editor", getEnv("EDITOR")).sanitize if editor.isEmptyOrWhitespace: editor = "vim" let saneFile = filePath.sanitize discard execCmd("$1 $2" % [editor, saneFile]) proc openEditor(conf: StringTableRef) = let filePath = conf.getOrDefault("calendarPath", conf.getOrDefault("path", "")) / conf.getOrDefault("defaultFile", "calendar.soon") openFileInEditor(conf, filePath) proc openConfig(conf: StringTableRef) = openFileInEditor(conf, conf.getOrDefault("configFile", conf.getOrDefault("path", "") / "soon.conf")) proc validateConfig(config: StringTableRef) = createDefaultCalendarFile(config["path"] / config["defaultFile"]) proc processCommands(commands: seq[string], conf: StringTableRef, interactive: bool) = var calendar = readAllSoonFiles(conf["calendarPath"]) var past = 0 - parseInt(conf["past"]) var future = parseInt(conf["future"]) let arch = archive.loadArchive(conf.getOrDefault("archiveFile", conf.getOrDefault("path", "") / "archive")) if interactive: var showAgenda = false var showTodos = false var showDeadlines = false for cmd in commands: case cmd of "a": showAgenda = true of "w": showAgenda = true past = 0 future = 7 of "m": showAgenda = true past = 0 future = 28 of "y": showAgenda = true past = 0 future = 365 of "t": showTodos = true of "d": showDeadlines = true else: discard if not showAgenda and not showTodos and not showDeadlines: showAgenda = true showTodos = true showDeadlines = true tui.start(calendar, conf, past, future, showAgenda, showTodos, showDeadlines) else: for command in commands: case command of "a": agenda.printAgenda(calendar, past, future, conf, arch) of "w": agenda.printAgenda(calendar, 0, 7, conf, arch) of "m": agenda.printAgenda(calendar, 0, 28, conf, arch) of "y": agenda.printAgenda(calendar, 0, 365, conf, arch) of "t": todos.printTodos(calendar) of "d": todos.printDeadlineTodos(calendar) of "c": printReference() of "e": openEditor(conf) of "s": openConfig(conf) else: discard proc findConfigArg(): string = let params = commandLineParams() var i = 0 while i < params.len: if params[i] == "--config" and i + 1 < params.len: return params[i + 1] elif params[i].startsWith("--config="): return params[i].split("=", maxsplit = 1)[1] inc i proc main() = var conf = config.loadConfig(findConfigArg()) let (commands, interactive) = parseArgs(conf) validateConfig(conf) processCommands(commands, conf, interactive) when isMainModule: main()