Format the output of {{ . | printf "%#v" }}
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Heiko Schlittermann (HS12-RIPE) d94af5d48a style: consolidate AST type block, split main into run() int
Two small idiomatic tweaks:

- Fold every AST type into one type ( ... ) block instead of the
  half-grouped, half-loose spelling.
- Split main into run() int + os.Exit(run()) so defer works. The
  new bufio.NewWriter(os.Stdout) + defer out.Flush() needed this;
  os.Exit skips deferred calls and would have silently swallowed
  the last record.

Also switches record output from fmt.Println(buf.String()) to
buf.WriteTo(out) on the buffered writer \u2014 one syscall for the
whole run instead of one per record.
2026-08-12 14:35:28 +02:00
docs readme: add hero SVG 2026-08-12 13:28:27 +02:00
.gitignore man: colocate gq.1.gz with gq.1.md under docs/ 2026-08-12 13:10:33 +02:00
AGENTS.md man: colocate gq.1.gz with gq.1.md under docs/ 2026-08-12 13:10:33 +02:00
go.mod man: embed a hand-written gq(1) page 2026-08-12 13:01:31 +02:00
go.sum man: embed a hand-written gq(1) page 2026-08-12 13:01:31 +02:00
LICENSE gq: format Go %#v like jq formats JSON 2026-08-12 12:23:40 +02:00
main.go style: consolidate AST type block, split main into run() int 2026-08-12 14:35:28 +02:00
main_test.go fix: unary sign on numeric literals; move module to go.schlittermann.de 2026-08-12 12:30:07 +02:00
man.go man: colocate gq.1.gz with gq.1.md under docs/ 2026-08-12 13:10:33 +02:00
README.md readme: embed the hero SVG at the top 2026-08-12 13:29:12 +02:00

gq — format Go %#v output like jq formats JSON

gq: pipe Go %#v output through gq to get a jq-style tree or JSON

gq reads Go's Go-syntax value printing (as produced by fmt.Printf("%#v", ...) or Go templates using printf "%#v") and prints either a human-readable indented tree or JSON.

Licensed under Apache-2.0. See LICENSE.

Install

go install go.schlittermann.de/heiko/gq@latest

Usage

gq [--json] [--types] [--indent STR] [FILE...]

One input line = one top-level Go value. Reads stdin when no FILE is given.

Examples

Human-readable, jq-style:

podman image ls --format '{{. | printf "%#v" }}' | gq

JSON, pipe into jq:

podman image ls --format '{{. | printf "%#v" }}' | gq --json | jq '.Names'

Show Go types alongside values:

podman image ls --format '{{. | printf "%#v" }}' | gq --types

What it understands

Go %#v Meaning JSON output
main.T{F:v, ...} struct object (field order)
[]T{a, b, c} slice / array array
map[K]V{k:v, ...} map object (source order)
&main.T{...} pointer inner value
<nil> nil interface null
(*T)(nil) nil pointer null
[]T(nil), map[K]V(nil) nil slice/map null
"quoted" / `raw` string JSON string
42, 0x2a, 0b101 int decimal integer
3.14, 1e-3 float JSON number
'A' rune integer code point
true / false bool true / false

Anything that survives as raw Go source (custom GoString() methods like time.Date(...), function values like (func())(0x1234), channels, etc.) is preserved verbatim as a scalar in pretty mode, and emitted as a JSON string in --json mode.

Why %#v and not %+v

%#v quotes strings, uses , as separator, and is (with one small preprocessing step for <nil>) valid Go source. That means we hand parsing off to go/parser, which is exact — no lookahead heuristics for scalar boundaries. Strings containing spaces, colons, or braces are handled correctly.

Limitations

  • Extremely large truncated %#v output (some tools cap it) fails to parse; gq prints the parse error to stderr and moves on to the next line.
  • Byte slices come through as integer arrays in JSON. If you want base64, pipe through jq and do it there.
  • Fields with GoStringer implementations that don't return valid Go source stay as raw scalars.