Include issues without project or milestone on boards.
In order to help organize issues, show issues on boards even if they don't have an assigned project or milestone. Refactor the issue hiding feature (using the `hide-until` property) to be an option to IssueFilter rather than a separate, special-case. This means that the CLI always filters by default. Hide issues in the Done state on project boards unless the new `--show-done` arg is passed.
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
26
src/pit.nim
26
src/pit.nim
@@ -172,8 +172,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 +205,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 +425,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 +441,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]
|
||||||
@@ -239,4 +239,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]]
|
||||||
|
|
||||||
@@ -151,6 +152,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 +186,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,6 +422,12 @@ 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)
|
f = f --> filter(it.hasProp(k) and it[k] == v)
|
||||||
|
|
||||||
|
|||||||
@@ -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