From 89c924bb72df5152b1a2ada5249218bcaa732a7e Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Mon, 1 Dec 2025 18:07:03 -0600 Subject: [PATCH] Only emit ANSI escape codes when stdout is a TTY. --- pit.nimble | 2 +- src/pit/cliconstants.nim | 4 +-- src/pit/libpit.nim | 2 +- src/pit/projects.nim | 70 ++++++++++++++++++++++++++-------------- 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/pit.nimble b/pit.nimble index 80284d2..78edde5 100644 --- a/pit.nimble +++ b/pit.nimble @@ -1,6 +1,6 @@ # Package -version = "4.31.1" +version = "4.31.2" author = "Jonathan Bernard" description = "Personal issue tracker." license = "MIT" diff --git a/src/pit/cliconstants.nim b/src/pit/cliconstants.nim index df2a56f..378cd70 100644 --- a/src/pit/cliconstants.nim +++ b/src/pit/cliconstants.nim @@ -1,4 +1,4 @@ -const PIT_VERSION* = "4.31.1" +const PIT_VERSION* = "4.31.2" const USAGE* = """Usage: pit ( new | add) [] [options] @@ -247,4 +247,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/libpit.nim b/src/pit/libpit.nim index 27808ae..8f2579d 100644 --- a/src/pit/libpit.nim +++ b/src/pit/libpit.nim @@ -1,4 +1,4 @@ -import std/[json, jsonutils, logging, options, os, strformat, strutils, tables, times, +import std/[json, logging, options, os, strformat, strutils, tables, times, unicode] import cliutils, docopt, langutils, uuids, zero_functional diff --git a/src/pit/projects.nim b/src/pit/projects.nim index 9261e71..5e8f60f 100644 --- a/src/pit/projects.nim +++ b/src/pit/projects.nim @@ -1,12 +1,12 @@ import std/[algorithm, json, jsonutils, options, os, sets, strutils, tables, - terminal, times, unicode, wordwrap] + terminal, times, unicode] from std/sequtils import repeat, toSeq import cliutils, uuids, zero_functional import ./[formatting, libpit] -const NO_PROJECT* = "" -const NO_MILESTONE* = "" -const NO_CONTEXT* = "" +const NO_PROJECT* = "∅ No Project" +const NO_MILESTONE* = "∅ No Milestone" +const NO_CONTEXT* = "" type ProjectCfg* = ref object of RootObj @@ -129,6 +129,8 @@ proc listProjects*(ctx: CliContext, filter = none[IssueFilter]()) = let projectsCfg = ctx.loadProjectsConfiguration() var projectsCfgChanged = false + var linesToPrint = newSeq[string]() + if filter.isSome: ctx.filterIssues(filter.get) let projectsByContext = newTable[string, CountTableRef[string]]() @@ -150,10 +152,10 @@ proc listProjects*(ctx: CliContext, filter = none[IssueFilter]()) = for (context, projects) in pairs(projectsByContext): - stdout.writeLine(withColor( + linesToPrint.add(withColor( ctx.getIssueContextDisplayName(context) & ":", fgYellow) & termReset) - stdout.writeLine("") + linesToPrint.add("") var toList = toHashSet(toSeq(keys(projects))) @@ -164,22 +166,29 @@ proc listProjects*(ctx: CliContext, filter = none[IssueFilter]()) = for project in projectsCfg[context]: if project.name in toList: toList.excl(project.name) - stdout.writeLine(" " & project.name & + linesToPrint.add(" " & project.name & " (" & $projects[project.name] & " issues)") # Then list any remaining projects not in the configuration, and add them # to the configuration for (projectName, count) in pairs(projects): if projectName in toList: - stdout.writeLine(" " & projectName & " (" & $count & " issues)") + linesToPrint.add(" " & projectName & " (" & $count & " issues)") projectsCfg[context].add(ProjectCfg(name: projectName, milestoneOrder: @[])) projectsCfgChanged = true - stdout.writeLine("") + linesToPrint.add("") if projectsCfgChanged: ctx.saveProjectsConfiguration(projectsCfg) + if isatty(stdout): + stdout.writeLine(linesToPrint.join("\p")) + else: + stdout.writeLine(stripAnsi(linesToPrint.join("\p"))) + proc listMilestones*(ctx: CliContext, filter = none[IssueFilter]()) = + var linesToPrint = newSeq[string]() + ctx.loadAllIssues() if filter.isSome: ctx.filterIssues(filter.get) @@ -194,8 +203,8 @@ proc listMilestones*(ctx: CliContext, filter = none[IssueFilter]()) = if values(project.milestones) --> all(it.len == 0): continue - stdout.writeLine(withColor(project.name, fgBlue, bold = true, bright=true)) - stdout.writeLine(withColor( + linesToPrint.add(withColor(project.name, fgBlue, bold = true, bright=true)) + linesToPrint.add(withColor( "─".repeat(runeLen(stripAnsi(project.name))), fgBlue, bold = true)) @@ -203,9 +212,15 @@ proc listMilestones*(ctx: CliContext, filter = none[IssueFilter]()) = if project.milestones.hasKey(milestone) and project.milestones[milestone].len > 0: let issueCount = project.milestones[milestone].len - stdout.writeLine(" " & milestone & " (" & $issueCount & " issues)") + linesToPrint.add(" " & milestone & " (" & $issueCount & " issues)") + + linesToPrint.add("") + + if isatty(stdout): + stdout.writeLine(linesToPrint.join("\p")) + else: + stdout.writeLine(stripAnsi(linesToPrint.join("\p"))) - stdout.writeLine("") proc formatProjectIssue( ctx: CliContext, @@ -306,22 +321,23 @@ proc joinColumns(columns: seq[seq[string]], columnWidth: int): seq[string] = proc showProject*(ctx: CliContext, project: Project) = + var linesToPrint = newSeq[string]() let fullWidth = terminalWidth() - 1 let columnWidth = 80 let numColumns = (fullWidth - 4) div (columnWidth + 2) - stdout.writeLine("") - stdout.writeLine(withColor( - "┌" & "─".repeat(project.name.len + 2) & - "┬" & "─".repeat(fullWidth - project.name.len - 4) & "┐", + linesToPrint.add("") + linesToPrint.add(withColor( + "┌" & "─".repeat(project.name.runeLen + 2) & + "┬" & "─".repeat(fullWidth - project.name.runeLen - 4) & "┐", fgBlue, bold=true)) - stdout.writeLine( + linesToPrint.add( withColor("│ ", fgBlue, bold=true) & withColor(project.name, fgBlue, bold=true, bright=true) & - withColor(" │" & " ".repeat(fullWidth - project.name.len - 4) & "│", fgBlue, bold=true)) - stdout.writeLine(withColor( - "├" & "─".repeat(project.name.len + 2) & - "┘" & " ".repeat(fullWidth - project.name.len - 4) & "│", + withColor(" │" & " ".repeat(fullWidth - project.name.runeLen - 4) & "│", fgBlue, bold=true)) + linesToPrint.add(withColor( + "├" & "─".repeat(project.name.runeLen + 2) & + "┘" & " ".repeat(fullWidth - project.name.runeLen - 4) & "│", fgBlue, bold=true)) let milestoneTexts: seq[seq[string]] = project.milestoneOrder --> @@ -338,16 +354,22 @@ proc showProject*(ctx: CliContext, project: Project) = for line in joinedLines: let padLen = fullWidth - runeLen(stripAnsi(line)) - 3 - stdout.writeLine( + linesToPrint.add( withColor("│ ", fgBlue) & line & " ".repeat(padLen) & withColor(" │", fgBlue)) - stdout.writeLine(withColor( + linesToPrint.add(withColor( "└" & "─".repeat(terminalWidth() - 2) & "┘", fgBlue, bold=true)) + if isatty(stdout): + stdout.writeLine(linesToPrint.join("\p")) + else: + stdout.writeLine(stripAnsi(linesToPrint.join("\p"))) + + proc showProjectBoard*(ctx: CliContext, filter = none[IssueFilter]()) = ctx.loadAllIssues()