jsontypes/cmd: migrate to flag.FlagSet, add version info

- Use flag.FlagSet with config struct instead of global flag
- Add version vars (name, version, commit, date, licenseYear, licenseOwner, licenseType)
- Add printVersion() with copyright and license
- Handle -V/--version/version/help/-help/--help before fs.Parse
- Handle flag.ErrHelp and fs.NArg() > 1 checks
This commit is contained in:
AJ ONeal 2026-06-11 16:23:34 -06:00
parent 5676790d03
commit aab60c7ac2
No known key found for this signature in database
4 changed files with 255 additions and 67 deletions

View File

@ -2,22 +2,74 @@ package main
import (
"bufio"
"errors"
"flag"
"fmt"
"io"
"os"
"github.com/therootcompany/golib/tools/jsontypes"
)
const description = "Coalesce raw JSON paths into a flat list."
// Replaced by goreleaser / ldflags at build time.
var (
name = "jsoncoalesce"
version = "0.0.0-dev"
commit = "0000000"
date = "0001-01-01"
licenseYear = "2024"
licenseOwner = "The Root Company"
licenseType = "CC0-1.0"
)
func printVersion(w io.Writer) {
_, _ = fmt.Fprintf(w, "%s v%s %s (%s)\n", name, version, commit[:7], date)
_, _ = fmt.Fprintf(w, "Copyright (C) %s %s\n", licenseYear, licenseOwner)
_, _ = fmt.Fprintf(w, "Licensed under %s\n", licenseType)
_, _ = fmt.Fprintf(w, "%s\n", description)
}
func main() {
if len(os.Args) > 1 {
switch os.Args[1] {
case "-V", "-version", "--version", "version":
printVersion(os.Stdout)
os.Exit(0)
case "help", "-help", "--help":
printVersion(os.Stdout)
fmt.Fprintln(os.Stdout)
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.SetOutput(os.Stdout)
fs.Usage()
os.Exit(0)
}
}
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
if err := fs.Parse(os.Args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(0)
}
os.Exit(1)
}
if fs.NArg() > 1 {
fmt.Fprintf(os.Stderr, "error: too many arguments\n")
os.Exit(1)
}
var input *os.File
if len(os.Args) > 1 && os.Args[1] != "-" {
f, err := os.Open(os.Args[1])
if fs.NArg() > 0 && fs.Arg(0) != "-" {
var err error
input, err = os.Open(fs.Arg(0))
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
defer f.Close()
input = f
defer input.Close()
} else {
input = os.Stdin
}

View File

@ -20,20 +20,24 @@ import (
"github.com/therootcompany/golib/tools/jsontypes"
)
const (
name = "jsonpaths"
description = "Infer types from JSON. Generate code."
)
const description = "Infer types from JSON. Generate code."
// Replaced by goreleaser / ldflags at build time.
var (
version = "0.0.0-dev"
commit = "0000000"
date = "0001-01-01"
name = "jsonpaths"
version = "0.0.0-dev"
commit = "0000000"
date = "0001-01-01"
licenseYear = "2024"
licenseOwner = "The Root Company"
licenseType = "CC0-1.0"
)
func printVersion(w io.Writer) {
fmt.Fprintf(w, "%s v%s %s (%s)\n", name, version, commit[:7], date)
fmt.Fprintf(w, "%s\n", description)
_, _ = fmt.Fprintf(w, "%s v%s %s (%s)\n", name, version, commit[:7], date)
_, _ = fmt.Fprintf(w, "Copyright (C) %s %s\n", licenseYear, licenseOwner)
_, _ = fmt.Fprintf(w, "Licensed under %s\n", licenseType)
_, _ = fmt.Fprintf(w, "%s\n", description)
}
// headerList implements flag.Value for repeatable -H flags.
@ -48,6 +52,19 @@ func (h *headerList) Set(val string) error {
return nil
}
type mainConfig struct {
headers headerList
anonymous bool
askTypes bool
bearer string
cookie string
cookieJar string
format string
timeout time.Duration
noCache bool
user string
}
func main() {
// Exit cleanly on Ctrl+C
sig := make(chan os.Signal, 1)
@ -58,43 +75,48 @@ func main() {
os.Exit(130)
}()
var headers headerList
flag.Var(&headers, "H", "add HTTP header (repeatable, e.g. -H 'X-API-Key: abc')")
anonymous := flag.Bool("anonymous", false, "skip all prompts; use heuristics and auto-inferred names")
askTypes := flag.Bool("ask-types", false, "prompt for each type name instead of auto-inferring")
bearer := flag.String("bearer", "", "set Authorization: Bearer token")
cookie := flag.String("cookie", "", "send cookie (name=value or Set-Cookie format)")
cookieJar := flag.String("cookie-jar", "", "read cookies from Netscape cookie jar file")
format := flag.String("format", "json-paths", "output format: json-paths, go, json-schema, json-typedef, typescript, jsdoc, zod, python, sql")
timeout := flag.Duration("timeout", 20*time.Second, "HTTP request timeout for URL inputs")
noCache := flag.Bool("no-cache", false, "skip local cache for URL inputs")
user := flag.String("user", "", "HTTP basic auth (user:password, like curl)")
cfg := mainConfig{}
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "USAGE\n %s [flags] [file | url]\n\n", name)
fmt.Fprintf(os.Stderr, "FLAGS\n")
flag.PrintDefaults()
}
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.Var(&cfg.headers, "H", "add HTTP header (repeatable, e.g. -H 'X-API-Key: abc')")
fs.BoolVar(&cfg.anonymous, "anonymous", false, "skip all prompts; use heuristics and auto-inferred names")
fs.BoolVar(&cfg.askTypes, "ask-types", false, "prompt for each type name instead of auto-inferring")
fs.StringVar(&cfg.bearer, "bearer", "", "set Authorization: Bearer token")
fs.StringVar(&cfg.cookie, "cookie", "", "send cookie (name=value or Set-Cookie format)")
fs.StringVar(&cfg.cookieJar, "cookie-jar", "", "read cookies from Netscape cookie jar file")
fs.StringVar(&cfg.format, "format", "json-paths", "output format: json-paths, go, json-schema, json-typedef, typescript, jsdoc, zod, python, sql")
fs.DurationVar(&cfg.timeout, "timeout", 20*time.Second, "HTTP request timeout for URL inputs")
fs.BoolVar(&cfg.noCache, "no-cache", false, "skip local cache for URL inputs")
fs.StringVar(&cfg.user, "user", "", "HTTP basic auth (user:password, like curl)")
// Handle version/help before flag parse
if len(os.Args) > 1 {
arg := os.Args[1]
if arg == "-V" || arg == "--version" || arg == "version" {
switch os.Args[1] {
case "-V", "-version", "--version", "version":
printVersion(os.Stdout)
os.Exit(0)
}
if arg == "help" || arg == "-help" || arg == "--help" {
case "help", "-help", "--help":
printVersion(os.Stdout)
fmt.Fprintln(os.Stdout)
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage()
fs.SetOutput(os.Stdout)
fs.Usage()
os.Exit(0)
}
}
flag.Parse()
if err := fs.Parse(os.Args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(0)
}
os.Exit(1)
}
outFormat, err := jsontypes.ParseFormat(*format)
if fs.NArg() > 1 {
fmt.Fprintf(os.Stderr, "error: too many arguments\n")
os.Exit(1)
}
outFormat, err := jsontypes.ParseFormat(cfg.format)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
@ -105,24 +127,24 @@ func main() {
inputIsStdin := true
// Build extra HTTP headers from flags
var extraHeaders http.Header
if *bearer != "" || *user != "" || *cookie != "" || *cookieJar != "" || len(headers) > 0 {
if cfg.bearer != "" || cfg.user != "" || cfg.cookie != "" || cfg.cookieJar != "" || len(cfg.headers) > 0 {
extraHeaders = make(http.Header)
}
for _, h := range headers {
for _, h := range cfg.headers {
name, value, _ := strings.Cut(h, ":")
extraHeaders.Add(strings.TrimSpace(name), strings.TrimSpace(value))
}
if *bearer != "" {
extraHeaders.Set("Authorization", "Bearer "+*bearer)
if cfg.bearer != "" {
extraHeaders.Set("Authorization", "Bearer "+cfg.bearer)
}
if *user != "" {
extraHeaders.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(*user)))
if cfg.user != "" {
extraHeaders.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(cfg.user)))
}
if *cookie != "" {
extraHeaders.Add("Cookie", parseCookieFlag(*cookie))
if cfg.cookie != "" {
extraHeaders.Add("Cookie", parseCookieFlag(cfg.cookie))
}
if *cookieJar != "" {
cookies, err := readCookieJar(*cookieJar)
if cfg.cookieJar != "" {
cookies, err := readCookieJar(cfg.cookieJar)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading cookie jar: %v\n", err)
os.Exit(1)
@ -132,10 +154,10 @@ func main() {
}
}
if args := flag.Args(); len(args) > 0 && args[0] != "-" {
if args := fs.Args(); len(args) > 0 && args[0] != "-" {
arg := args[0]
if strings.HasPrefix(arg, "https://") || strings.HasPrefix(arg, "http://") {
r, err := fetchOrCache(arg, *timeout, *noCache, extraHeaders)
r, err := fetchOrCache(arg, cfg.timeout, cfg.noCache, extraHeaders)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
@ -168,7 +190,7 @@ func main() {
var formatted []string
var pr *prompter
if *anonymous {
if cfg.anonymous {
// Pipeline mode: RawPaths → Coalesce → Generate (deterministic, no prompts).
raw := jsontypes.RawPaths(data)
formatted = jsontypes.Coalesce(raw)
@ -186,7 +208,7 @@ func main() {
resolver := newCLIResolver(pr)
a := jsontypes.New(jsontypes.AnalyzerConfig{
Resolver: resolver,
AskTypes: *askTypes,
AskTypes: cfg.askTypes,
})
rawPaths := a.Analyze(".", data)
formatted = jsontypes.FormatPaths(rawPaths)

View File

@ -2,26 +2,83 @@ package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"github.com/therootcompany/golib/tools/jsontypes"
)
const description = "Show raw JSON paths from a JSON document."
// Replaced by goreleaser / ldflags at build time.
var (
name = "jsonrawpaths"
version = "0.0.0-dev"
commit = "0000000"
date = "0001-01-01"
licenseYear = "2024"
licenseOwner = "The Root Company"
licenseType = "CC0-1.0"
)
func printVersion(w io.Writer) {
_, _ = fmt.Fprintf(w, "%s v%s %s (%s)\n", name, version, commit[:7], date)
_, _ = fmt.Fprintf(w, "Copyright (C) %s %s\n", licenseYear, licenseOwner)
_, _ = fmt.Fprintf(w, "Licensed under %s\n", licenseType)
_, _ = fmt.Fprintf(w, "%s\n", description)
}
type mainConfig struct {
samples int
format string
}
func main() {
samples := flag.Int("samples", 0, "show sample values truncated to this length (0 = type names)")
flag.Parse()
if len(os.Args) > 1 {
switch os.Args[1] {
case "-V", "-version", "--version", "version":
printVersion(os.Stdout)
os.Exit(0)
case "help", "-help", "--help":
printVersion(os.Stdout)
fmt.Fprintln(os.Stdout)
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.SetOutput(os.Stdout)
fs.Usage()
os.Exit(0)
}
}
cfg := mainConfig{}
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.IntVar(&cfg.samples, "samples", 0, "show sample values truncated to this length (0 = type names)")
fs.StringVar(&cfg.format, "format", "", "output format: pretty, tsv, csv, json (default: auto)")
if err := fs.Parse(os.Args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(0)
}
os.Exit(1)
}
if fs.NArg() > 1 {
fmt.Fprintf(os.Stderr, "error: too many arguments\n")
os.Exit(1)
}
var input *os.File
if flag.NArg() > 0 && flag.Arg(0) != "-" {
f, err := os.Open(flag.Arg(0))
if fs.NArg() > 0 && fs.Arg(0) != "-" {
var err error
input, err = os.Open(fs.Arg(0))
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
defer f.Close()
input = f
defer input.Close()
} else {
input = os.Stdin
}
@ -34,8 +91,8 @@ func main() {
os.Exit(1)
}
cfg := jsontypes.RawPathsConfig{SampleLen: *samples}
for _, path := range jsontypes.RawPathsWithConfig(data, cfg) {
cfgs := jsontypes.RawPathsConfig{SampleLen: cfg.samples}
for _, path := range jsontypes.RawPathsWithConfig(data, cfgs) {
fmt.Println(path)
}
}

View File

@ -2,26 +2,83 @@ package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"github.com/therootcompany/golib/tools/jsontypes"
)
const description = "Generate sample JSON values from a JSON document."
// Replaced by goreleaser / ldflags at build time.
var (
name = "jsonsample"
version = "0.0.0-dev"
commit = "0000000"
date = "0001-01-01"
licenseYear = "2024"
licenseOwner = "The Root Company"
licenseType = "CC0-1.0"
)
func printVersion(w io.Writer) {
_, _ = fmt.Fprintf(w, "%s v%s %s (%s)\n", name, version, commit[:7], date)
_, _ = fmt.Fprintf(w, "Copyright (C) %s %s\n", licenseYear, licenseOwner)
_, _ = fmt.Fprintf(w, "Licensed under %s\n", licenseType)
_, _ = fmt.Fprintf(w, "%s\n", description)
}
type mainConfig struct {
maxStr int
format string
}
func main() {
maxStr := flag.Int("maxstr", 40, "max string length before truncation (0 = no limit)")
flag.Parse()
if len(os.Args) > 1 {
switch os.Args[1] {
case "-V", "-version", "--version", "version":
printVersion(os.Stdout)
os.Exit(0)
case "help", "-help", "--help":
printVersion(os.Stdout)
fmt.Fprintln(os.Stdout)
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.SetOutput(os.Stdout)
fs.Usage()
os.Exit(0)
}
}
cfg := mainConfig{}
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.IntVar(&cfg.maxStr, "maxstr", 40, "max string length before truncation (0 = no limit)")
fs.StringVar(&cfg.format, "format", "", "output format: pretty, tsv, csv, json (default: auto)")
if err := fs.Parse(os.Args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(0)
}
os.Exit(1)
}
if fs.NArg() > 1 {
fmt.Fprintf(os.Stderr, "error: too many arguments\n")
os.Exit(1)
}
var input *os.File
if flag.NArg() > 0 && flag.Arg(0) != "-" {
f, err := os.Open(flag.Arg(0))
if fs.NArg() > 0 && fs.Arg(0) != "-" {
var err error
input, err = os.Open(fs.Arg(0))
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
defer f.Close()
input = f
defer input.Close()
} else {
input = os.Stdin
}
@ -34,8 +91,8 @@ func main() {
os.Exit(1)
}
cfg := jsontypes.SampleConfig{MaxStringLen: *maxStr}
for _, line := range jsontypes.SampleWithConfig(data, cfg) {
cfgs := jsontypes.SampleConfig{MaxStringLen: cfg.maxStr}
for _, line := range jsontypes.SampleWithConfig(data, cfgs) {
fmt.Println(line)
}
}