Only emit ANSI escape codes when stdout is a TTY.

This commit is contained in:
2025-12-01 18:07:03 -06:00
parent 3d1dc7512a
commit 89c924bb72
4 changed files with 50 additions and 28 deletions

View File

@@ -1,6 +1,6 @@
# Package # Package
version = "4.31.1" version = "4.31.2"
author = "Jonathan Bernard" author = "Jonathan Bernard"
description = "Personal issue tracker." description = "Personal issue tracker."
license = "MIT" license = "MIT"

View File

@@ -1,4 +1,4 @@
const PIT_VERSION* = "4.31.1" const PIT_VERSION* = "4.31.2"
const USAGE* = """Usage: const USAGE* = """Usage:
pit ( new | add) <summary> [<state>] [options] pit ( new | add) <summary> [<state>] [options]

View File

@@ -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] unicode]
import cliutils, docopt, langutils, uuids, zero_functional import cliutils, docopt, langutils, uuids, zero_functional

View File

@@ -1,12 +1,12 @@
import std/[algorithm, json, jsonutils, options, os, sets, strutils, tables, import std/[algorithm, json, jsonutils, options, os, sets, strutils, tables,
terminal, times, unicode, wordwrap] terminal, times, unicode]
from std/sequtils import repeat, toSeq 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_PROJECT* = "∅ No Project"
const NO_MILESTONE* = "<no-project>" const NO_MILESTONE* = "∅ No Milestone"
const NO_CONTEXT* = "<no-project>" const NO_CONTEXT* = "<no-context>"
type type
ProjectCfg* = ref object of RootObj ProjectCfg* = ref object of RootObj
@@ -129,6 +129,8 @@ proc listProjects*(ctx: CliContext, filter = none[IssueFilter]()) =
let projectsCfg = ctx.loadProjectsConfiguration() let projectsCfg = ctx.loadProjectsConfiguration()
var projectsCfgChanged = false var projectsCfgChanged = false
var linesToPrint = newSeq[string]()
if filter.isSome: ctx.filterIssues(filter.get) if filter.isSome: ctx.filterIssues(filter.get)
let projectsByContext = newTable[string, CountTableRef[string]]() let projectsByContext = newTable[string, CountTableRef[string]]()
@@ -150,10 +152,10 @@ proc listProjects*(ctx: CliContext, filter = none[IssueFilter]()) =
for (context, projects) in pairs(projectsByContext): for (context, projects) in pairs(projectsByContext):
stdout.writeLine(withColor( linesToPrint.add(withColor(
ctx.getIssueContextDisplayName(context) & ":", ctx.getIssueContextDisplayName(context) & ":",
fgYellow) & termReset) fgYellow) & termReset)
stdout.writeLine("") linesToPrint.add("")
var toList = toHashSet(toSeq(keys(projects))) var toList = toHashSet(toSeq(keys(projects)))
@@ -164,22 +166,29 @@ proc listProjects*(ctx: CliContext, filter = none[IssueFilter]()) =
for project in projectsCfg[context]: for project in projectsCfg[context]:
if project.name in toList: if project.name in toList:
toList.excl(project.name) toList.excl(project.name)
stdout.writeLine(" " & project.name & linesToPrint.add(" " & project.name &
" (" & $projects[project.name] & " issues)") " (" & $projects[project.name] & " issues)")
# Then list any remaining projects not in the configuration, and add them # Then list any remaining projects not in the configuration, and add them
# to the configuration # to the configuration
for (projectName, count) in pairs(projects): for (projectName, count) in pairs(projects):
if projectName in toList: if projectName in toList:
stdout.writeLine(" " & projectName & " (" & $count & " issues)") linesToPrint.add(" " & projectName & " (" & $count & " issues)")
projectsCfg[context].add(ProjectCfg(name: projectName, milestoneOrder: @[])) projectsCfg[context].add(ProjectCfg(name: projectName, milestoneOrder: @[]))
projectsCfgChanged = true projectsCfgChanged = true
stdout.writeLine("") linesToPrint.add("")
if projectsCfgChanged: ctx.saveProjectsConfiguration(projectsCfg) 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]()) = proc listMilestones*(ctx: CliContext, filter = none[IssueFilter]()) =
var linesToPrint = newSeq[string]()
ctx.loadAllIssues() ctx.loadAllIssues()
if filter.isSome: ctx.filterIssues(filter.get) 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): if values(project.milestones) --> all(it.len == 0):
continue continue
stdout.writeLine(withColor(project.name, fgBlue, bold = true, bright=true)) linesToPrint.add(withColor(project.name, fgBlue, bold = true, bright=true))
stdout.writeLine(withColor( linesToPrint.add(withColor(
"".repeat(runeLen(stripAnsi(project.name))), "".repeat(runeLen(stripAnsi(project.name))),
fgBlue, bold = true)) fgBlue, bold = true))
@@ -203,9 +212,15 @@ proc listMilestones*(ctx: CliContext, filter = none[IssueFilter]()) =
if project.milestones.hasKey(milestone) and if project.milestones.hasKey(milestone) and
project.milestones[milestone].len > 0: project.milestones[milestone].len > 0:
let issueCount = project.milestones[milestone].len 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( proc formatProjectIssue(
ctx: CliContext, ctx: CliContext,
@@ -306,22 +321,23 @@ proc joinColumns(columns: seq[seq[string]], columnWidth: int): seq[string] =
proc showProject*(ctx: CliContext, project: Project) = proc showProject*(ctx: CliContext, project: Project) =
var linesToPrint = newSeq[string]()
let fullWidth = terminalWidth() - 1 let fullWidth = terminalWidth() - 1
let columnWidth = 80 let columnWidth = 80
let numColumns = (fullWidth - 4) div (columnWidth + 2) let numColumns = (fullWidth - 4) div (columnWidth + 2)
stdout.writeLine("") linesToPrint.add("")
stdout.writeLine(withColor( linesToPrint.add(withColor(
"" & "".repeat(project.name.len + 2) & "" & "".repeat(project.name.runeLen + 2) &
"" & "".repeat(fullWidth - project.name.len - 4) & "", "" & "".repeat(fullWidth - project.name.runeLen - 4) & "",
fgBlue, bold=true)) fgBlue, bold=true))
stdout.writeLine( linesToPrint.add(
withColor("", fgBlue, bold=true) & withColor("", fgBlue, bold=true) &
withColor(project.name, fgBlue, bold=true, bright=true) & withColor(project.name, fgBlue, bold=true, bright=true) &
withColor("" & " ".repeat(fullWidth - project.name.len - 4) & "", fgBlue, bold=true)) withColor("" & " ".repeat(fullWidth - project.name.runeLen - 4) & "", fgBlue, bold=true))
stdout.writeLine(withColor( linesToPrint.add(withColor(
"" & "".repeat(project.name.len + 2) & "" & "".repeat(project.name.runeLen + 2) &
"" & " ".repeat(fullWidth - project.name.len - 4) & "", "" & " ".repeat(fullWidth - project.name.runeLen - 4) & "",
fgBlue, bold=true)) fgBlue, bold=true))
let milestoneTexts: seq[seq[string]] = project.milestoneOrder --> let milestoneTexts: seq[seq[string]] = project.milestoneOrder -->
@@ -338,16 +354,22 @@ proc showProject*(ctx: CliContext, project: Project) =
for line in joinedLines: for line in joinedLines:
let padLen = fullWidth - runeLen(stripAnsi(line)) - 3 let padLen = fullWidth - runeLen(stripAnsi(line)) - 3
stdout.writeLine( linesToPrint.add(
withColor("", fgBlue) & withColor("", fgBlue) &
line & line &
" ".repeat(padLen) & " ".repeat(padLen) &
withColor("", fgBlue)) withColor("", fgBlue))
stdout.writeLine(withColor( linesToPrint.add(withColor(
"" & "".repeat(terminalWidth() - 2) & "", "" & "".repeat(terminalWidth() - 2) & "",
fgBlue, bold=true)) 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]()) = proc showProjectBoard*(ctx: CliContext, filter = none[IssueFilter]()) =
ctx.loadAllIssues() ctx.loadAllIssues()