Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
98f4dda1ad | |||
393be347c9 | |||
f8fed9d937 | |||
ef16eafd48 | |||
4af0d09356 | |||
071c4b66e5 |
@ -136,6 +136,3 @@ in the configuration file. All options are optional unless stated otherwise.
|
|||||||
|
|
||||||
* `tasksDir` **required**: a file path to the root directory for the issue
|
* `tasksDir` **required**: a file path to the root directory for the issue
|
||||||
repository (same as `--tasks-dir` CLI parameter).
|
repository (same as `--tasks-dir` CLI parameter).
|
||||||
|
|
||||||
- CLI parameter: *cannot be specified via CLI*
|
|
||||||
- config file key: `contexts`
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "4.9.2"
|
version = "4.11.1"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Personal issue tracker."
|
description = "Personal issue tracker."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@ -10,9 +10,9 @@ bin = @["pit", "pit_api"]
|
|||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
requires @[
|
requires @[
|
||||||
"nim >= 0.19.0",
|
"nim >= 1.4.0",
|
||||||
"docopt 0.6.8",
|
"docopt 0.6.8",
|
||||||
"jester 0.4.1",
|
"jester 0.5.0",
|
||||||
"uuids 0.1.10",
|
"uuids 0.1.10",
|
||||||
|
|
||||||
"https://git.jdb-labs.com/jdb/nim-cli-utils.git >= 0.6.4",
|
"https://git.jdb-labs.com/jdb/nim-cli-utils.git >= 0.6.4",
|
||||||
|
33
src/pit.nim
33
src/pit.nim
@ -1,7 +1,7 @@
|
|||||||
## Personal Issue Tracker CLI interface
|
## Personal Issue Tracker CLI interface
|
||||||
## ====================================
|
## ====================================
|
||||||
|
|
||||||
import cliutils, docopt, json, logging, options, os, sequtils,
|
import algorithm, cliutils, docopt, json, logging, options, os, sequtils,
|
||||||
std/wordwrap, tables, terminal, times, timeutils, unicode, uuids
|
std/wordwrap, tables, terminal, times, timeutils, unicode, uuids
|
||||||
|
|
||||||
from nre import re
|
from nre import re
|
||||||
@ -70,6 +70,8 @@ proc formatIssue(ctx: CliContext, issue: Issue): string =
|
|||||||
if not issue.details.isEmptyOrWhitespace:
|
if not issue.details.isEmptyOrWhitespace:
|
||||||
result &= issue.details.strip.withColor(fgCyan) & "\n"
|
result &= issue.details.strip.withColor(fgCyan) & "\n"
|
||||||
|
|
||||||
|
result &= termReset
|
||||||
|
|
||||||
proc formatSectionIssue(ctx: CliContext, issue: Issue, width: int, indent = "",
|
proc formatSectionIssue(ctx: CliContext, issue: Issue, width: int, indent = "",
|
||||||
verbose = false): string =
|
verbose = false): string =
|
||||||
|
|
||||||
@ -199,6 +201,10 @@ proc list(ctx: CliContext, filter: Option[IssueFilter], state: Option[IssueState
|
|||||||
if state.isSome:
|
if state.isSome:
|
||||||
ctx.loadIssues(state.get)
|
ctx.loadIssues(state.get)
|
||||||
if filter.isSome: ctx.filterIssues(filter.get)
|
if filter.isSome: ctx.filterIssues(filter.get)
|
||||||
|
if state.get == Done and showToday:
|
||||||
|
ctx.issues[Done] = ctx.issues[Done].filterIt(
|
||||||
|
it.hasProp("completed") and
|
||||||
|
sameDay(getTime().local, it.getDateTime("completed")))
|
||||||
stdout.write ctx.formatSection(ctx.issues[state.get], state.get, "", verbose)
|
stdout.write ctx.formatSection(ctx.issues[state.get], state.get, "", verbose)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -221,13 +227,6 @@ proc list(ctx: CliContext, filter: Option[IssueFilter], state: Option[IssueState
|
|||||||
if ctx.issues.hasKey(s) and ctx.issues[s].len > 0:
|
if ctx.issues.hasKey(s) and ctx.issues[s].len > 0:
|
||||||
stdout.write ctx.formatSection(ctx.issues[s], s, indent, verbose)
|
stdout.write ctx.formatSection(ctx.issues[s], s, indent, verbose)
|
||||||
|
|
||||||
if ctx.issues.hasKey(Done):
|
|
||||||
let doneIssues = ctx.issues[Done].filterIt(
|
|
||||||
it.hasProp("completed") and
|
|
||||||
sameDay(getTime().local, it.getDateTime("completed")))
|
|
||||||
if doneIssues.len > 0:
|
|
||||||
stdout.write ctx.formatSection(doneIssues, Done, indent, verbose)
|
|
||||||
|
|
||||||
# Future items
|
# Future items
|
||||||
if future:
|
if future:
|
||||||
if today: ctx.writeHeader("Future")
|
if today: ctx.writeHeader("Future")
|
||||||
@ -242,7 +241,8 @@ when isMainModule:
|
|||||||
let doc = """
|
let doc = """
|
||||||
Usage:
|
Usage:
|
||||||
pit ( new | add) <summary> [<state>] [options]
|
pit ( new | add) <summary> [<state>] [options]
|
||||||
pit list [<listable>] [options]
|
pit list contexts
|
||||||
|
pit list [<stateOrId>] [options]
|
||||||
pit ( start | done | pending | todo-today | todo | suspend ) <id>... [options]
|
pit ( start | done | pending | todo-today | todo | suspend ) <id>... [options]
|
||||||
pit edit <ref>...
|
pit edit <ref>...
|
||||||
pit tag <id>... [options]
|
pit tag <id>... [options]
|
||||||
@ -465,6 +465,10 @@ Options:
|
|||||||
filter.properties["context"] = ctx.defaultContext.get
|
filter.properties["context"] = ctx.defaultContext.get
|
||||||
filterOption = some(filter)
|
filterOption = some(filter)
|
||||||
|
|
||||||
|
if args["--tags"]:
|
||||||
|
filter.hasTags = ($args["--tags"]).split(',')
|
||||||
|
filterOption = some(filter)
|
||||||
|
|
||||||
# Finally, if the "context" is "all", don't filter on context
|
# Finally, if the "context" is "all", don't filter on context
|
||||||
if filter.properties.hasKey("context") and
|
if filter.properties.hasKey("context") and
|
||||||
filter.properties["context"] == "all":
|
filter.properties["context"] == "all":
|
||||||
@ -475,11 +479,10 @@ Options:
|
|||||||
var stateOption = none(IssueState)
|
var stateOption = none(IssueState)
|
||||||
var issueIdOption = none(string)
|
var issueIdOption = none(string)
|
||||||
|
|
||||||
if args["<listable>"]:
|
if args["contexts"]: listContexts = true
|
||||||
if $args["<listable>"] == "contexts": listContexts = true
|
elif args["<stateOrId>"]:
|
||||||
else:
|
try: stateOption = some(parseEnum[IssueState]($args["<stateOrId>"]))
|
||||||
try: stateOption = some(parseEnum[IssueState]($args["<listable>"]))
|
except: issueIdOption = some($args["<stateOrId>"])
|
||||||
except: issueIdOption = some($args["<listable>"])
|
|
||||||
|
|
||||||
# List the known contexts
|
# List the known contexts
|
||||||
if listContexts:
|
if listContexts:
|
||||||
@ -495,7 +498,7 @@ Options:
|
|||||||
else: b
|
else: b
|
||||||
).len
|
).len
|
||||||
|
|
||||||
for c in uniqContexts:
|
for c in uniqContexts.sorted:
|
||||||
stdout.writeLine(c.alignLeft(maxLen+2) & ctx.getIssueContextDisplayName(c))
|
stdout.writeLine(c.alignLeft(maxLen+2) & ctx.getIssueContextDisplayName(c))
|
||||||
|
|
||||||
# List a specific issue
|
# List a specific issue
|
||||||
|
@ -22,6 +22,7 @@ type
|
|||||||
IssueFilter* = ref object
|
IssueFilter* = ref object
|
||||||
completedRange*: Option[tuple[b, e: DateTime]]
|
completedRange*: Option[tuple[b, e: DateTime]]
|
||||||
fullMatch*, summaryMatch*: Option[Regex]
|
fullMatch*, summaryMatch*: Option[Regex]
|
||||||
|
hasTags*: seq[string]
|
||||||
properties*: TableRef[string, string]
|
properties*: TableRef[string, string]
|
||||||
|
|
||||||
PitConfig* = ref object
|
PitConfig* = ref object
|
||||||
@ -69,6 +70,7 @@ proc initFilter*(): IssueFilter =
|
|||||||
completedRange: none(tuple[b, e: DateTime]),
|
completedRange: none(tuple[b, e: DateTime]),
|
||||||
fullMatch: none(Regex),
|
fullMatch: none(Regex),
|
||||||
summaryMatch: none(Regex),
|
summaryMatch: none(Regex),
|
||||||
|
hasTags: @[],
|
||||||
properties: newTable[string, string]())
|
properties: newTable[string, string]())
|
||||||
|
|
||||||
proc propsFilter*(props: TableRef[string, string]): IssueFilter =
|
proc propsFilter*(props: TableRef[string, string]): IssueFilter =
|
||||||
@ -91,6 +93,10 @@ proc fullMatchFilter*(pattern: string): IssueFilter =
|
|||||||
result = initFilter()
|
result = initFilter()
|
||||||
result.fullMatch = some(re("(?i)" & pattern))
|
result.fullMatch = some(re("(?i)" & pattern))
|
||||||
|
|
||||||
|
proc hasTagsFilter*(tags: seq[string]): IssueFilter =
|
||||||
|
result = initFilter()
|
||||||
|
result.hasTags = tags
|
||||||
|
|
||||||
proc groupBy*(issues: seq[Issue], propertyKey: string): TableRef[string, seq[Issue]] =
|
proc groupBy*(issues: seq[Issue], propertyKey: string): TableRef[string, seq[Issue]] =
|
||||||
result = newTable[string, seq[Issue]]()
|
result = newTable[string, seq[Issue]]()
|
||||||
for i in issues:
|
for i in issues:
|
||||||
@ -259,6 +265,9 @@ proc filter*(issues: seq[Issue], filter: IssueFilter): seq[Issue] =
|
|||||||
let p = filter.fullMatch.get
|
let p = filter.fullMatch.get
|
||||||
result = result.filterIt( it.summary.find(p).isSome or it.details.find(p).isSome)
|
result = result.filterIt( it.summary.find(p).isSome or it.details.find(p).isSome)
|
||||||
|
|
||||||
|
for tag in filter.hasTags:
|
||||||
|
result = result.filterIt(it.tags.find(tag) >= 0)
|
||||||
|
|
||||||
### Configuration utilities
|
### Configuration utilities
|
||||||
proc loadConfig*(args: Table[string, Value] = initTable[string, Value]()): PitConfig =
|
proc loadConfig*(args: Table[string, Value] = initTable[string, Value]()): PitConfig =
|
||||||
let pitrcLocations = @[
|
let pitrcLocations = @[
|
||||||
@ -266,9 +275,9 @@ proc loadConfig*(args: Table[string, Value] = initTable[string, Value]()): PitCo
|
|||||||
".pitrc", $getEnv("PITRC"), $getEnv("HOME") & "/.pitrc"]
|
".pitrc", $getEnv("PITRC"), $getEnv("HOME") & "/.pitrc"]
|
||||||
|
|
||||||
var pitrcFilename: string =
|
var pitrcFilename: string =
|
||||||
foldl(pitrcLocations, if len(a) > 0: a elif existsFile(b): b else: "")
|
foldl(pitrcLocations, if len(a) > 0: a elif fileExists(b): b else: "")
|
||||||
|
|
||||||
if not existsFile(pitrcFilename):
|
if not fileExists(pitrcFilename):
|
||||||
warn "pit: could not find .pitrc file: " & pitrcFilename
|
warn "pit: could not find .pitrc file: " & pitrcFilename
|
||||||
if isEmptyOrWhitespace(pitrcFilename):
|
if isEmptyOrWhitespace(pitrcFilename):
|
||||||
pitrcFilename = $getEnv("HOME") & "/.pitrc"
|
pitrcFilename = $getEnv("HOME") & "/.pitrc"
|
||||||
@ -299,12 +308,10 @@ proc loadConfig*(args: Table[string, Value] = initTable[string, Value]()): PitCo
|
|||||||
if isEmptyOrWhitespace(result.tasksDir):
|
if isEmptyOrWhitespace(result.tasksDir):
|
||||||
raise newException(Exception, "no tasks directory configured")
|
raise newException(Exception, "no tasks directory configured")
|
||||||
|
|
||||||
if not existsDir(result.tasksDir):
|
if not dirExists(result.tasksDir):
|
||||||
raise newException(Exception, "cannot find tasks dir: " & result.tasksDir)
|
raise newException(Exception, "cannot find tasks dir: " & result.tasksDir)
|
||||||
|
|
||||||
# Create our tasks directory structure if needed
|
# Create our tasks directory structure if needed
|
||||||
for s in IssueState:
|
for s in IssueState:
|
||||||
if not existsDir(result.tasksDir / $s):
|
if not dirExists(result.tasksDir / $s):
|
||||||
(result.tasksDir / $s).createDir
|
(result.tasksDir / $s).createDir
|
||||||
|
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
const PIT_VERSION* = "4.9.2"
|
const PIT_VERSION* = "4.11.1"
|
Reference in New Issue
Block a user