From 1d18be9d1b1b5a0abe4bab4e5879c3b6425d8ddf Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Mon, 1 Dec 2025 13:50:13 -0600 Subject: [PATCH] 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. --- pit.nimble | 2 +- src/pit.nim | 26 +++++++++----------------- src/pit/cliconstants.nim | 6 +++--- src/pit/formatting.nim | 11 ++--------- src/pit/libpit.nim | 12 ++++++++++++ src/pit/projects.nim | 36 +++++++++++++++++++++++------------- 6 files changed, 50 insertions(+), 43 deletions(-) diff --git a/pit.nimble b/pit.nimble index f8fdd8f..80284d2 100644 --- a/pit.nimble +++ b/pit.nimble @@ -1,6 +1,6 @@ # Package -version = "4.31.0" +version = "4.31.1" author = "Jonathan Bernard" description = "Personal issue tracker." license = "MIT" diff --git a/src/pit.nim b/src/pit.nim index 637bfa7..6d3860e 100644 --- a/src/pit.nim +++ b/src/pit.nim @@ -172,8 +172,6 @@ when isMainModule: var exclTagsOption = none(seq[string]) let filter = initFilter() - var filterOption = none(IssueFilter) - if args["--properties"] or args["--context"]: @@ -207,43 +205,37 @@ when isMainModule: # Initialize filter with properties (if given) if propertiesOption.isSome: filter.properties = propertiesOption.get - filterOption = some(filter) # Add property exclusions (if given) if exclPropsOption.isSome: filter.exclProperties = exclPropsOption.get - filterOption = some(filter) # If they supplied text matches, add that to the filter. if args["--match"]: filter.summaryMatch = some(re("(?i)" & $args["--match"])) - filterOption = some(filter) if 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 ctx.defaultContext.isSome and not filter.properties.hasKey("context"): stderr.writeLine("Limiting to default context: " & ctx.defaultContext.get) filter.properties["context"] = ctx.defaultContext.get - filterOption = some(filter) if tagsOption.isSome: filter.hasTags = tagsOption.get - filterOption = some(filter) if exclTagsOption.isSome: filter.exclTags = exclTagsOption.get - filterOption = some(filter) if args["--today"]: filter.inclStates.add(@[Current, TodoToday, Pending]) - filterOption = some(filter) if args["--future"]: 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 if filter.properties.hasKey("context") and @@ -433,7 +425,7 @@ when isMainModule: for state in statesOption.get: ctx.loadIssues(state) else: ctx.loadAllIssues() - if filterOption.isSome: ctx.filterIssues(filterOption.get) + ctx.filterIssues(filter) for state, issueList in ctx.issues: for issue in issueList: @@ -449,27 +441,27 @@ when isMainModule: stdout.writeLine formatIssue(issue) # List projects - elif listProjects: ctx.listProjects(filterOption) + elif listProjects: ctx.listProjects(some(filter)) # List milestones - elif listMilestones: ctx.listMilestones(filterOption) + elif listMilestones: ctx.listMilestones(some(filter)) # List all issues else: trace "listing all issues" let showBoth = args["--today"] == args["--future"] ctx.list( - filter = filterOption, + filter = some(filter), states = statesOption, showToday = showBoth or args["--today"], showFuture = showBoth or args["--future"], - showHidden = args["--show-hidden"], verbose = ctx.verbose) elif args["show"]: if args["project-board"]: - ctx.showProjectBoard(filterOption) + if not args["--show-done"]: filter.exclStates.add(Done) + ctx.showProjectBoard(some(filter)) discard elif args["dupes"]: diff --git a/src/pit/cliconstants.nim b/src/pit/cliconstants.nim index 49d4422..2c28e8f 100644 --- a/src/pit/cliconstants.nim +++ b/src/pit/cliconstants.nim @@ -1,4 +1,4 @@ -const PIT_VERSION* = "4.31.0" +const PIT_VERSION* = "4.31.1" const USAGE* = """Usage: pit ( new | add) [] [options] @@ -18,7 +18,7 @@ const USAGE* = """Usage: pit add-binary-property [options] pit get-binary-property [options] pit show dupes [options] - pit show project-board [options] + pit show project-board [--show-done] [options] pit show [options] pit sync [...] [options] pit help [options] @@ -239,4 +239,4 @@ Issue Properties: If present, expected to be a comma-delimited list of text tags. The -g option is a short-hand for '-p tags:'. -""" \ No newline at end of file +""" diff --git a/src/pit/formatting.nim b/src/pit/formatting.nim index 0f440b5..c649bbc 100644 --- a/src/pit/formatting.nim +++ b/src/pit/formatting.nim @@ -173,7 +173,6 @@ proc list*( states: Option[seq[IssueState]], showToday = false, showFuture = false, - showHidden = false, verbose: bool) = if states.isSome: @@ -219,10 +218,7 @@ proc list*( for s in [Current, TodoToday, Pending]: if ctx.issues.hasKey(s) and ctx.issues[s].len > 0: - let visibleIssues = ctx.issues[s].filterIt( - showHidden or - not (it.hasProp("hide-until") and - it.getDateTime("hide-until") > getTime().local)) + let visibleIssues = ctx.issues[s] if isatty(stdout): stdout.write ctx.formatSection(visibleIssues, s, indent, verbose) @@ -242,10 +238,7 @@ proc list*( for s in futureCategories: if ctx.issues.hasKey(s) and ctx.issues[s].len > 0: - let visibleIssues = ctx.issues[s].filterIt( - showHidden or - not (it.hasProp("hide-until") and - it.getDateTime("hide-until") > getTime().local)) + let visibleIssues = ctx.issues[s] if isatty(stdout): stdout.write ctx.formatSection(visibleIssues, s, indent, verbose) diff --git a/src/pit/libpit.nim b/src/pit/libpit.nim index 8c9f990..433c623 100644 --- a/src/pit/libpit.nim +++ b/src/pit/libpit.nim @@ -30,6 +30,7 @@ type exclStates*: seq[IssueState] hasTags*: seq[string] exclTags*: seq[string] + exclHidden*: bool properties*: TableRef[string, string] exclProperties*: TableRef[string, seq[string]] @@ -151,6 +152,7 @@ proc initFilter*(): IssueFilter = summaryMatch: none(Regex), inclStates: @[], exclStates: @[], + exclHidden: true, hasTags: @[], exclTags: @[], properties: newTable[string, string](), @@ -184,6 +186,10 @@ proc stateFilter*(states: seq[IssueState]): IssueFilter = result = initFilter() result.inclStates = states +proc showHiddenFilter*(): IssueFilter = + result = initFilter() + result.exclHidden = false + proc groupBy*(issues: seq[Issue], propertyKey: string): TableRef[string, seq[Issue]] = result = newTable[string, seq[Issue]]() 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] = 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: f = f --> filter(it.hasProp(k) and it[k] == v) diff --git a/src/pit/projects.nim b/src/pit/projects.nim index c6ea050..9261e71 100644 --- a/src/pit/projects.nim +++ b/src/pit/projects.nim @@ -4,6 +4,10 @@ from std/sequtils import repeat, toSeq import cliutils, uuids, zero_functional import ./[formatting, libpit] +const NO_PROJECT* = "" +const NO_MILESTONE* = "" +const NO_CONTEXT* = "" + type ProjectCfg* = ref object of RootObj name: string @@ -62,15 +66,18 @@ proc buildDb*(ctx: CliContext, cfg: ProjectsConfiguration): ProjectsDatabase = # Now populate the database with issues for (state, issues) in pairs(ctx.issues): for issue in issues: - if not issue.hasProp("project") or - not issue.hasProp("milestone"): - continue - let projectName = issue["project"] - let milestone = issue["milestone"] + let projectName = + if issue.hasProp("project"): issue["project"] + else: NO_PROJECT + + let milestone = + if issue.hasProp("milestone"): issue["milestone"] + else: NO_MILESTONE + let context = if issue.hasProp("context"): issue["context"] - else: "" + else: NO_CONTEXT # Make sure we have entries for this context and project 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 issue in issues: - if issue.hasProp("project"): - let context = - if issue.hasProp("context"): issue["context"] - else: "" + let context = + if issue.hasProp("context"): issue["context"] + else: NO_CONTEXT - if not projectsByContext.hasKey(context): - projectsByContext[context] = newCountTable[string]() + let projectName = + 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):