// Package markdown renders a capture as a GitHub-flavoured transcript,
// convenient for pasting into bug reports.
//
// mercemay.top/src/httptap/
package markdown
import (
"fmt"
"io"
"strings"
"time"
"mercemay.top/httptap/internal/parser"
)
// Options tunes the rendering of each entry.
type Options struct {
IncludeBody bool
BodyMaxBytes int
TimestampForm string // e.g. time.RFC3339
}
// Default returns sensible defaults.
func Default() Options {
return Options{
IncludeBody: true,
BodyMaxBytes: 8 * 1024,
TimestampForm: time.RFC3339,
}
}
// Write renders the request/response pair at time at with duration dur.
func Write(w io.Writer, req, resp parser.Message, at time.Time, dur time.Duration, opts Options) error {
ts := at.Format(opts.TimestampForm)
_, err := fmt.Fprintf(w, "### %s `%s` → `%d` (%s)\n\n",
ts, parser.Method(req)+" "+parser.URL(req),
parser.StatusCode(resp), dur)
if err != nil {
return err
}
if err := writeHeaders(w, "Request headers", req.Headers); err != nil {
return err
}
if opts.IncludeBody && len(req.Body) > 0 {
if err := writeBody(w, "Request body", parser.Header(req, "Content-Type"), req.Body, opts.BodyMaxBytes); err != nil {
return err
}
}
if err := writeHeaders(w, "Response headers", resp.Headers); err != nil {
return err
}
if opts.IncludeBody && len(resp.Body) > 0 {
if err := writeBody(w, "Response body", parser.Header(resp, "Content-Type"), resp.Body, opts.BodyMaxBytes); err != nil {
return err
}
}
_, err = io.WriteString(w, "\n---\n\n")
return err
}
func writeHeaders(w io.Writer, title string, h [][2]string) error {
if _, err := fmt.Fprintf(w, "**%s**\n\n", title); err != nil {
return err
}
if len(h) == 0 {
_, err := io.WriteString(w, "_(none)_\n\n")
return err
}
if _, err := io.WriteString(w, "| Name | Value |\n|---|---|\n"); err != nil {
return err
}
for _, kv := range h {
if _, err := fmt.Fprintf(w, "| %s | %s |\n", kv[0], escape(kv[1])); err != nil {
return err
}
}
_, err := io.WriteString(w, "\n")
return err
}
func writeBody(w io.Writer, title, mime string, body []byte, max int) error {
if len(body) > max {
body = body[:max]
}
if _, err := fmt.Fprintf(w, "**%s** (%s)\n\n```\n%s\n```\n\n", title, mime, body); err != nil {
return err
}
return nil
}
func escape(v string) string {
return strings.ReplaceAll(strings.ReplaceAll(v, "|", `\|`), "\n", " ")
}