mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
feat(ipcohort-contains): add --format pretty|tsv|csv|json
Auto-detects format when --format is empty: pretty when stdout is a TTY,
tsv when piped/redirected. JSON emits an array of {ip, found} objects;
CSV adds an 'ip,status' header row.
This commit is contained in:
parent
d37bd1d08c
commit
cf5632e5d6
@ -12,6 +12,8 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
@ -20,6 +22,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/therootcompany/golib/net/ipcohort"
|
||||
)
|
||||
|
||||
@ -50,13 +54,87 @@ func commafy(n int) string {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
IP string
|
||||
IP string
|
||||
Format string
|
||||
}
|
||||
|
||||
const (
|
||||
formatPretty = "pretty"
|
||||
formatTSV = "tsv"
|
||||
formatCSV = "csv"
|
||||
formatJSON = "json"
|
||||
)
|
||||
|
||||
func parseFormat(s string) (string, error) {
|
||||
switch s {
|
||||
case "":
|
||||
if term.IsTerminal(int(os.Stdout.Fd())) {
|
||||
return formatPretty, nil
|
||||
}
|
||||
return formatTSV, nil
|
||||
case formatPretty, formatTSV, formatCSV, formatJSON:
|
||||
return s, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown format %q (want pretty, tsv, csv, json)", s)
|
||||
}
|
||||
}
|
||||
|
||||
type result struct {
|
||||
IP string `json:"ip"`
|
||||
Found bool `json:"found"`
|
||||
}
|
||||
|
||||
func writeResults(format string, results []result) error {
|
||||
switch format {
|
||||
case formatPretty:
|
||||
w := 0
|
||||
for _, r := range results {
|
||||
if len(r.IP) > w {
|
||||
w = len(r.IP)
|
||||
}
|
||||
}
|
||||
for _, r := range results {
|
||||
status := "NOT FOUND"
|
||||
if r.Found {
|
||||
status = "FOUND"
|
||||
}
|
||||
fmt.Printf("%-*s %s\n", w, r.IP, status)
|
||||
}
|
||||
case formatTSV:
|
||||
for _, r := range results {
|
||||
status := "NOT FOUND"
|
||||
if r.Found {
|
||||
status = "FOUND"
|
||||
}
|
||||
fmt.Printf("%s\t%s\n", r.IP, status)
|
||||
}
|
||||
case formatCSV:
|
||||
cw := csv.NewWriter(os.Stdout)
|
||||
_ = cw.Write([]string{"ip", "status"})
|
||||
for _, r := range results {
|
||||
status := "NOT FOUND"
|
||||
if r.Found {
|
||||
status = "FOUND"
|
||||
}
|
||||
if err := cw.Write([]string{r.IP, status}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cw.Flush()
|
||||
return cw.Error()
|
||||
case formatJSON:
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(results)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := Config{}
|
||||
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
fs.StringVar(&cfg.IP, "ip", "", "IP address to check (alternative to -- separator)")
|
||||
fs.StringVar(&cfg.Format, "format", "", "output format: pretty, tsv, csv, json (default: auto)")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [flags] <file>... -- <ip>...\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, " %s --ip <ip> <file>...\n", os.Args[0])
|
||||
@ -145,15 +223,26 @@ func main() {
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr)
|
||||
|
||||
format, err := parseFormat(cfg.Format)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
results := make([]result, 0, len(ips))
|
||||
allFound := true
|
||||
for _, ip := range ips {
|
||||
if cohort.Contains(ip) {
|
||||
fmt.Printf("%s\tFOUND\n", ip)
|
||||
} else {
|
||||
fmt.Printf("%s\tNOT FOUND\n", ip)
|
||||
found := cohort.Contains(ip)
|
||||
results = append(results, result{IP: ip, Found: found})
|
||||
if !found {
|
||||
allFound = false
|
||||
}
|
||||
}
|
||||
if err := writeResults(format, results); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if !allFound {
|
||||
os.Exit(1)
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
module github.com/therootcompany/golib/net/ipcohort
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require golang.org/x/term v0.42.0
|
||||
|
||||
require golang.org/x/sys v0.43.0 // indirect
|
||||
|
||||
4
net/ipcohort/go.sum
Normal file
4
net/ipcohort/go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
@ -103,7 +103,6 @@ func TestContains_FailClosed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestContains_Empty(t *testing.T) {
|
||||
c, err := ipcohort.Parse(nil)
|
||||
if err != nil {
|
||||
@ -163,4 +162,3 @@ func TestLoadFiles_Merge(t *testing.T) {
|
||||
t.Error("missing merged CIDR member")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user