From 7edd9831756fac2585cc616b47cc12dc5e97aa41 Mon Sep 17 00:00:00 2001
From: Sam <sam@samtherapy.net>
Date: Mon, 5 Sep 2022 00:05:06 +0000
Subject: [PATCH] feat: add preliminary message tinkering (#79)

Reviewed-on: https://git.froth.zone/sam/awl/pulls/79
Reviewed-by: grumbulon <grumbulon@grumbulon.xyz>
---
 .golangci.yaml      |  3 +-
 .goreleaser.yaml    |  6 ++-
 cli/cli.go          |  6 ++-
 cli/cli_test.go     | 64 ++++++++++++++++++++------------
 cli/dig.go          |  4 +-
 cli/dig_test.go     |  5 +++
 cli/misc.go         |  2 +-
 cli/misc_test.go    |  2 +
 doc/wiki            |  2 +-
 go.mod              |  2 +-
 go.sum              | 22 +++--------
 main.go             |  5 ++-
 main_test.go        |  5 +++
 query/print_test.go | 15 +++++---
 query/query.go      | 13 +++++--
 query/query_test.go |  4 +-
 query/struct.go     | 90 +++++++++++++++++++++++++++++++++++++++++----
 util/options.go     |  1 +
 18 files changed, 183 insertions(+), 68 deletions(-)

diff --git a/.golangci.yaml b/.golangci.yaml
index 9ce9b3f..9eca14e 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -32,10 +32,11 @@ linters:
     - predeclared
     - revive
     - staticcheck
-    #- testpackage
     - whitespace
     - wrapcheck
     - wsl
+  disable:
+    - structcheck
 
 linters-settings:
   govet:
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 70a9ce1..80c67a5 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -19,7 +19,11 @@ builds:
 universal_binaries:
   - replace: true
 archives:
-  - replacements:
+  -
+    files:
+      - LICENCE
+      - completions/**
+    replacements:
       darwin: macOS
       linux: Linux
       windows: Windows
diff --git a/cli/cli.go b/cli/cli.go
index 2e176f2..8a593cb 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -36,6 +36,8 @@ func ParseCLI(version string) (util.Options, error) {
 	}
 
 	// CLI flags
+	//
+	// Remember, when adding a flag edit the manpage and the completions :)
 	var (
 		port  = flag.Int("port", 0, "`port` to make DNS query (default: 53 for UDP/TCP, 853 for TLS/QUIC)", flag.OptShorthand('p'), flag.OptDisablePrintDefault(true))
 		query = flag.String("query", "", "domain name to `query` (default: .)", flag.OptShorthand('q'))
@@ -51,14 +53,14 @@ func ParseCLI(version string) (util.Options, error) {
 
 		edns         = flag.Bool("no-edns", false, "disable EDNS entirely")
 		ednsVer      = flag.Uint8("edns-ver", 0, "set EDNS version")
-		expire       = flag.Bool("expire", false, "set EDNS expire")
 		dnssec       = flag.Bool("dnssec", false, "enable DNSSEC", flag.OptShorthand('D'))
+		expire       = flag.Bool("expire", false, "set EDNS expire")
 		nsid         = flag.Bool("nsid", false, "set EDNS NSID", flag.OptShorthand('n'))
 		cookie       = flag.Bool("no-cookie", false, "disable sending EDNS cookie (default: cookie sent)")
 		tcpKeepAlive = flag.Bool("keep-alive", false, "send EDNS TCP keep-alive")
 		udpBufSize   = flag.Uint16("buffer-size", 1232, "set EDNS UDP buffer size", flag.OptShorthand('b'))
 		mbzflag      = flag.String("zflag", "0", "set EDNS z-flag `value`")
-		subnet       = flag.String("subnet", "", "set EDNS subnet")
+		subnet       = flag.String("subnet", "", "set EDNS client subnet")
 		padding      = flag.Bool("pad", false, "set EDNS padding")
 
 		truncate = flag.Bool("no-truncate", false, "ignore truncation if a UDP request truncates (default: retry with TCP)")
diff --git a/cli/cli_test.go b/cli/cli_test.go
index 0250015..0930fbe 100644
--- a/cli/cli_test.go
+++ b/cli/cli_test.go
@@ -12,7 +12,7 @@ import (
 )
 
 func TestEmpty(t *testing.T) {
-	old := os.Args
+	args := os.Args
 	os.Args = []string{"awl", "-4"}
 
 	opts, err := cli.ParseCLI("TEST")
@@ -21,11 +21,11 @@ func TestEmpty(t *testing.T) {
 	assert.Equal(t, opts.Port, 53)
 	assert.Assert(t, opts.IPv4)
 
-	os.Args = old
+	os.Args = args
 }
 
 func TestTLSPort(t *testing.T) {
-	old := os.Args
+	args := os.Args
 	os.Args = []string{"awl", "-T"}
 
 	opts, err := cli.ParseCLI("TEST")
@@ -33,11 +33,11 @@ func TestTLSPort(t *testing.T) {
 	assert.NilError(t, err)
 	assert.Equal(t, opts.Port, 853)
 
-	os.Args = old
+	os.Args = args
 }
 
 func TestSubnet(t *testing.T) {
-	old := os.Args
+	args := os.Args
 	os.Args = []string{"awl", "--subnet", "127.0.0.1/32"}
 
 	opts, err := cli.ParseCLI("TEST")
@@ -45,7 +45,7 @@ func TestSubnet(t *testing.T) {
 	assert.NilError(t, err)
 	assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
 
-	os.Args = old
+	os.Args = args
 
 	os.Args = []string{"awl", "--subnet", "0"}
 
@@ -53,7 +53,7 @@ func TestSubnet(t *testing.T) {
 	assert.NilError(t, err)
 	assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
 
-	os.Args = old
+	os.Args = args
 
 	os.Args = []string{"awl", "--subnet", "::/0"}
 
@@ -61,58 +61,71 @@ func TestSubnet(t *testing.T) {
 	assert.NilError(t, err)
 	assert.Equal(t, opts.EDNS.Subnet.Family, uint16(2))
 
-	os.Args = old
+	os.Args = args
 
 	os.Args = []string{"awl", "--subnet", "/"}
 
 	opts, err = cli.ParseCLI("TEST")
 	assert.ErrorContains(t, err, "EDNS subnet")
 
-	os.Args = old
+	os.Args = args
 }
 
 func TestMBZ(t *testing.T) { //nolint: paralleltest // Race conditions
-	old := os.Args
+	args := os.Args
 	os.Args = []string{"awl", "--zflag", "G"}
 
 	_, err := cli.ParseCLI("TEST")
 
 	assert.ErrorContains(t, err, "EDNS MBZ")
 
-	os.Args = old
+	os.Args = args
 }
 
 func TestInvalidFlag(t *testing.T) { //nolint: paralleltest // Race conditions
-	old := os.Args
+	args := os.Args
+	stdout := os.Stdout
+	stderr := os.Stderr
+
+	os.Stdout = os.NewFile(0, os.DevNull)
+	os.Stderr = os.NewFile(0, os.DevNull)
+
 	os.Args = []string{"awl", "--treebug"}
 
 	_, err := cli.ParseCLI("TEST")
 
 	assert.ErrorContains(t, err, "unknown flag")
 
-	os.Args = old
+	os.Args = args
+	os.Stdout = stdout
+	os.Stderr = stderr
 }
 
 func TestInvalidDig(t *testing.T) { //nolint: paralleltest // Race conditions
-	old := os.Args
+	args := os.Args
 	os.Args = []string{"awl", "+a"}
 
 	_, err := cli.ParseCLI("TEST")
 
 	assert.ErrorContains(t, err, "digflags: invalid argument")
 
-	os.Args = old
+	os.Args = args
 }
 
 func TestVersion(t *testing.T) { //nolint: paralleltest // Race conditions
-	old := os.Args
+	args := os.Args
+	stdout := os.Stdout
+	stderr := os.Stderr
+
 	os.Args = []string{"awl", "--version"}
 
-	_, err := cli.ParseCLI("TEST")
+	_, err := cli.ParseCLI("test")
 
 	assert.ErrorType(t, err, cli.ErrNotError)
 
-	os.Args = old
+	os.Args = args
+	os.Stdout = stdout
+	os.Stderr = stderr
 }
 
 func TestTimeout(t *testing.T) { //nolint: paralleltest // Race conditions
@@ -121,7 +134,7 @@ func TestTimeout(t *testing.T) { //nolint: paralleltest // Race conditions
 		{"awl", "--timeout", "0"},
 	}
 	for _, test := range args {
-		old := os.Args
+		args := os.Args
 		os.Args = test
 
 		opt, err := cli.ParseCLI("TEST")
@@ -129,7 +142,7 @@ func TestTimeout(t *testing.T) { //nolint: paralleltest // Race conditions
 		assert.NilError(t, err)
 		assert.Equal(t, opt.Request.Timeout, time.Second/2)
 
-		os.Args = old
+		os.Args = args
 	}
 }
 
@@ -140,7 +153,7 @@ func TestRetries(t *testing.T) { //nolint: paralleltest // Race conditions
 		{"awl", "--retries", "-2"},
 	}
 	for _, test := range args {
-		old := os.Args
+		args := os.Args
 		os.Args = test
 
 		opt, err := cli.ParseCLI("TEST")
@@ -148,21 +161,24 @@ func TestRetries(t *testing.T) { //nolint: paralleltest // Race conditions
 		assert.NilError(t, err)
 		assert.Equal(t, opt.Request.Retries, 0)
 
-		os.Args = old
+		os.Args = args
 	}
 }
 
 func FuzzFlags(f *testing.F) {
 	testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"}
+
 	for _, tc := range testcases {
 		f.Add(tc)
 	}
 
 	f.Fuzz(func(t *testing.T, orig string) {
-		old := os.Args
+		// Get rid of outputs
+
+		args := os.Args
 		os.Args = []string{"awl", orig}
 		//nolint:errcheck,gosec // Only make sure the program does not crash
 		cli.ParseCLI("TEST")
-		os.Args = old
+		os.Args = args
 	})
 }
diff --git a/cli/dig.go b/cli/dig.go
index 59f49c0..6194e81 100644
--- a/cli/dig.go
+++ b/cli/dig.go
@@ -48,8 +48,10 @@ func ParseDig(arg string, opts *util.Options) error {
 		opts.ShowQuery = isNo
 	case "ttlunits":
 		opts.HumanTTL = isNo
-	case "ttlid":
+	case "ttl", "ttlid":
 		opts.ShowTTL = isNo
+	case "class":
+		opts.ShowClass = isNo
 
 	// EDNS queries
 	case "dnssec":
diff --git a/cli/dig_test.go b/cli/dig_test.go
index c21a19e..5293bb2 100644
--- a/cli/dig_test.go
+++ b/cli/dig_test.go
@@ -58,6 +58,7 @@ func FuzzDig(f *testing.F) {
 		"stats", "nostats",
 		"all", "noall",
 		"idnout", "noidnout",
+		"class", "noclass",
 		"invalid",
 	}
 
@@ -66,6 +67,10 @@ func FuzzDig(f *testing.F) {
 	}
 
 	f.Fuzz(func(t *testing.T, orig string) {
+		// Get rid of outputs
+		// os.Stdout = os.NewFile(0, os.DevNull)
+		// os.Stderr = os.NewFile(0, os.DevNull)
+
 		opts := new(util.Options)
 		opts.Logger = util.InitLogger(0)
 		if err := cli.ParseDig(orig, opts); err != nil {
diff --git a/cli/misc.go b/cli/misc.go
index 219c3f5..85228fd 100644
--- a/cli/misc.go
+++ b/cli/misc.go
@@ -13,7 +13,7 @@ import (
 	"golang.org/x/net/idna"
 )
 
-// ParseMiscArgs parses the wildcard arguments, drill style.
+// ParseMiscArgs parses the wildcard arguments, dig style.
 // Only one command is supported at a time, so any extra information overrides previous.
 func ParseMiscArgs(args []string, opts *util.Options) error {
 	for _, arg := range args {
diff --git a/cli/misc_test.go b/cli/misc_test.go
index 16d8b50..286e0e3 100644
--- a/cli/misc_test.go
+++ b/cli/misc_test.go
@@ -166,6 +166,8 @@ func FuzzParseArgs(f *testing.F) {
 	}
 
 	f.Fuzz(func(t *testing.T, arg string) {
+		// Get rid of outputs
+
 		args := []string{arg}
 		opts := new(util.Options)
 		opts.Logger = util.InitLogger(0)
diff --git a/doc/wiki b/doc/wiki
index 4974d31..96f2699 160000
--- a/doc/wiki
+++ b/doc/wiki
@@ -1 +1 @@
-Subproject commit 4974d316fc3be3c28b54af5188860f8cfb2bdd6b
+Subproject commit 96f2699a2f22a5bdcf37a48d0a20fd85c96bcc81
diff --git a/go.mod b/go.mod
index b59e56e..c2d9b24 100644
--- a/go.mod
+++ b/go.mod
@@ -31,7 +31,7 @@ require (
 	github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/onsi/ginkgo v1.16.5 // indirect
-	golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
+	golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
 	golang.org/x/sys v0.0.0-20220829200755-d48e67d00261
 	golang.org/x/text v0.3.7 // indirect
diff --git a/go.sum b/go.sum
index b602654..502ecb9 100644
--- a/go.sum
+++ b/go.sum
@@ -79,6 +79,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
 golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -115,23 +117,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME=
-golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs=
-golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
-golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U=
-golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 h1:TyKJRhyo17yWxOMCTHKWrc5rddHORMlnZ/j57umaUd8=
-golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
-golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
 golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -157,8 +142,10 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -167,6 +154,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
diff --git a/main.go b/main.go
index 1b266f4..db41dea 100644
--- a/main.go
+++ b/main.go
@@ -57,7 +57,10 @@ func run() (opts util.Options, code int, err error) {
 			return opts, 10, fmt.Errorf("format print: %w", err)
 		}
 	} else {
-		str = query.ToString(resp, opts)
+		str, err = query.ToString(resp, opts)
+		if err != nil {
+			return opts, 15, fmt.Errorf("standard print: %w", err)
+		}
 	}
 
 	fmt.Println(str)
diff --git a/main_test.go b/main_test.go
index a1a7c8a..eb42484 100644
--- a/main_test.go
+++ b/main_test.go
@@ -11,6 +11,9 @@ import (
 )
 
 func TestMain(t *testing.T) { //nolint: paralleltest // Race conditions
+	os.Stdout = os.NewFile(0, os.DevNull)
+	os.Stderr = os.NewFile(0, os.DevNull)
+
 	old := os.Args
 
 	os.Args = []string{"awl", "+yaml", "@1.1.1.1"}
@@ -30,6 +33,8 @@ func TestMain(t *testing.T) { //nolint: paralleltest // Race conditions
 
 func TestHelp(t *testing.T) {
 	old := os.Args
+	os.Stdout = os.NewFile(0, os.DevNull)
+	os.Stderr = os.NewFile(0, os.DevNull)
 
 	os.Args = []string{"awl", "-h"}
 
diff --git a/query/print_test.go b/query/print_test.go
index 0e91c2b..8dce5d1 100644
--- a/query/print_test.go
+++ b/query/print_test.go
@@ -114,9 +114,7 @@ func TestRealPrint(t *testing.T) {
 			ShowQuery: true,
 			RD:        true,
 			Verbosity: 0,
-			ShowTTL:   true,
-			Identify:  true,
-			YAML:      false,
+			ShowTTL:   false,
 			Display: util.Displays{
 				Comments:       true,
 				Question:       true,
@@ -177,11 +175,13 @@ func TestRealPrint(t *testing.T) {
 			assert.NilError(t, err)
 
 			if test.JSON || test.XML || test.YAML {
-				str, err := query.PrintSpecial(resp.DNS, test)
+				str := ""
+				str, err = query.PrintSpecial(resp.DNS, test)
 				assert.NilError(t, err)
 				assert.Assert(t, str != "")
 			}
-			str := query.ToString(resp, test)
+			str, err := query.ToString(resp, test)
+			assert.NilError(t, err)
 			assert.Assert(t, str != "")
 		})
 	}
@@ -197,5 +197,8 @@ func TestBadFormat(t *testing.T) {
 func TestEmpty(t *testing.T) {
 	t.Parallel()
 
-	assert.Assert(t, query.ToString(util.Response{}, util.Options{}) == "<nil> MsgHdr")
+	str, err := query.ToString(util.Response{}, util.Options{})
+
+	assert.Error(t, err, "no message")
+	assert.Assert(t, str == "<nil> MsgHdr")
 }
diff --git a/query/query.go b/query/query.go
index 673d559..9b1cda9 100644
--- a/query/query.go
+++ b/query/query.go
@@ -118,10 +118,15 @@ func CreateQuery(opts util.Options) (util.Response, error) {
 			} else {
 				temp := opts.Display.Statistics
 				opts.Display.Statistics = false
-				str = ToString(util.Response{
-					DNS: req,
-					RTT: 0,
-				}, opts)
+				str, err = ToString(
+					util.Response{
+						DNS: req,
+						RTT: 0,
+					}, opts)
+				if err != nil {
+					return util.Response{}, err
+				}
+
 				opts.Display.Statistics = temp
 				str += "\n;; QUERY SIZE: " + strconv.Itoa(req.Len()) + "\n"
 			}
diff --git a/query/query_test.go b/query/query_test.go
index b4550a1..b9c2be9 100644
--- a/query/query_test.go
+++ b/query/query_test.go
@@ -110,10 +110,12 @@ func TestCreateQ(t *testing.T) {
 			assert.Assert(t, res != util.Response{})
 
 			str, err := query.PrintSpecial(res.DNS, opt)
+
 			assert.NilError(t, err)
 			assert.Assert(t, str != "")
 
-			str = query.ToString(res, opt)
+			str, err = query.ToString(res, opt)
+			assert.NilError(t, err)
 			assert.Assert(t, str != "")
 		})
 	}
diff --git a/query/struct.go b/query/struct.go
index 4a64cf8..908c10f 100644
--- a/query/struct.go
+++ b/query/struct.go
@@ -3,12 +3,15 @@
 package query
 
 import (
+	"errors"
+	"fmt"
 	"strconv"
 	"strings"
 	"time"
 
 	"git.froth.zone/sam/awl/util"
 	"github.com/miekg/dns"
+	"golang.org/x/net/idna"
 )
 
 // Message is for overall DNS responses.
@@ -45,9 +48,9 @@ type Answer struct {
 // ToString turns the response into something that looks a lot like dig
 //
 // Much of this is taken from https://github.com/miekg/dns/blob/master/msg.go#L900
-func ToString(res util.Response, opts util.Options) string {
+func ToString(res util.Response, opts util.Options) (string, error) {
 	if res.DNS == nil {
-		return "<nil> MsgHdr"
+		return "<nil> MsgHdr", errNoMessage
 	}
 
 	var (
@@ -77,7 +80,12 @@ func ToString(res util.Response, opts util.Options) string {
 				}
 
 				for _, r := range res.DNS.Question {
-					s += r.String() + "\n"
+					str, err := stringParse(r.String(), false, opts)
+					if err != nil {
+						return "", fmt.Errorf("%w", err)
+					}
+
+					s += str + "\n"
 				}
 			}
 		}
@@ -90,7 +98,12 @@ func ToString(res util.Response, opts util.Options) string {
 
 				for _, r := range res.DNS.Answer {
 					if r != nil {
-						s += r.String() + "\n"
+						str, err := stringParse(r.String(), true, opts)
+						if err != nil {
+							return "", fmt.Errorf("%w", err)
+						}
+
+						s += str + "\n"
 					}
 				}
 			}
@@ -104,7 +117,12 @@ func ToString(res util.Response, opts util.Options) string {
 
 				for _, r := range res.DNS.Ns {
 					if r != nil {
-						s += r.String() + "\n"
+						str, err := stringParse(r.String(), true, opts)
+						if err != nil {
+							return "", fmt.Errorf("%w", err)
+						}
+
+						s += str + "\n"
 					}
 				}
 			}
@@ -118,7 +136,12 @@ func ToString(res util.Response, opts util.Options) string {
 
 				for _, r := range res.DNS.Extra {
 					if r != nil && r.Header().Rrtype != dns.TypeOPT {
-						s += r.String() + "\n"
+						str, err := stringParse(r.String(), true, opts)
+						if err != nil {
+							return "", fmt.Errorf("%w", err)
+						}
+
+						s += str + "\n"
 					}
 				}
 			}
@@ -163,5 +186,58 @@ func ToString(res util.Response, opts util.Options) string {
 		}
 	}
 
-	return s
+	return s, nil
 }
+
+func stringParse(str string, isAns bool, opts util.Options) (string, error) {
+	split := strings.Split(str, "\t")
+
+	// Make edits if so requested
+
+	// TODO: make less ew?
+	// This exists because the question section should be left alone EXCEPT for punycode.
+
+	if isAns {
+		if !opts.ShowTTL {
+			// Remove from existence
+			split = append(split[:1], split[2:]...)
+		}
+
+		if !opts.ShowClass {
+			// Position depends on if the TTL is there or not.
+			if opts.ShowTTL {
+				split = append(split[:2], split[3:]...)
+			} else {
+				split = append(split[:1], split[2:]...)
+			}
+		}
+
+		if opts.HumanTTL {
+			ttl, _ := strconv.Atoi(split[1])
+			split[1] = (time.Duration(ttl) * time.Second).String()
+		}
+	}
+
+	if opts.Display.UcodeTranslate {
+		var (
+			err  error
+			semi string
+		)
+
+		if strings.HasPrefix(split[0], ";") {
+			split[0] = strings.TrimPrefix(split[0], ";")
+			semi = ";"
+		}
+
+		split[0], err = idna.ToUnicode(split[0])
+		if err != nil {
+			return "", fmt.Errorf("punycode: %w", err)
+		}
+
+		split[0] = semi + split[0]
+	}
+
+	return strings.Join(split, "\t"), nil
+}
+
+var errNoMessage = errors.New("no message")
diff --git a/util/options.go b/util/options.go
index ff29c65..aaf1a74 100644
--- a/util/options.go
+++ b/util/options.go
@@ -21,6 +21,7 @@ type Options struct {
 	Display     Displays
 	TC          bool
 	ShowTTL     bool
+	ShowClass   bool
 	ShowQuery   bool
 	AA          bool
 	AD          bool
