diff --git a/pit.nimble b/pit.nimble index b539fdf..b191193 100644 --- a/pit.nimble +++ b/pit.nimble @@ -12,4 +12,4 @@ bin = @["pit", "pit_api"] # Dependencies requires @[ "nim >= 0.18.0", "cliutils 0.4.1", "docopt 0.6.5", "jester 0.2.0", - "timeutils 0.3.0", "uuids 0.1.9" ] + "langutils >= 0.4.0", "timeutils 0.3.0", "uuids 0.1.9" ] diff --git a/src/pit.nim b/src/pit.nim index 68d23f3..72c210e 100644 --- a/src/pit.nim +++ b/src/pit.nim @@ -21,6 +21,11 @@ type termWidth*: int triggerPtk*, verbose*: bool +let EDITOR = + if existsEnv("EDITOR"): getEnv("EDITOR") + else: "vi" + + proc initContext(args: Table[string, Value]): CliContext = let pitCfg = loadConfig(args) @@ -163,16 +168,18 @@ proc writeHeader(ctx: CliContext, header: string) = stdout.writeLine('~'.repeat(ctx.termWidth)) stdout.resetAttributes +proc reorder(ctx: CliContext, state: IssueState) = + + # load the issues to make sure the order file contains all issues in the state. + ctx.loadIssues(state) + discard os.execShellCmd(EDITOR & " '" & (ctx.tasksDir / $state / "order.txt") & "' /dev/tty") + proc edit(issue: Issue) = # Write format comments (to help when editing) writeFile(issue.filepath, toStorageFormat(issue, true)) - let editor = - if existsEnv("EDITOR"): getEnv("EDITOR") - else: "vi" - - discard os.execShellCmd(editor & " " & issue.filepath & " /dev/tty") + discard os.execShellCmd(EDITOR & " '" & issue.filepath & "' /dev/tty") try: # Try to parse the newly-edited issue to make sure it was successful. @@ -234,6 +241,7 @@ Usage: pit list [] [options] pit ( start | done | pending | todo-today | todo | suspend ) ... [options] pit edit ... + pit reorder pit ( delete | rm ) ... Options: @@ -327,6 +335,9 @@ Options: stdout.writeLine ctx.formatIssue(issue) + elif args["reorder"]: + ctx.reorder(parseEnum[IssueState]($args[""])) + elif args["edit"]: for editRef in @(args[""]): diff --git a/src/pitpkg/private/libpit.nim b/src/pitpkg/private/libpit.nim index d67d071..e6d2ffb 100644 --- a/src/pitpkg/private/libpit.nim +++ b/src/pitpkg/private/libpit.nim @@ -1,5 +1,5 @@ -import cliutils, docopt, json, logging, options, os, ospaths, sequtils, - strutils, tables, times, timeutils, uuids +import cliutils, docopt, json, logging, langutils, options, os, ospaths, + sequtils, strutils, tables, times, timeutils, uuids from nre import find, match, re, Regex @@ -89,6 +89,14 @@ proc fullMatchFilter*(pattern: string): IssueFilter = result = initFilter() result.fullMatch = some(re("(?i)" & pattern)) +proc groupBy*(issues: seq[Issue], propertyKey: string): TableRef[string, seq[Issue]] = + result = newTable[string, seq[Issue]]() + for i in issues: + let key = if i.hasProp(propertyKey): i[propertyKey] else: "" + if not result.hasKey(key): result[key] = newSeq[Issue]() + result[key].add(i) + + ## Parse and format issues proc fromStorageFormat*(id: string, issueTxt: string): Issue = type ParseState = enum ReadingSummary, ReadingProps, ReadingDetails @@ -172,11 +180,51 @@ proc store*(tasksDir: string, issue: Issue, state: IssueState, withComments = fa issue.store() +proc storeOrder*(issues: seq[Issue], path: string) = + var orderLines = newSeq[string]() + + for context, issues in issues.groupBy("context"): + orderLines.add("> " & context) + for issue in issues: orderLines.add($issue.id & " " & issue.summary) + orderLines.add("") + + let orderFile = path / "order.txt" + orderFile.writeFile(orderLines.join("\n")) + proc loadIssues*(path: string): seq[Issue] = - result = @[] + let orderFile = path / "order.txt" + + let orderedIds = + if fileExists(orderFile): + toSeq(orderFile.lines) + .mapIt(it.split(' ')[0]) + .deduplicate + .filterIt(not it.startsWith("> ") and not it.isNilOrWhitespace) + else: newSeq[string]() + + type TaggedIssue = tuple[issue: Issue, ordered: bool] + var unorderedIssues: seq[TaggedIssue] = @[] + for path in walkDirRec(path): if extractFilename(path).match(ISSUE_FILE_PATTERN).isSome(): - result.add(loadIssue(path)) + unorderedIssues.add((loadIssue(path), false)) + + result = @[] + + # Add all ordered issues in order + for id in orderedIds: + let idx = unorderedIssues.indexOf(($it.issue.id).startsWith(id)) + if idx > 0: + result.add(unorderedIssues[idx].issue) + unorderedIssues[idx].ordered = true + + # Add all remaining, unordered issues in the order they were loaded + for taggedIssue in unorderedIssues: + if taggedIssue.ordered: continue + result.add(taggedIssue.issue) + + # Finally, save current order + result.storeOrder(path) proc changeState*(issue: Issue, tasksDir: string, newState: IssueState) = let oldFilepath = issue.filepath @@ -187,13 +235,6 @@ proc changeState*(issue: Issue, tasksDir: string, newState: IssueState) = proc delete*(issue: Issue) = removeFile(issue.filepath) ## Utilities for working with issue collections. -proc groupBy*(issues: seq[Issue], propertyKey: string): TableRef[string, seq[Issue]] = - result = newTable[string, seq[Issue]]() - for i in issues: - let key = if i.hasProp(propertyKey): i[propertyKey] else: "" - if not result.hasKey(key): result[key] = newSeq[Issue]() - result[key].add(i) - proc filter*(issues: seq[Issue], filter: IssueFilter): seq[Issue] = result = issues diff --git a/src/pitpkg/version.nim b/src/pitpkg/version.nim index d8f879f..9955a7c 100644 --- a/src/pitpkg/version.nim +++ b/src/pitpkg/version.nim @@ -1 +1 @@ -const PIT_VERSION = "4.3.0" +const PIT_VERSION = "4.4.0"