From d3eacc67e46d064584241ef6c68f092105b64ad4 Mon Sep 17 00:00:00 2001 From: Chris Howey Date: Fri, 26 Jul 2024 19:43:15 -0500 Subject: [PATCH] fast color output --- go.mod | 4 +- ledger/cmd/print.go | 37 ++++++-------- ledger/internal/fastcolor/LICENSE.txt | 20 ++++++++ ledger/internal/fastcolor/fastcolor.go | 71 ++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 ledger/internal/fastcolor/LICENSE.txt create mode 100644 ledger/internal/fastcolor/fastcolor.go diff --git a/go.mod b/go.mod index 5618b083..81e1912e 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.22 require ( github.com/alfredxing/calc v0.0.0-20180827002445-77daf576f976 github.com/andybalholm/brotli v1.0.6 - github.com/fatih/color v1.15.0 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b github.com/ivanpirog/coloredcobra v1.0.1 github.com/jbrukh/bayesian v0.0.0-20200318221351-d726b684ca4a github.com/joyt/godate v0.0.0-20150226210126-7151572574a7 github.com/juztin/numeronym v0.0.0-20160223091026-859fcc2918e2 github.com/lucasb-eyer/go-colorful v1.2.0 + github.com/mattn/go-isatty v0.0.20 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pelletier/go-toml v1.9.5 github.com/shopspring/decimal v1.3.1 @@ -21,9 +21,9 @@ require ( ) require ( + github.com/fatih/color v1.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.13.0 // indirect ) diff --git a/ledger/cmd/print.go b/ledger/cmd/print.go index a177f939..e6816550 100644 --- a/ledger/cmd/print.go +++ b/ledger/cmd/print.go @@ -13,9 +13,9 @@ import ( "time" "unicode/utf8" - "github.com/fatih/color" "github.com/howeyc/ledger" "github.com/howeyc/ledger/decimal" + "github.com/howeyc/ledger/ledger/internal/fastcolor" date "github.com/joyt/godate" "github.com/spf13/cobra" "golang.org/x/term" @@ -121,12 +121,10 @@ func PrintBalances(accountList []*ledger.Account, printZeroBalances bool, depth, fmt.Fprintf(os.Stderr, "warning: `columns` too small, setting to %d\n", columns) } accWidth := columns - 11 - formatAcc := fmt.Sprintf("%%-%[1]d.%[1]ds", accWidth) - formatAmt := "%10.10s" - colorNeg := color.New(color.FgRed) - colorAccount := color.New(color.FgBlue) - colorReset := color.New(color.Reset) + colorNeg := fastcolor.FgRed + colorAccount := fastcolor.FgBlue + colorReset := fastcolor.Reset buf := bufio.NewWriter(os.Stdout) overallBalance := decimal.Zero @@ -141,9 +139,9 @@ func PrintBalances(accountList []*ledger.Account, printZeroBalances bool, depth, if account.Balance.Sign() < 0 { amtColor = colorNeg } - colorAccount.Fprintf(buf, formatAcc, account.Name) + colorAccount.WriteStringFixed(buf, account.Name, accWidth, false) buf.WriteString(" ") - amtColor.Fprintf(buf, formatAmt, outBalanceString) + amtColor.WriteStringFixed(buf, outBalanceString, 10, true) buf.WriteString(newLine) } } @@ -153,9 +151,9 @@ func PrintBalances(accountList []*ledger.Account, printZeroBalances bool, depth, if overallBalance.Sign() < 0 { amtColor = colorNeg } - colorAccount.Fprintf(buf, formatAcc, "") + colorAccount.WriteStringFixed(buf, "", accWidth, false) buf.WriteString(" ") - amtColor.Fprintf(buf, formatAmt, outBalanceString) + amtColor.WriteStringFixed(buf, outBalanceString, 10, true) buf.WriteString(newLine) buf.Flush() } @@ -241,14 +239,11 @@ func PrintRegister(generalLedger []*ledger.Transaction, filterArr []string, colu remainingWidth := columns - (10 * 3) - (4 * 1) col1width := remainingWidth / 3 col2width := remainingWidth - col1width - formatAmount := "%10.10s" - formatPayee := fmt.Sprintf("%%-%[1]d.%[1]ds", col1width) - formatAccount := fmt.Sprintf("%%-%[1]d.%[1]ds", col2width) - colorNeg := color.New(color.FgRed) - colorPayee := color.New(color.Bold) - colorAccount := color.New(color.FgBlue) - colorReset := color.New(color.Reset) + colorNeg := fastcolor.FgRed + colorPayee := fastcolor.Bold + colorAccount := fastcolor.FgBlue + colorReset := fastcolor.Reset buf := bufio.NewWriter(os.Stdout) runningBalance := decimal.Zero @@ -276,13 +271,13 @@ func PrintRegister(generalLedger []*ledger.Transaction, filterArr []string, colu buf.WriteString(trans.Date.Format(transactionDateFormat)) buf.WriteString(" ") - colorPayee.Fprintf(buf, formatPayee, trans.Payee) + colorPayee.WriteStringFixed(buf, trans.Payee, col1width, false) buf.WriteString(" ") - colorAccount.Fprintf(buf, formatAccount, accChange.Name) + colorAccount.WriteStringFixed(buf, accChange.Name, col2width, false) buf.WriteString(" ") - balamtColor.Fprintf(buf, formatAmount, outBalanceString) + balamtColor.WriteStringFixed(buf, outBalanceString, 10, true) buf.WriteString(" ") - runamtColor.Fprintf(buf, formatAmount, outRunningBalanceString) + runamtColor.WriteStringFixed(buf, outRunningBalanceString, 10, true) buf.WriteString(newLine) } } diff --git a/ledger/internal/fastcolor/LICENSE.txt b/ledger/internal/fastcolor/LICENSE.txt new file mode 100644 index 00000000..25fdaf63 --- /dev/null +++ b/ledger/internal/fastcolor/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Fatih Arslan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ledger/internal/fastcolor/fastcolor.go b/ledger/internal/fastcolor/fastcolor.go new file mode 100644 index 00000000..0d2d29f0 --- /dev/null +++ b/ledger/internal/fastcolor/fastcolor.go @@ -0,0 +1,71 @@ +// Package fastcolor is an extreme subset of the fatih/color package to get +// ANSI colors on standard output. +// +// Modified to output the color string to a StringWriter with fixed-width +// formatting (spaces for padding). Minimal color and attribute support. +package fastcolor + +import ( + "io" + "os" + "strings" + "unicode/utf8" + + "github.com/mattn/go-isatty" +) + +type Color string + +const ( + Reset Color = "0" + Bold Color = "1" + FgBlack Color = "30" + FgRed Color = "31" + FgGreen Color = "32" + FgYellow Color = "33" + FgBlue Color = "34" + FgMagenta Color = "35" + FgCyan Color = "36" + FgWhite Color = "37" +) + +var spaceStr string = strings.Repeat(" ", 132) + +// NoColor defines if the output is colorized or not. It's dynamically set to +// false or true based on the stdout's file descriptor referring to a terminal +// or not. It's also set to true if the NO_COLOR environment variable is +// set (regardless of its value). This is a global option and affects all +// colors. +var NoColor = noColorIsSet() || os.Getenv("TERM") == "dumb" || + (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) + +// noColorIsSet returns true if the environment variable NO_COLOR is set to a non-empty string. +func noColorIsSet() bool { + return os.Getenv("NO_COLOR") != "" +} + +func (c Color) WriteStringFixed(w io.StringWriter, s string, width int, leftpad bool) { + if !NoColor { + w.WriteString("\x1b[") + w.WriteString(string(c)) + w.WriteString("m") + } + + l := utf8.RuneCountInString(s) + spaces := width - l + if spaces > 0 { + if leftpad { + w.WriteString(spaceStr[:spaces]) + w.WriteString(s) + } else { + w.WriteString(s) + w.WriteString(spaceStr[:spaces]) + } + } else { + w.WriteString(s[:width]) + } + + if !NoColor { + w.WriteString("\x1b[0m") + } +}