diff --git a/cfg/config.go b/cfg/config.go index 2517263..e0f0208 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -1,5 +1,5 @@ /* -Copyright © 2022-2025 Thomas von Dein +Copyright © 2022-2026 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -102,10 +102,14 @@ type Config struct { SortByColumn string // 1,2 UseSortByColumn []int // []int{1,2} - TransposeColumns string // 1,2 - UseTransposeColumns []int // []int{1,2} - Transposers []string // []string{"/ /-/", "/foo/bar/"} - UseTransposers []Transposer // {Search: re, Replace: string} + TransposeColumns string // 1,2 + UseTransposeColumns []int // []int{1,2} + + Transposers []string // []string{"/ /-/", "/foo/bar/"} + UseTransposers []Transposer // {Search: re, Replace: string} + + Colorizers []string // []string{"/ /-/", "/foo/fg[:bg]/"} + UseColorizers []Transposer // {Search: re, Replace: color} /* FIXME: make configurable somehow, config file or ENV @@ -357,6 +361,23 @@ func (conf *Config) PrepareTransposers() error { return nil } +func (conf *Config) PrepareColorizers() error { + for _, colorizer := range conf.Colorizers { + parts := strings.Split(colorizer, string(colorizer[0])) + if len(parts) != 4 { + return fmt.Errorf("colorizer function must have the format /regexp/foreground-color[:background-color]/") + } + + conf.UseColorizers = append(conf.UseColorizers, + Transposer{ + Search: *regexp.MustCompile(parts[1]), + Replace: parts[2]}, + ) + } + + return nil +} + func (conf *Config) CheckEnv() { // check for environment vars, command line flags have precedence, // NO_COLOR is being checked by the color module itself. diff --git a/cmd/root.go b/cmd/root.go index d9ceddf..404e5ea 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,5 +1,5 @@ /* -Copyright © 2022-2025 Thomas von Dein +Copyright © 2022-2026 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -95,6 +95,7 @@ func Execute() { conf.PrepareCustomHeaders(headers) wrapE(conf.PrepareFilters()) + wrapE(conf.PrepareColorizers()) conf.DetermineColormode() conf.ApplyDefaults() @@ -191,6 +192,8 @@ func Execute() { "filter", "F", nil, "Filter by field (field=regexp || field!=regexp)") rootCmd.PersistentFlags().StringArrayVarP(&conf.Transposers, "regex-transposer", "R", nil, "apply /search/replace/ regexp to fields given in -T") + rootCmd.PersistentFlags().StringArrayVarP(&conf.Colorizers, + "regex-colorizer", "K", nil, "apply /search/color[:background]/ to the whole output") // input rootCmd.PersistentFlags().StringVarP(&conf.InputFile, "read-file", "r", "", diff --git a/cmd/shortusage.go b/cmd/shortusage.go index aa2f118..9c8b9f6 100644 --- a/cmd/shortusage.go +++ b/cmd/shortusage.go @@ -13,6 +13,7 @@ const shortusage = `tablizer [regex,...] [-r file] [flags] -x col,... use custom headers -d debug -o char use char as output separator -g auto generate headers +-K /pattern/foreground[:background]/ colorize pattern of output -O org -C CSV -M md -X ext -S shell -Y yaml -J json -P template -a sort by age -i sort numerically -t sort by time -D sort descending order -m show manual -v show version --help show detailed help` diff --git a/lib/filter.go b/lib/filter.go index f603c8b..3d2f349 100644 --- a/lib/filter.go +++ b/lib/filter.go @@ -1,5 +1,5 @@ /* -Copyright © 2022-2025 Thomas von Dein +Copyright © 2022-2026 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,8 +22,8 @@ import ( "io" "strings" - "github.com/lithammer/fuzzysearch/fuzzy" "codeberg.org/scip/tablizer/cfg" + "github.com/lithammer/fuzzysearch/fuzzy" ) /* diff --git a/lib/helpers.go b/lib/helpers.go index cd63cd8..6027b08 100644 --- a/lib/helpers.go +++ b/lib/helpers.go @@ -1,5 +1,5 @@ /* -Copyright © 2022 Thomas von Dein +Copyright © 2022-2026 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,8 +26,8 @@ import ( "strconv" "strings" - "github.com/gookit/color" "codeberg.org/scip/tablizer/cfg" + "github.com/gookit/color" ) func findindex(s []int, e int) (int, bool) { diff --git a/lib/printer.go b/lib/printer.go index 72ba39e..367f4a0 100644 --- a/lib/printer.go +++ b/lib/printer.go @@ -1,5 +1,5 @@ /* -Copyright © 2022-2025 Thomas von Dein +Copyright © 2022-2026 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ import ( "fmt" "io" "log" + "os" "strconv" "strings" "text/template" @@ -75,8 +76,8 @@ func printData(writer io.Writer, conf cfg.Config, data *Tabdata) { } } -func output(writer io.Writer, str string) { - _, err := fmt.Fprint(writer, str) +func output(writer io.Writer, conf cfg.Config, str string) { + _, err := fmt.Fprint(writer, colorizeOutput(conf, str)) if err != nil { log.Fatalf("failed to print output: %s", err) } @@ -138,7 +139,7 @@ func printOrgmodeData(writer io.Writer, conf cfg.Config, data *Tabdata) { log.Fatalf("Failed to render table: %s", err) } - output(writer, color.Sprint(colorizeData(conf, tableString.String()))) + output(writer, conf, color.Sprint(colorizeData(conf, tableString.String()))) } /* @@ -197,7 +198,7 @@ func printMarkdownData(writer io.Writer, conf cfg.Config, data *Tabdata) { log.Fatalf("Failed to render table: %s", err) } - output(writer, color.Sprint(colorizeData(conf, tableString.String()))) + output(writer, conf, color.Sprint(colorizeData(conf, tableString.String()))) } /* @@ -254,7 +255,7 @@ func printASCIIData(writer io.Writer, conf cfg.Config, data *Tabdata) { log.Fatalf("Failed to render table: %s", err) } - output(writer, color.Sprint(colorizeData(conf, tableString.String()))) + output(writer, conf, color.Sprint(colorizeData(conf, tableString.String()))) } /* @@ -275,7 +276,7 @@ func printExtendedData(writer io.Writer, conf cfg.Config, data *Tabdata) { } } - output(writer, colorizeData(conf, out)) + output(writer, conf, colorizeData(conf, out)) } /* @@ -298,7 +299,7 @@ func printShellData(writer io.Writer, data *Tabdata) { } // no colorization here - output(writer, out) + output(writer, cfg.Config{}, out) } func printJsonData(writer io.Writer, data *Tabdata) { @@ -327,7 +328,7 @@ func printJsonData(writer io.Writer, data *Tabdata) { log.Fatal(err) } - output(writer, string(jsonstr)) + output(writer, cfg.Config{}, string(jsonstr)) } func printYamlData(writer io.Writer, data *Tabdata) { @@ -364,7 +365,7 @@ func printYamlData(writer io.Writer, data *Tabdata) { log.Fatal(err) } - output(writer, string(yamlstr)) + output(writer, cfg.Config{}, string(yamlstr)) } func printCSVData(writer io.Writer, conf cfg.Config, data *Tabdata) { @@ -414,3 +415,23 @@ func printTemplateData(writer io.Writer, conf cfg.Config, data *Tabdata) { log.Fatalf("failed to print output: %s", err) } } + +func colorizeOutput(conf cfg.Config, input string) string { + if len(conf.UseColorizers) > 0 && !conf.NoColor && color.IsConsole(os.Stdout) { + for _, colorizer := range conf.UseColorizers { + // colorize matching parts of the whole output with given color, if the terminal supports it + // color may contain fg:bg or just fg. Color definitions see https://github.com/gookit/color + input = colorizer.Search.ReplaceAllStringFunc(input, func(in string) string { + col := colorizer.Replace + if strings.Contains(col, ":") { + parts := strings.Split(col, ":") + return color.Sprintf(fmt.Sprintf("%s", parts[0], parts[1], in)) + } + + return color.Sprintf(fmt.Sprintf("%s", col, in)) + }) + } + } + + return input +} diff --git a/t/testtable6.csv b/t/testtable6.csv new file mode 100644 index 0000000..4c9c01b --- /dev/null +++ b/t/testtable6.csv @@ -0,0 +1,5 @@ +Date,Account Number,Subject,Amount +20250101,968723487,Dogs Medication Invoice 9919292,-450.00 +20250103,172747812,Tax return tax id HHD813D/12564H,+912.14 +20250105,987122711,Car repair order 020123,-299.45 +20250108,731217273,Rent - 12234 Sunset Blvd,-2960.00 diff --git a/tablizer.pod b/tablizer.pod index 740cdb0..4884981 100644 --- a/tablizer.pod +++ b/tablizer.pod @@ -19,6 +19,7 @@ tablizer - Manipulate tabular output of other programs -F, --filter Filter given field with regex, can be used multiple times -T, --transpose-columns string Transpose the speficied columns (separated by ,) -R, --regex-transposer Apply /search/replace/ regexp to fields given in -T + -K --regex-colorizer /from/color/ colorize pattern of output (color: fg[:bg]) -j, --json Read JSON input (must be array of hashes) -I, --interactive Interactively filter and select rows -g, --auto-headers Generate headers if there are none present in input @@ -514,9 +515,20 @@ black, blue, cyan, darkGray, default, green, lightBlue, lightCyan, lightGreen, lightMagenta, lightRed, lightWhite, lightYellow, magenta, red, white, yellow -The Variables B and B are being used to highlight matches. The -other *FG and *BG variables are for colored table output (enabled with -the C<-L> parameter). +but you may also use HTML color codes without the hash sign. + +The Variables B and B are being used to highlight matching +rows. The other *FG and *BG variables are for colored table output +(enabled with the C<-L> parameter). + +You can also use the option C<-K> to colorize particular patterns, not +whole lines. The option can be given multiple times and expects the +following parameter: + + -K '/regex/foreground[:background]/ + +that is, background color is optional. This colorization will applied +on top of any previous colorizations, if any. Colorization can be turned off completely either by setting the parameter C<-N> or the environment variable B to a true value.