Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d1dc7512a | |||
| 1d18be9d1b |
@@ -1,6 +1,6 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "4.31.0"
|
version = "4.31.1"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Personal issue tracker."
|
description = "Personal issue tracker."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
37
src/pit.nim
37
src/pit.nim
@@ -19,18 +19,16 @@ proc parsePropertiesOption(propsOpt: string): TableRef[string, string] =
|
|||||||
result = newTable[string, string]()
|
result = newTable[string, string]()
|
||||||
for propText in propsOpt.split(";"):
|
for propText in propsOpt.split(";"):
|
||||||
let pair = propText.split(":", 1)
|
let pair = propText.split(":", 1)
|
||||||
if pair.len == 1: result[pair[0]] = "true"
|
if pair.len == 1: result[pair[0]] = MATCH_ANY
|
||||||
else: result[pair[0]] = pair[1]
|
else: result[pair[0]] = pair[1]
|
||||||
|
|
||||||
|
|
||||||
proc parseExclPropertiesOption(propsOpt: string): TableRef[string, seq[string]] =
|
proc parseExclPropertiesOption(propsOpt: string): TableRef[string, seq[string]] =
|
||||||
result = newTable[string, seq[string]]()
|
result = newTable[string, seq[string]]()
|
||||||
for propText in propsOpt.split(";"):
|
for propText in propsOpt.split(";"):
|
||||||
let pair = propText.split(":", 1)
|
let pair = propText.split(":", 1)
|
||||||
let val =
|
if not result.hasKey(pair[0]): result[pair[0]] = @[]
|
||||||
if pair.len == 1: "true"
|
if pair.len == 2: result[pair[0]].add(pair[1])
|
||||||
else: pair[1]
|
|
||||||
if result.hasKey(pair[0]): result[pair[0]].add(val)
|
|
||||||
else: result[pair[0]] = @[val]
|
|
||||||
|
|
||||||
|
|
||||||
proc reorder(ctx: CliContext, state: IssueState) =
|
proc reorder(ctx: CliContext, state: IssueState) =
|
||||||
@@ -39,6 +37,7 @@ proc reorder(ctx: CliContext, state: IssueState) =
|
|||||||
ctx.loadIssues(state)
|
ctx.loadIssues(state)
|
||||||
discard os.execShellCmd(EDITOR & " '" & (ctx.cfg.tasksDir / $state / "order.txt") & "' </dev/tty >/dev/tty")
|
discard os.execShellCmd(EDITOR & " '" & (ctx.cfg.tasksDir / $state / "order.txt") & "' </dev/tty >/dev/tty")
|
||||||
|
|
||||||
|
|
||||||
proc addIssue(
|
proc addIssue(
|
||||||
ctx: CliContext,
|
ctx: CliContext,
|
||||||
args: Table[string, Value],
|
args: Table[string, Value],
|
||||||
@@ -172,8 +171,6 @@ when isMainModule:
|
|||||||
var exclTagsOption = none(seq[string])
|
var exclTagsOption = none(seq[string])
|
||||||
|
|
||||||
let filter = initFilter()
|
let filter = initFilter()
|
||||||
var filterOption = none(IssueFilter)
|
|
||||||
|
|
||||||
|
|
||||||
if args["--properties"] or args["--context"]:
|
if args["--properties"] or args["--context"]:
|
||||||
|
|
||||||
@@ -207,43 +204,37 @@ when isMainModule:
|
|||||||
# Initialize filter with properties (if given)
|
# Initialize filter with properties (if given)
|
||||||
if propertiesOption.isSome:
|
if propertiesOption.isSome:
|
||||||
filter.properties = propertiesOption.get
|
filter.properties = propertiesOption.get
|
||||||
filterOption = some(filter)
|
|
||||||
|
|
||||||
# Add property exclusions (if given)
|
# Add property exclusions (if given)
|
||||||
if exclPropsOption.isSome:
|
if exclPropsOption.isSome:
|
||||||
filter.exclProperties = exclPropsOption.get
|
filter.exclProperties = exclPropsOption.get
|
||||||
filterOption = some(filter)
|
|
||||||
|
|
||||||
# If they supplied text matches, add that to the filter.
|
# If they supplied text matches, add that to the filter.
|
||||||
if args["--match"]:
|
if args["--match"]:
|
||||||
filter.summaryMatch = some(re("(?i)" & $args["--match"]))
|
filter.summaryMatch = some(re("(?i)" & $args["--match"]))
|
||||||
filterOption = some(filter)
|
|
||||||
|
|
||||||
if args["--match-all"]:
|
if args["--match-all"]:
|
||||||
filter.fullMatch = some(re("(?i)" & $args["--match-all"]))
|
filter.fullMatch = some(re("(?i)" & $args["--match-all"]))
|
||||||
filterOption = some(filter)
|
|
||||||
|
|
||||||
# If no "context" property is given, use the default (if we have one)
|
# If no "context" property is given, use the default (if we have one)
|
||||||
if ctx.defaultContext.isSome and not filter.properties.hasKey("context"):
|
if ctx.defaultContext.isSome and not filter.properties.hasKey("context"):
|
||||||
stderr.writeLine("Limiting to default context: " & ctx.defaultContext.get)
|
stderr.writeLine("Limiting to default context: " & ctx.defaultContext.get)
|
||||||
filter.properties["context"] = ctx.defaultContext.get
|
filter.properties["context"] = ctx.defaultContext.get
|
||||||
filterOption = some(filter)
|
|
||||||
|
|
||||||
if tagsOption.isSome:
|
if tagsOption.isSome:
|
||||||
filter.hasTags = tagsOption.get
|
filter.hasTags = tagsOption.get
|
||||||
filterOption = some(filter)
|
|
||||||
|
|
||||||
if exclTagsOption.isSome:
|
if exclTagsOption.isSome:
|
||||||
filter.exclTags = exclTagsOption.get
|
filter.exclTags = exclTagsOption.get
|
||||||
filterOption = some(filter)
|
|
||||||
|
|
||||||
if args["--today"]:
|
if args["--today"]:
|
||||||
filter.inclStates.add(@[Current, TodoToday, Pending])
|
filter.inclStates.add(@[Current, TodoToday, Pending])
|
||||||
filterOption = some(filter)
|
|
||||||
|
|
||||||
if args["--future"]:
|
if args["--future"]:
|
||||||
filter.inclStates.add(@[Pending, Todo])
|
filter.inclStates.add(@[Pending, Todo])
|
||||||
filterOption = some(filter)
|
|
||||||
|
if args["--show-hidden"]:
|
||||||
|
filter.exclHidden = false
|
||||||
|
|
||||||
# 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
|
||||||
@@ -433,7 +424,7 @@ when isMainModule:
|
|||||||
for state in statesOption.get: ctx.loadIssues(state)
|
for state in statesOption.get: ctx.loadIssues(state)
|
||||||
else: ctx.loadAllIssues()
|
else: ctx.loadAllIssues()
|
||||||
|
|
||||||
if filterOption.isSome: ctx.filterIssues(filterOption.get)
|
ctx.filterIssues(filter)
|
||||||
|
|
||||||
for state, issueList in ctx.issues:
|
for state, issueList in ctx.issues:
|
||||||
for issue in issueList:
|
for issue in issueList:
|
||||||
@@ -449,27 +440,27 @@ when isMainModule:
|
|||||||
stdout.writeLine formatIssue(issue)
|
stdout.writeLine formatIssue(issue)
|
||||||
|
|
||||||
# List projects
|
# List projects
|
||||||
elif listProjects: ctx.listProjects(filterOption)
|
elif listProjects: ctx.listProjects(some(filter))
|
||||||
|
|
||||||
# List milestones
|
# List milestones
|
||||||
elif listMilestones: ctx.listMilestones(filterOption)
|
elif listMilestones: ctx.listMilestones(some(filter))
|
||||||
|
|
||||||
# List all issues
|
# List all issues
|
||||||
else:
|
else:
|
||||||
trace "listing all issues"
|
trace "listing all issues"
|
||||||
let showBoth = args["--today"] == args["--future"]
|
let showBoth = args["--today"] == args["--future"]
|
||||||
ctx.list(
|
ctx.list(
|
||||||
filter = filterOption,
|
filter = some(filter),
|
||||||
states = statesOption,
|
states = statesOption,
|
||||||
showToday = showBoth or args["--today"],
|
showToday = showBoth or args["--today"],
|
||||||
showFuture = showBoth or args["--future"],
|
showFuture = showBoth or args["--future"],
|
||||||
showHidden = args["--show-hidden"],
|
|
||||||
verbose = ctx.verbose)
|
verbose = ctx.verbose)
|
||||||
|
|
||||||
elif args["show"]:
|
elif args["show"]:
|
||||||
|
|
||||||
if args["project-board"]:
|
if args["project-board"]:
|
||||||
ctx.showProjectBoard(filterOption)
|
if not args["--show-done"]: filter.exclStates.add(Done)
|
||||||
|
ctx.showProjectBoard(some(filter))
|
||||||
discard
|
discard
|
||||||
|
|
||||||
elif args["dupes"]:
|
elif args["dupes"]:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const PIT_VERSION* = "4.31.0"
|
const PIT_VERSION* = "4.31.1"
|
||||||
|
|
||||||
const USAGE* = """Usage:
|
const USAGE* = """Usage:
|
||||||
pit ( new | add) <summary> [<state>] [options]
|
pit ( new | add) <summary> [<state>] [options]
|
||||||
@@ -18,7 +18,7 @@ const USAGE* = """Usage:
|
|||||||
pit add-binary-property <id> <propName> <propSource> [options]
|
pit add-binary-property <id> <propName> <propSource> [options]
|
||||||
pit get-binary-property <id> <propName> <propDest> [options]
|
pit get-binary-property <id> <propName> <propDest> [options]
|
||||||
pit show dupes [options]
|
pit show dupes [options]
|
||||||
pit show project-board [options]
|
pit show project-board [--show-done] [options]
|
||||||
pit show <id> [options]
|
pit show <id> [options]
|
||||||
pit sync [<syncTarget>...] [options]
|
pit sync [<syncTarget>...] [options]
|
||||||
pit help [options]
|
pit help [options]
|
||||||
@@ -33,12 +33,20 @@ Options:
|
|||||||
a filter to the issues listed, only allowing those
|
a filter to the issues listed, only allowing those
|
||||||
which have all of the given properties.
|
which have all of the given properties.
|
||||||
|
|
||||||
|
If a propert name is provided without a value, this
|
||||||
|
will allow all issues which have any value defined
|
||||||
|
for the named property.
|
||||||
|
|
||||||
-P, --excl-properties <props>
|
-P, --excl-properties <props>
|
||||||
When used with the list command, exclude issues
|
When used with the list command, exclude issues
|
||||||
that contain properties with the given value. This
|
that contain properties with the given value. This
|
||||||
parameter is formatted the same as the --properties
|
parameter is formatted the same as the --properties
|
||||||
parameter: "key:val;key:val"
|
parameter: "key:val;key:val"
|
||||||
|
|
||||||
|
If no value is provided for a property, this will
|
||||||
|
filter out all issues with *any* value for that
|
||||||
|
property.
|
||||||
|
|
||||||
-c, --context <ctx> Shorthand for '-p context:<ctx>'
|
-c, --context <ctx> Shorthand for '-p context:<ctx>'
|
||||||
|
|
||||||
-C, --excl-context <ctx> Don't show issues from the given context(s).
|
-C, --excl-context <ctx> Don't show issues from the given context(s).
|
||||||
@@ -239,4 +247,4 @@ Issue Properties:
|
|||||||
|
|
||||||
If present, expected to be a comma-delimited list of text tags. The -g
|
If present, expected to be a comma-delimited list of text tags. The -g
|
||||||
option is a short-hand for '-p tags:<tags-value>'.
|
option is a short-hand for '-p tags:<tags-value>'.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ proc list*(
|
|||||||
states: Option[seq[IssueState]],
|
states: Option[seq[IssueState]],
|
||||||
showToday = false,
|
showToday = false,
|
||||||
showFuture = false,
|
showFuture = false,
|
||||||
showHidden = false,
|
|
||||||
verbose: bool) =
|
verbose: bool) =
|
||||||
|
|
||||||
if states.isSome:
|
if states.isSome:
|
||||||
@@ -219,10 +218,7 @@ proc list*(
|
|||||||
|
|
||||||
for s in [Current, TodoToday, Pending]:
|
for s in [Current, TodoToday, Pending]:
|
||||||
if ctx.issues.hasKey(s) and ctx.issues[s].len > 0:
|
if ctx.issues.hasKey(s) and ctx.issues[s].len > 0:
|
||||||
let visibleIssues = ctx.issues[s].filterIt(
|
let visibleIssues = ctx.issues[s]
|
||||||
showHidden or
|
|
||||||
not (it.hasProp("hide-until") and
|
|
||||||
it.getDateTime("hide-until") > getTime().local))
|
|
||||||
|
|
||||||
if isatty(stdout):
|
if isatty(stdout):
|
||||||
stdout.write ctx.formatSection(visibleIssues, s, indent, verbose)
|
stdout.write ctx.formatSection(visibleIssues, s, indent, verbose)
|
||||||
@@ -242,10 +238,7 @@ proc list*(
|
|||||||
|
|
||||||
for s in futureCategories:
|
for s in futureCategories:
|
||||||
if ctx.issues.hasKey(s) and ctx.issues[s].len > 0:
|
if ctx.issues.hasKey(s) and ctx.issues[s].len > 0:
|
||||||
let visibleIssues = ctx.issues[s].filterIt(
|
let visibleIssues = ctx.issues[s]
|
||||||
showHidden or
|
|
||||||
not (it.hasProp("hide-until") and
|
|
||||||
it.getDateTime("hide-until") > getTime().local))
|
|
||||||
|
|
||||||
if isatty(stdout):
|
if isatty(stdout):
|
||||||
stdout.write ctx.formatSection(visibleIssues, s, indent, verbose)
|
stdout.write ctx.formatSection(visibleIssues, s, indent, verbose)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type
|
|||||||
exclStates*: seq[IssueState]
|
exclStates*: seq[IssueState]
|
||||||
hasTags*: seq[string]
|
hasTags*: seq[string]
|
||||||
exclTags*: seq[string]
|
exclTags*: seq[string]
|
||||||
|
exclHidden*: bool
|
||||||
properties*: TableRef[string, string]
|
properties*: TableRef[string, string]
|
||||||
exclProperties*: TableRef[string, seq[string]]
|
exclProperties*: TableRef[string, seq[string]]
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ type
|
|||||||
isFromCompletion*: bool
|
isFromCompletion*: bool
|
||||||
|
|
||||||
|
|
||||||
|
const MATCH_ANY* = "<match-any>"
|
||||||
const DONE_FOLDER_FORMAT* = "yyyy-MM"
|
const DONE_FOLDER_FORMAT* = "yyyy-MM"
|
||||||
const ISO8601_MS = "yyyy-MM-dd'T'HH:mm:ss'.'fffzzz"
|
const ISO8601_MS = "yyyy-MM-dd'T'HH:mm:ss'.'fffzzz"
|
||||||
|
|
||||||
@@ -151,6 +153,7 @@ proc initFilter*(): IssueFilter =
|
|||||||
summaryMatch: none(Regex),
|
summaryMatch: none(Regex),
|
||||||
inclStates: @[],
|
inclStates: @[],
|
||||||
exclStates: @[],
|
exclStates: @[],
|
||||||
|
exclHidden: true,
|
||||||
hasTags: @[],
|
hasTags: @[],
|
||||||
exclTags: @[],
|
exclTags: @[],
|
||||||
properties: newTable[string, string](),
|
properties: newTable[string, string](),
|
||||||
@@ -184,6 +187,10 @@ proc stateFilter*(states: seq[IssueState]): IssueFilter =
|
|||||||
result = initFilter()
|
result = initFilter()
|
||||||
result.inclStates = states
|
result.inclStates = states
|
||||||
|
|
||||||
|
proc showHiddenFilter*(): IssueFilter =
|
||||||
|
result = initFilter()
|
||||||
|
result.exclHidden = false
|
||||||
|
|
||||||
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:
|
||||||
@@ -416,11 +423,23 @@ proc nextRecurrence*(tasksDir: string, rec: Recurrence, defaultIssue: Issue): Is
|
|||||||
proc filter*(issues: seq[Issue], filter: IssueFilter): seq[Issue] =
|
proc filter*(issues: seq[Issue], filter: IssueFilter): seq[Issue] =
|
||||||
var f: seq[Issue] = issues
|
var f: seq[Issue] = issues
|
||||||
|
|
||||||
|
if filter.exclHidden:
|
||||||
|
let now = getTime().local
|
||||||
|
f = f --> filter(
|
||||||
|
not it.hasProp("hide-until") or
|
||||||
|
it.getDateTime("hide-until") <= now)
|
||||||
|
|
||||||
for k,v in filter.properties:
|
for k,v in filter.properties:
|
||||||
f = f --> filter(it.hasProp(k) and it[k] == v)
|
if v == MATCH_ANY:
|
||||||
|
f = f --> filter(it.hasProp(k))
|
||||||
|
else:
|
||||||
|
f = f --> filter(it.hasProp(k) and it[k] == v)
|
||||||
|
|
||||||
for k,v in filter.exclProperties:
|
for k,v in filter.exclProperties:
|
||||||
f = f --> filter(not (it.hasProp(k) and v.contains(it[k])))
|
if v.len == 0:
|
||||||
|
f = f --> filter(not (it.hasProp(k)))
|
||||||
|
else:
|
||||||
|
f = f --> filter(not (it.hasProp(k) and v.contains(it[k])))
|
||||||
|
|
||||||
if filter.completedRange.isSome:
|
if filter.completedRange.isSome:
|
||||||
let range = filter.completedRange.get
|
let range = filter.completedRange.get
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ from std/sequtils import repeat, toSeq
|
|||||||
import cliutils, uuids, zero_functional
|
import cliutils, uuids, zero_functional
|
||||||
import ./[formatting, libpit]
|
import ./[formatting, libpit]
|
||||||
|
|
||||||
|
const NO_PROJECT* = "<no-project>"
|
||||||
|
const NO_MILESTONE* = "<no-project>"
|
||||||
|
const NO_CONTEXT* = "<no-project>"
|
||||||
|
|
||||||
type
|
type
|
||||||
ProjectCfg* = ref object of RootObj
|
ProjectCfg* = ref object of RootObj
|
||||||
name: string
|
name: string
|
||||||
@@ -62,15 +66,18 @@ proc buildDb*(ctx: CliContext, cfg: ProjectsConfiguration): ProjectsDatabase =
|
|||||||
# Now populate the database with issues
|
# Now populate the database with issues
|
||||||
for (state, issues) in pairs(ctx.issues):
|
for (state, issues) in pairs(ctx.issues):
|
||||||
for issue in issues:
|
for issue in issues:
|
||||||
if not issue.hasProp("project") or
|
|
||||||
not issue.hasProp("milestone"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
let projectName = issue["project"]
|
let projectName =
|
||||||
let milestone = issue["milestone"]
|
if issue.hasProp("project"): issue["project"]
|
||||||
|
else: NO_PROJECT
|
||||||
|
|
||||||
|
let milestone =
|
||||||
|
if issue.hasProp("milestone"): issue["milestone"]
|
||||||
|
else: NO_MILESTONE
|
||||||
|
|
||||||
let context =
|
let context =
|
||||||
if issue.hasProp("context"): issue["context"]
|
if issue.hasProp("context"): issue["context"]
|
||||||
else: "<no-context>"
|
else: NO_CONTEXT
|
||||||
|
|
||||||
# Make sure we have entries for this context and project
|
# Make sure we have entries for this context and project
|
||||||
if not result.hasKey(context): result[context] = @[]
|
if not result.hasKey(context): result[context] = @[]
|
||||||
@@ -128,15 +135,18 @@ proc listProjects*(ctx: CliContext, filter = none[IssueFilter]()) =
|
|||||||
|
|
||||||
for (state, issues) in pairs(ctx.issues):
|
for (state, issues) in pairs(ctx.issues):
|
||||||
for issue in issues:
|
for issue in issues:
|
||||||
if issue.hasProp("project"):
|
let context =
|
||||||
let context =
|
if issue.hasProp("context"): issue["context"]
|
||||||
if issue.hasProp("context"): issue["context"]
|
else: NO_CONTEXT
|
||||||
else: "<no-context>"
|
|
||||||
|
|
||||||
if not projectsByContext.hasKey(context):
|
let projectName =
|
||||||
projectsByContext[context] = newCountTable[string]()
|
if issue.hasProp("project"): issue["project"]
|
||||||
|
else: NO_PROJECT
|
||||||
|
|
||||||
projectsByContext[context].inc(issue["project"])
|
if not projectsByContext.hasKey(context):
|
||||||
|
projectsByContext[context] = newCountTable[string]()
|
||||||
|
|
||||||
|
projectsByContext[context].inc(projectName)
|
||||||
|
|
||||||
for (context, projects) in pairs(projectsByContext):
|
for (context, projects) in pairs(projectsByContext):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user