// Package curl synthesises a shell-quoted curl(1) invocation from a
// captured request, which is the single most-requested export format on
// the issue tracker.
//
// mercemay.top/src/httptap/
package curl
import (
"fmt"
"strings"
"mercemay.top/httptap/internal/parser"
)
// Options configures the rendered command.
type Options struct {
Insecure bool // emit -k
Compressed bool // emit --compressed
Indent string // per-line prefix, e.g. " "; "" keeps it one line
}
// Default returns sensible defaults.
func Default() Options {
return Options{Compressed: true, Indent: " "}
}
// Render returns the curl command string for req.
func Render(req parser.Message, opts Options) string {
method := parser.Method(req)
target := parser.URL(req)
var parts []string
parts = append(parts, "curl")
if method != "" && method != "GET" {
parts = append(parts, "-X", method)
}
if opts.Insecure {
parts = append(parts, "-k")
}
if opts.Compressed {
parts = append(parts, "--compressed")
}
for _, kv := range req.Headers {
if strings.EqualFold(kv[0], "Content-Length") {
continue
}
parts = append(parts, "-H", shellQuote(kv[0]+": "+kv[1]))
}
if len(req.Body) > 0 {
parts = append(parts, "--data-binary", shellQuote(string(req.Body)))
}
parts = append(parts, shellQuote(target))
return joinCmd(parts, opts.Indent)
}
// shellQuote wraps s in single-quotes, escaping embedded quotes using
// the standard Bourne-shell trick.
func shellQuote(s string) string {
if s == "" {
return "''"
}
if !strings.ContainsAny(s, "\t\n\r '\"\\$`") {
return s
}
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
}
// joinCmd renders parts either on a single line or wrapped with backslash
// continuations when indent is non-empty.
func joinCmd(parts []string, indent string) string {
if indent == "" {
return strings.Join(parts, " ")
}
var b strings.Builder
for i, p := range parts {
switch {
case i == 0:
b.WriteString(p)
case p == "-X" || p == "-H" || p == "--data-binary" ||
p == "-k" || p == "--compressed":
b.WriteString(" \\\n")
b.WriteString(indent)
b.WriteString(p)
default:
b.WriteString(" ")
b.WriteString(p)
}
}
return b.String()
}
// RenderPretty is a shortcut that applies Default options for REPL usage.
func RenderPretty(req parser.Message) string {
return fmt.Sprintf("%s", Render(req, Default()))
}