Skip to content

Commit

Permalink
Simplify stderr cleanup logic and improve args handling
Browse files Browse the repository at this point in the history
  • Loading branch information
rs committed Aug 2, 2018
1 parent cad0632 commit b76acab
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 93 deletions.
14 changes: 11 additions & 3 deletions args/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,26 @@ func Parse(argv []string) (opts Opts) {
more = false
continue
}
opts = append(opts, arg)
if arg[1] == '-' {
opts = append(opts, arg)
if longHasValue(arg[2:]) && i+1 < len(argv) {
opts = append(opts, argv[i+1])
i++
}
continue
}
// Parse componed short args
for j := 1; j < len(arg); j++ {
opts = append(opts, string([]byte{'-', arg[j]}))
if strings.IndexByte(curlShortValues, arg[j]) != -1 {
if j == len(arg)-1 && i+1 < len(argv) {
opts = append(opts, argv[i+1])
// Short arg as value, it must be last in compound.
// The value is either the remaining or the next arg.
if j == len(arg)-1 {
if i+1 < len(argv) {
opts = append(opts, argv[i+1])
}
} else {
opts = append(opts, arg[j+1:])
}
break
}
Expand Down
123 changes: 39 additions & 84 deletions formatter/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"io"
)

var curlErrPrefix = []byte("curl: (")

// HeaderCleaner removes > and < from curl --verbose output.
type HeaderCleaner struct {
Out io.Writer
Expand All @@ -18,100 +16,57 @@ type HeaderCleaner struct {
// Post is inserted after the request headers.
Post *bytes.Buffer

inited bool
muted bool
buf []byte
last byte // last byte scanned
skip int // skip n chars
skipLine bool // skip all chars until next \n
printLine bool // print all chars until next \n
body bool

// curl error doesn't come in a predictable single write(), we thus
// need to match it byte per byte
curlErrIdx int
buf []byte
line []byte
}

func (c *HeaderCleaner) Write(p []byte) (n int, err error) {
if !c.inited {
c.inited = true
c.muted = !c.Verbose
}
n = len(p)
cp := c.buf
for i := 0; i < len(p); i++ {
b := p[i]
if c.printLine && b != '\n' {
cp = append(cp, b)
continue
}
if c.skipLine && b != '\n' {
continue
}
c.skipLine = false
if c.skip > 0 {
c.skip--
continue
}
if (c.last == '\n' || c.last == 0 || c.curlErrIdx > 0) && curlErrPrefix[c.curlErrIdx] == b {
c.curlErrIdx++
if c.curlErrIdx >= len(curlErrPrefix) {
c.curlErrIdx = 0
c.printLine = true
cp = append(cp, curlErrPrefix...)
}
continue
for len(p) > 0 {
idx := bytes.IndexByte(p, '\n')
if idx == -1 {
c.line = append(c.line, p...)
break
}
c.curlErrIdx = 0
c.line = append(c.line, p[:idx+1]...)
p = p[idx+1:]
ignore := false
b, i := firstVisibleChar(c.line)
switch b {
case '>', '<':
if c.last == '\n' {
c.skip = 1 // space
c.last = b
continue
c.line = c.line[i+2:]
case '}', '{':
ignore = true
if c.Post != nil {
cp = append(append(cp, bytes.TrimSpace(c.Post.Bytes())...), '\n', '\n')
c.Post = nil
}
case '\r':
if c.last == '>' {
c.body = true
if c.muted {
c.muted = false
c.skip = 1
c.last = '\n'
continue
}
}
default:
if c.last == '\n' || c.last == 0 {
switch b {
case '{', '}':
c.skipLine = true
c.skip = 1
continue
case '*':
cp = append(cp, "###")
if !c.Verbose {
c.skipLine = true
c.skip = 1
continue
}
if c.Post != nil && c.body {
cp = append(append(cp, c.Post.Bytes()...), '\n')
c.Post = nil
}
}
case '*':
if !c.Verbose {
ignore = true
}
}
if !c.muted || c.printLine {
cp = append(cp, b)
if !ignore {
cp = append(cp, c.line...)
}
c.last = b
c.printLine = false
c.line = c.line[:0]
}
if len(cp) > 0 {
n, err = c.Out.Write(cp)
if err != nil || n != len(cp) {
return
_, err = c.Out.Write(cp)
return
}

var colorEscape = []byte("\x1b[")

func firstVisibleChar(b []byte) (byte, int) {
if bytes.HasPrefix(b, colorEscape) {
if idx := bytes.IndexByte(b, 'm'); idx != -1 {
if idx < len(b) {
return b[idx+1], idx + 1
} else {
return 0, -1
}
}
}
return len(p), nil
return b[0], 0
}

15 changes: 10 additions & 5 deletions formatter/color.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package formatter

import (
"bytes"
"io"
"regexp"
)
Expand All @@ -14,6 +13,7 @@ type ColorScheme struct {
Field string
Value string
Literal string
Error string
}

type ColorName int
Expand All @@ -26,6 +26,7 @@ const (
FieldColor
ValueColor
LiteralColor
ErrorColor
)

func (cs ColorScheme) Color(name ColorName) string {
Expand All @@ -44,6 +45,8 @@ func (cs ColorScheme) Color(name ColorName) string {
return cs.Value
case LiteralColor:
return cs.Literal
case ErrorColor:
return cs.Error
}
return ""
}
Expand All @@ -59,6 +62,7 @@ var DefaultColorScheme = ColorScheme{
Field: "\x1b[38;5;33m",
Value: "\x1b[38;5;37m",
Literal: "\x1b[38;5;166m",
Error: "\x1b[38;5;1m",
}

type HeaderColorizer struct {
Expand Down Expand Up @@ -91,6 +95,11 @@ type headerFormatter struct {
}

var headerFormatters = []headerFormatter{
{
// Curl errors
regexp.MustCompile(`^(curl: \(\d+\).*)(\n)$`),
[]ColorName{ErrorColor, ResetColor},
},
{
// Method + Status line
regexp.MustCompile(`^([A-Z]+)(\s+\S+\s+)(HTTP)(/)([\d\.]+\s*)(\n)$`),
Expand Down Expand Up @@ -122,10 +131,6 @@ func (c *HeaderColorizer) formatLine() {
c.buf = append(c.buf, c.line...)
return
}
if bytes.HasPrefix(c.line, curlErrPrefix) {
c.buf = append(c.buf, c.line...)
return
}
for _, formatter := range headerFormatters {
m := formatter.re.FindSubmatch(c.line)
if m == nil {
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ func main() {
verbose := opts.Has("verbose") || opts.Has("v")
quiet := opts.Has("silent") || opts.Has("s")
pretty := opts.Remove("pretty")
opts.Remove("i")

if len(opts) == 0 {
// Show help if no args
opts = append(opts, "-h")
} else {
// Remove progress bar.
opts = append(opts, "-sS")
opts = append(opts, "-s", "-S")
}

// Change default method based on binary name.
Expand Down

0 comments on commit b76acab

Please sign in to comment.