From f1aa9d00006d4ad2e17c58e99850acf36bad3c7d Mon Sep 17 00:00:00 2001 From: "T.v.Dein" Date: Tue, 14 Oct 2025 07:18:30 +0200 Subject: [PATCH] add json output mode (-J) (#87) --- cfg/config.go | 6 +++++- cmd/root.go | 8 +++++--- cmd/shortusage.go | 28 +++++++++++++++------------- cmd/tablizer.go | 14 ++++++++------ lib/printer.go | 32 ++++++++++++++++++++++++++++++++ lib/printer_test.go | 25 +++++++++++++++++++++++++ tablizer.1 | 9 +++++---- tablizer.pod | 7 ++++--- 8 files changed, 99 insertions(+), 30 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 08c7920..b61d988 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -28,7 +28,7 @@ import ( ) const ( - Version = "v1.5.10" + Version = "v1.5.11" MAXPARTS = 2 ) @@ -141,6 +141,7 @@ type Modeflag struct { Y bool A bool C bool + J bool } // used for switching printers @@ -152,6 +153,7 @@ const ( Yaml CSV ASCII + Json ) // various sort types @@ -290,6 +292,8 @@ func (conf *Config) PrepareModeFlags(flag Modeflag) { conf.OutputMode = Yaml case flag.C: conf.OutputMode = CSV + case flag.J: + conf.OutputMode = Json default: conf.OutputMode = ASCII } diff --git a/cmd/root.go b/cmd/root.go index 5e6e8e0..8532abe 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -135,13 +135,13 @@ func Execute() { "Transpose the speficied columns (separated by ,)") rootCmd.PersistentFlags().BoolVarP(&conf.Interactive, "interactive", "I", false, "interactive mode") - rootCmd.PersistentFlags().StringVarP(&conf.OFS, "ofs", "", "", + rootCmd.PersistentFlags().StringVarP(&conf.OFS, "ofs", "o", "", "Output field separator (' ' for ascii table, ',' for CSV)") rootCmd.PersistentFlags().BoolVarP(&conf.InputJSON, "json", "j", false, "JSON input mode") - rootCmd.PersistentFlags().BoolVarP(&conf.AutoHeaders, "auto-headers", "", false, + rootCmd.PersistentFlags().BoolVarP(&conf.AutoHeaders, "auto-headers", "g", false, "Generate headers automatically") - rootCmd.PersistentFlags().StringVarP(&headers, "custom-headers", "", "", + rootCmd.PersistentFlags().StringVarP(&headers, "custom-headers", "x", "", "Custom headers") // sort options @@ -171,6 +171,8 @@ func Execute() { "Enable shell mode output") rootCmd.PersistentFlags().BoolVarP(&modeflag.Y, "yaml", "Y", false, "Enable yaml output") + rootCmd.PersistentFlags().BoolVarP(&modeflag.J, "jsonout", "J", false, + "Enable json output") rootCmd.PersistentFlags().BoolVarP(&modeflag.C, "csv", "C", false, "Enable CSV output") rootCmd.PersistentFlags().BoolVarP(&modeflag.A, "ascii", "A", false, diff --git a/cmd/shortusage.go b/cmd/shortusage.go index 5c22e2c..34cc3ee 100644 --- a/cmd/shortusage.go +++ b/cmd/shortusage.go @@ -1,16 +1,18 @@ package cmd const shortusage = `tablizer [regex,...] [-r file] [flags] --c col,... show specified columns -L highlight matching lines --k col,... sort by specified columns -j read JSON input --F col=reg filter field with regexp -v invert match --T col,... transpose specified columns -n numberize columns --R /from/to/ apply replacement to columns in -T -N do not use colors --y col,... yank columns to clipboard -H do not show headers ---ofs char output field separator -s specify field separator --r file read input from file -z use fuzzy search --f file read config from file -I interactive filter mode - -d debug --O org -C CSV -M md -X ext -S shell -Y yaml -D sort descending order --m show manual --help show detailed help -v show version --a sort by age -i sort numerically -t sort by time` +-c col,... show specified columns -L highlight matching lines +-k col,... sort by specified columns -j read JSON input +-F col=reg filter field with regexp -v invert match +-T col,... transpose specified columns -n numberize columns +-R /from/to/ apply replacement to columns in -T -N do not use colors +-y col,... yank columns to clipboard -H do not show headers +--ofs char output field separator -s specify field separator +-r file read input from file -z use fuzzy search +-f file read config from file -I interactive filter mode +-x col,... use custom headers -d debug +-o char use char as output separator -g auto generate headers + +-O org -C CSV -M md -X ext -S shell -Y yaml -J json -D sort descending order +-m show manual --help show detailed help -v show version +-a sort by age -i sort numerically -t sort by time` diff --git a/cmd/tablizer.go b/cmd/tablizer.go index 9d8f8c9..f732afa 100644 --- a/cmd/tablizer.go +++ b/cmd/tablizer.go @@ -22,8 +22,8 @@ SYNOPSIS -R, --regex-transposer Apply /search/replace/ regexp to fields given in -T -j, --json Read JSON input (must be array of hashes) -I, --interactive Interactively filter and select rows - --auto-headers Generate headers if there are none present in input - --custom-headers a,b,... Use custom headers, separated by comma + -g, --auto-headers Generate headers if there are none present in input + -x, --custom-headers a,b,... Use custom headers, separated by comma Output Flags (mutually exclusive): -X, --extended Enable extended output @@ -31,12 +31,13 @@ SYNOPSIS -O, --orgtbl Enable org-mode table output -S, --shell Enable shell evaluable output -Y, --yaml Enable yaml output + -J, --jsonout Enable JSON output -C, --csv Enable CSV output -A, --ascii Default output mode, ascii tabular -L, --hightlight-lines Use alternating background colors for tables + -o, --ofs Output field separator, used by -A and -C. -y, --yank-columns Yank specified columns (separated by ,) to clipboard, space separated - --ofs Output field separator, used by -A and -C. Sort Mode Flags (mutually exclusive): -a, --sort-age sort according to age (duration) string @@ -519,8 +520,8 @@ Operational Flags: -R, --regex-transposer Apply /search/replace/ regexp to fields given in -T -j, --json Read JSON input (must be array of hashes) -I, --interactive Interactively filter and select rows - --auto-headers Generate headers if there are none present in input - --custom-headers a,b,... Use custom headers, separated by comma + -g, --auto-headers Generate headers if there are none present in input + -x, --custom-headers a,b,... Use custom headers, separated by comma Output Flags (mutually exclusive): -X, --extended Enable extended output @@ -528,12 +529,13 @@ Output Flags (mutually exclusive): -O, --orgtbl Enable org-mode table output -S, --shell Enable shell evaluable output -Y, --yaml Enable yaml output + -J, --jsonout Enable JSON output -C, --csv Enable CSV output -A, --ascii Default output mode, ascii tabular -L, --hightlight-lines Use alternating background colors for tables + -o, --ofs Output field separator, used by -A and -C. -y, --yank-columns Yank specified columns (separated by ,) to clipboard, space separated - --ofs Output field separator, used by -A and -C. Sort Mode Flags (mutually exclusive): -a, --sort-age sort according to age (duration) string diff --git a/lib/printer.go b/lib/printer.go index 27a4f81..2805712 100644 --- a/lib/printer.go +++ b/lib/printer.go @@ -19,6 +19,7 @@ package lib import ( "encoding/csv" + "encoding/json" "fmt" "io" "log" @@ -61,6 +62,8 @@ func printData(writer io.Writer, conf cfg.Config, data *Tabdata) { printShellData(writer, data) case cfg.Yaml: printYamlData(writer, data) + case cfg.Json: + printJsonData(writer, data) case cfg.CSV: printCSVData(writer, conf, data) default: @@ -291,6 +294,35 @@ func printShellData(writer io.Writer, data *Tabdata) { output(writer, out) } +func printJsonData(writer io.Writer, data *Tabdata) { + objlist := make([]map[string]any, len(data.entries)) + + if len(data.entries) > 0 { + for i, entry := range data.entries { + obj := make(map[string]any, len(entry)) + + for idx, value := range entry { + num, err := strconv.Atoi(value) + if err == nil { + obj[data.headers[idx]] = num + } else { + obj[data.headers[idx]] = value + } + } + + objlist[i] = obj + } + } + + jsonstr, err := json.MarshalIndent(&objlist, "", " ") + + if err != nil { + log.Fatal(err) + } + + output(writer, string(jsonstr)) +} + func printYamlData(writer io.Writer, data *Tabdata) { type Data struct { Entries []map[string]interface{} `yaml:"entries"` diff --git a/lib/printer_test.go b/lib/printer_test.go index 7506a90..50db4aa 100644 --- a/lib/printer_test.go +++ b/lib/printer_test.go @@ -125,6 +125,31 @@ ceta,33d12h,9,06/Jan/2008 15:04:05 -0700`, NAME="beta" DURATION="1d10h5m1s" COUNT="33" WHEN="3/1/2014" NAME="alpha" DURATION="4h35m" COUNT="170" WHEN="2013-Feb-03" NAME="ceta" DURATION="33d12h" COUNT="9" WHEN="06/Jan/2008 15:04:05 -0700"`, + }, + { + name: "json", + mode: cfg.Json, + numberize: false, + expect: `[ + { + "COUNT": 33, + "DURATION": "1d10h5m1s", + "NAME": "beta", + "WHEN": "3/1/2014" + }, + { + "COUNT": 170, + "DURATION": "4h35m", + "NAME": "alpha", + "WHEN": "2013-Feb-03" + }, + { + "COUNT": 9, + "DURATION": "33d12h", + "NAME": "ceta", + "WHEN": "06/Jan/2008 15:04:05 -0700" + } +]`, }, { name: "yaml", diff --git a/tablizer.1 b/tablizer.1 index 549b2d4..13c54f7 100644 --- a/tablizer.1 +++ b/tablizer.1 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "TABLIZER 1" -.TH TABLIZER 1 "2025-10-10" "1" "User Commands" +.TH TABLIZER 1 "2025-10-13" "1" "User Commands" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -160,8 +160,8 @@ tablizer \- Manipulate tabular output of other programs \& \-R, \-\-regex\-transposer Apply /search/replace/ regexp to fields given in \-T \& \-j, \-\-json Read JSON input (must be array of hashes) \& \-I, \-\-interactive Interactively filter and select rows -\& \-\-auto\-headers Generate headers if there are none present in input -\& \-\-custom\-headers a,b,... Use custom headers, separated by comma +\& \-g, \-\-auto\-headers Generate headers if there are none present in input +\& \-x, \-\-custom\-headers a,b,... Use custom headers, separated by comma \& \& Output Flags (mutually exclusive): \& \-X, \-\-extended Enable extended output @@ -169,12 +169,13 @@ tablizer \- Manipulate tabular output of other programs \& \-O, \-\-orgtbl Enable org\-mode table output \& \-S, \-\-shell Enable shell evaluable output \& \-Y, \-\-yaml Enable yaml output +\& \-J, \-\-jsonout Enable JSON output \& \-C, \-\-csv Enable CSV output \& \-A, \-\-ascii Default output mode, ascii tabular \& \-L, \-\-hightlight\-lines Use alternating background colors for tables +\& \-o, \-\-ofs Output field separator, used by \-A and \-C. \& \-y, \-\-yank\-columns Yank specified columns (separated by ,) to clipboard, \& space separated -\& \-\-ofs Output field separator, used by \-A and \-C. \& \& Sort Mode Flags (mutually exclusive): \& \-a, \-\-sort\-age sort according to age (duration) string diff --git a/tablizer.pod b/tablizer.pod index e3791f9..d43a227 100644 --- a/tablizer.pod +++ b/tablizer.pod @@ -21,8 +21,8 @@ tablizer - Manipulate tabular output of other programs -R, --regex-transposer Apply /search/replace/ regexp to fields given in -T -j, --json Read JSON input (must be array of hashes) -I, --interactive Interactively filter and select rows - --auto-headers Generate headers if there are none present in input - --custom-headers a,b,... Use custom headers, separated by comma + -g, --auto-headers Generate headers if there are none present in input + -x, --custom-headers a,b,... Use custom headers, separated by comma Output Flags (mutually exclusive): -X, --extended Enable extended output @@ -30,12 +30,13 @@ tablizer - Manipulate tabular output of other programs -O, --orgtbl Enable org-mode table output -S, --shell Enable shell evaluable output -Y, --yaml Enable yaml output + -J, --jsonout Enable JSON output -C, --csv Enable CSV output -A, --ascii Default output mode, ascii tabular -L, --hightlight-lines Use alternating background colors for tables + -o, --ofs Output field separator, used by -A and -C. -y, --yank-columns Yank specified columns (separated by ,) to clipboard, space separated - --ofs Output field separator, used by -A and -C. Sort Mode Flags (mutually exclusive): -a, --sort-age sort according to age (duration) string