Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func runAPI(cmd *cobra.Command, args []string) error {
return handleAPIError(err)
}

return output.FprintProcess(cmd.OutOrStdout(), result, jq)
return output.FprintProcess(cmd.OutOrStdout(), result, jq, GetRawFlag(cmd))
}

// resolveMethod determines the HTTP method from the flag or defaults.
Expand Down
6 changes: 3 additions & 3 deletions cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ Token resolution order:

jq := GetJQFlag(cmd)
data, _ := json.Marshal(statusResult)
return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq)
return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq, GetRawFlag(cmd))
},
}

Expand Down Expand Up @@ -498,7 +498,7 @@ func runAuthSignupVerify(cmd *cobra.Command, args []string) error {
// the response so the caller can capture the token manually.
saveErr := saveSignupCredentials(result, body, baseURL)

if err := output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)); err != nil {
if err := output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)); err != nil {
return err
}
if saveErr != nil {
Expand Down Expand Up @@ -597,7 +597,7 @@ func runSignupRequest(cmd *cobra.Command, path string) error {
return handleAPIError(err)
}

return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}

// loadStoredServiceAccountToken reads the saved sa_live_ token from
Expand Down
2 changes: 1 addition & 1 deletion cmd/domains_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,5 @@ func runDomainsAdd(cmd *cobra.Command, args []string) error {
if err != nil {
return handleAPIError(err)
}
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}
2 changes: 1 addition & 1 deletion cmd/domains_authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ func runDomainsVerify(cmd *cobra.Command, args []string) error {
}

if GetJQFlag(cmd) != "" {
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}

return printDomainVerifyResult(cmd, result, dom.Name)
Expand Down
6 changes: 3 additions & 3 deletions cmd/domains_from_addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func runFromAddressesList(cmd *cobra.Command, args []string) error {
if err != nil {
return handleAPIError(err)
}
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}

func runFromAddressesAdd(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -209,7 +209,7 @@ func runFromAddressesAdd(cmd *cobra.Command, args []string) error {
if err != nil {
return handleAPIError(err)
}
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}

func runFromAddressesUpdate(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -280,7 +280,7 @@ func runFromAddressesUpdate(cmd *cobra.Command, args []string) error {
if err != nil {
return handleAPIError(err)
}
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}

func runFromAddressesDelete(cmd *cobra.Command, args []string) error {
Expand Down
2 changes: 1 addition & 1 deletion cmd/domains_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,5 @@ func runDomainsGet(cmd *cobra.Command, args []string) error {
if err != nil {
return handleAPIError(err)
}
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}
4 changes: 2 additions & 2 deletions cmd/domains_link_tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func runLinkTrackingConfigure(cmd *cobra.Command, args []string) error {
if err != nil {
return handleAPIError(err)
}
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}

func runLinkTrackingVerify(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -188,7 +188,7 @@ func runLinkTrackingVerify(cmd *cobra.Command, args []string) error {
}

if GetJQFlag(cmd) != "" {
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}

return printDNSCheckResult(cmd, result, dom.Name, "link_tracking", "cio domains link_tracking verify")
Expand Down
2 changes: 1 addition & 1 deletion cmd/domains_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ func runDomainsList(cmd *cobra.Command, args []string) error {
if err != nil {
return handleAPIError(err)
}
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd))
}
14 changes: 8 additions & 6 deletions cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import (
// doPageAll runs auto-pagination and writes NDJSON to stdout.
func doPageAll(cmd *cobra.Command, c *client.Client, path string, params map[string]string, startPage, limit int) error {
jq := GetJQFlag(cmd)
raw := GetRawFlag(cmd)

var w io.Writer = cmd.OutOrStdout()
if jq != "" {
w = &filterWriter{w: cmd.OutOrStdout(), jq: jq}
if jq != "" || raw {
w = &filterWriter{w: cmd.OutOrStdout(), jq: jq, raw: raw}
}

err := c.PageAll(client.PageAllConfig{
Expand All @@ -36,18 +37,19 @@ func doPageAll(cmd *cobra.Command, c *client.Client, path string, params map[str
return nil
}

// filterWriter wraps an io.Writer and applies --jq to each line written.
// filterWriter wraps an io.Writer and applies --jq / --raw-output to each line written.
type filterWriter struct {
w io.Writer
jq string
w io.Writer
jq string
raw bool
}

func (fw *filterWriter) Write(p []byte) (int, error) {
trimmed := bytes.TrimRight(p, "\n")
if len(trimmed) == 0 {
return len(p), nil
}
if err := output.FprintProcess(fw.w, json.RawMessage(trimmed), fw.jq); err != nil {
if err := output.FprintProcess(fw.w, json.RawMessage(trimmed), fw.jq, fw.raw); err != nil {
return 0, err
}
return len(p), nil
Expand Down
2 changes: 1 addition & 1 deletion cmd/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ var profileListCmd = &cobra.Command{
profiles = []client.ProfileInfo{}
}
data, _ := json.Marshal(map[string]any{"profiles": profiles})
return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), GetJQFlag(cmd))
return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), GetJQFlag(cmd), GetRawFlag(cmd))
},
}

Expand Down
7 changes: 7 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func init() {
flags.String("json", "", "Raw JSON request body, @filename to read from a file, or - to read from stdin")
flags.String("params", "", "Query parameters as JSON, converted to query string for GET")
flags.String("jq", "", "jq expression filter (via gojq)")
flags.BoolP("raw-output", "r", false, "Print string results unquoted, like jq -r (no external jq needed)")
flags.Bool("dry-run", false, "Validate and print request, don't execute")
flags.Bool("read-only", false, "Request a read-only session (scope=read_only); only GET requests are permitted")
flags.StringSlice("scope", nil, "Additional OAuth scope(s) to request during token exchange")
Expand Down Expand Up @@ -227,6 +228,12 @@ func GetJQFlag(cmd *cobra.Command) string {
return jq
}

// GetRawFlag returns the --raw-output flag value.
func GetRawFlag(cmd *cobra.Command) bool {
raw, _ := cmd.Flags().GetBool("raw-output")
return raw
}

// GetPaginationFlags returns the --page, --limit, and --page-all flag values.
func GetPaginationFlags(cmd *cobra.Command) (page, limit int, pageAll bool) {
page, _ = cmd.Flags().GetInt("page")
Expand Down
2 changes: 1 addition & 1 deletion cmd/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,5 +336,5 @@ func schemaOutput(cmd *cobra.Command, v any) error {
return err
}
jq := GetJQFlag(cmd)
return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq)
return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq, GetRawFlag(cmd))
}
4 changes: 2 additions & 2 deletions cmd/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ func runTrackSend(cmd *cobra.Command, sendPath string, body json.RawMessage) err

watch, _ := cmd.Flags().GetBool("watch")
if !watch {
return output.FprintProcess(cmd.OutOrStdout(), result, jq)
return output.FprintProcess(cmd.OutOrStdout(), result, jq, GetRawFlag(cmd))
}

// Extract the delivery_id so we can poll its status.
Expand Down Expand Up @@ -416,7 +416,7 @@ func watchDelivery(cmd *cobra.Command, envID, deliveryID string) error {
}
if terminal, state := isTerminalDelivery(result); terminal {
fmt.Fprintf(stderr, " email %s!\n", state)
return output.FprintProcess(cmd.OutOrStdout(), result, jq)
return output.FprintProcess(cmd.OutOrStdout(), result, jq, GetRawFlag(cmd))
}
fmt.Fprint(stderr, ".")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/skills.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,5 @@ func skillsOutput(cmd *cobra.Command, v any) error {
return err
}
jq := GetJQFlag(cmd)
return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq)
return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq, GetRawFlag(cmd))
}
2 changes: 1 addition & 1 deletion cmd/transactional.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,5 +280,5 @@ func runTransactionalList(cmd *cobra.Command, args []string) error {
return handleAPIError(err)
}

return output.FprintProcess(cmd.OutOrStdout(), result, jq)
return output.FprintProcess(cmd.OutOrStdout(), result, jq, GetRawFlag(cmd))
}
20 changes: 20 additions & 0 deletions internal/output/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,23 @@ func FprintNDJSON(w io.Writer, items []json.RawMessage) error {
}
return nil
}

// FprintRaw writes each item the way `jq -r` does: a JSON string is written as
// its raw, unquoted value; anything else (object, array, number, bool, null) is
// written as its compact JSON. One item per line. This lets callers extract a
// scalar (e.g. a compiled HTML body) without the external `jq -r` round-trip.
func FprintRaw(w io.Writer, items []json.RawMessage) error {
for _, item := range items {
var s string
if err := json.Unmarshal(item, &s); err == nil {
if _, err := fmt.Fprintln(w, s); err != nil {
return err
}
continue
}
if _, err := fmt.Fprintf(w, "%s\n", item); err != nil {
return err
}
}
return nil
}
11 changes: 9 additions & 2 deletions internal/output/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import (
"io"
)

// FprintProcess applies --jq filtering to data and prints to w.
func FprintProcess(w io.Writer, data json.RawMessage, jqExpr string) error {
// FprintProcess applies --jq filtering to data and prints to w. When raw is
// true, string results are printed unquoted (like `jq -r`).
func FprintProcess(w io.Writer, data json.RawMessage, jqExpr string, raw bool) error {
if jqExpr != "" {
results, err := ApplyJQ(data, jqExpr)
if err != nil {
return err
}
if raw {
return FprintRaw(w, results)
}
return FprintNDJSON(w, results)
}

if raw {
return FprintRaw(w, []json.RawMessage{data})
}
return FprintJSON(w, json.RawMessage(data))
}
57 changes: 57 additions & 0 deletions internal/output/raw_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package output

import (
"bytes"
"encoding/json"
"testing"
)

func TestFprintProcess_Raw(t *testing.T) {
cases := []struct {
name string
data string
jq string
raw bool
want string
}{
{
name: "raw string result is unquoted",
data: `{"html":"<h1>Hi</h1>"}`,
jq: ".html",
raw: true,
want: "<h1>Hi</h1>\n",
},
{
name: "raw preserves multi-line value verbatim",
data: `{"html":"line1\nline2"}`,
jq: ".html",
raw: true,
want: "line1\nline2\n",
},
{
name: "without raw, string stays JSON-quoted",
data: `{"name":"Acme Inc"}`,
jq: ".name",
raw: false,
want: "\"Acme Inc\"\n",
},
{
name: "raw on non-string result falls back to compact JSON",
data: `{"obj":{"a":1}}`,
jq: ".obj",
raw: true,
want: "{\"a\":1}\n",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var buf bytes.Buffer
if err := FprintProcess(&buf, json.RawMessage(tc.data), tc.jq, tc.raw); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got := buf.String(); got != tc.want {
t.Errorf("output mismatch:\n want: %q\n got: %q", tc.want, got)
}
})
}
}