Skip to content

Commit f5da3c1

Browse files
committed
Initial Commit
0 parents  commit f5da3c1

13 files changed

+665
-0
lines changed

.goreleaser.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
project_name: curl-httpie
2+
builds:
3+
-
4+
env:
5+
- CGO_ENABLED=0
6+
goos:
7+
- darwin
8+
binary: http
9+
git:
10+
short_hash: true
11+
brew:
12+
github:
13+
owner: rs
14+
name: homebrew-tap
15+
commit_author:
16+
name: Olivier Poitrey
17+
email: rs@rhapsodyk.net
18+
homepage: https://github.com/rs/curl-httpie
19+
description: HTTPie for curl
20+
21+
release:
22+
name_template: "{{.ProjectName}}-v{{.Version}}"

.travis.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
language: go
2+
go:
3+
- '1.9'
4+
- '1.10'
5+
- master
6+
matrix:
7+
allow_failures:
8+
- go: master
9+
script: go test -v -race -cpu=1,2,4 -bench . -benchmem ./...
10+
deploy:
11+
- provider: script
12+
skip_cleanup: true
13+
script: curl -sL http://git.io/goreleaser | bash
14+
on:
15+
tags: true
16+
condition: "$TRAVIS_OS_NAME = linux && $TRAVIS_GO_VERSION = '1.10'"
17+
env:
18+
global:
19+
secure: 7Hb/SLMTpff9oj9VYaWhl/P6eDqBav17PCIVAjyNxlRSKpRJlNDsYGz3/i1XHy7BiNMKPstOK+VlB3sR4hrGrNew8EVhROXOXl8OFstA0eptdG0TgwBxsjXiXNFRR3ancgrOlbbsfcUx5qXapQEv/kqqhhh1DrR5i/khDeMIUnZ6/Lwtgw4VTbrQD3I599ORXV7VOxu6qQKL9g0w1rJb9UM+zIDJMMULmlYp31toA520dr4tIlKJANYJLlU5hnFJAvq7ywMTbfA5h6nDtahlTP0iF879gmIoXg9MZkynmPZ4fXhdFxn3Y2xLBCUAU+DoGAXvD3ggffUWVOrnZBKXSPVTBpMHbt9Xd/HPitShOeaQQNlaj29Ky4xzD/p2cDCY4oDL8QLtRF2zP7+GfRaUY0ara3D1Q33fyyoVEeiWskCEZSiENZjrxoyevVvjbLCgT7kDJp+szvfVWJp16oHMtLQ/yXt63ZJ0PAJXUp1UJHSrlmy0r7pfEqfRWN47d45b/ld0ARMLOpMpBVGYQK8SeH2hqQ4EAyU9Erf4llFT0c/T5xgO90s+0uPUswqnIj7BuzgILsAFZQsMW3PU3HQx0mmB7m/qHYWbmkudR3C3kkKMP8IPmhZUETcCPzj4Iy93GNU6De1bv+TrjY4OFBPgKEY52ji01940v5bq90UD7jQ=

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# HTTPie for curl
2+
3+
If you like the interface of [HTTPie](https://httpie.org) but miss the features of [curl](https://curl.haxx.se), curl-httpie is what you are searching for. Curl-httpie is a drop-in replacement for `httpie` that use `curl` to perform operations. All `curl` options exposed with most syntax sugar and output formatting provided by `httpie`.
4+
5+
## Install
6+
7+
Using [homebrew](http://brew.sh/):
8+
9+
```
10+
brew install rs/tap/curl-httpie
11+
```
12+
13+
## Usage
14+
15+
Hello World:
16+
17+
$ http httpie.org
18+
19+
Synopsis:
20+
21+
$ http [curl options] [METHOD] URL [ITEM [ITEM]]
22+
23+
See [HTTPie doc](https://httpie.org/doc) for more examples.
24+
25+
## Differences with httpie
26+
27+
* Like `curl` but unlike `httpie`, headers are written on `stderr` instead of `stdout`.
28+
* Output is not buffered, all the formatting is done on the fly so you can easily debug streamed data.
29+
*

args/curlopts.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package args
2+
3+
var (
4+
curlShortValues = "EKCbcdDFPHmoUreXYytzTuAw"
5+
curlLongValues = []string{"abstract-unix-socket", "cacert", "capath", "cert", "cert-type", "ciphers", "config", "connect-timeout", "connect-to", "continue-at", "cookie", "cookie-jar", "crlfile", "data", "data-ascii", "data-binary", "data-raw", "data-urlencode", "delegation", "dns-interface", "dns-ipv4-addr", "dns-ipv6-addr", "dns-servers", "dump-header", "egd-file", "engine", "expect100-timeout", "form", "form-string", "ftp-account", "ftp-alternative-to-user", "ftp-method", "ftp-port", "ftp-ssl-ccc-mode", "header", "hostpubmd5", "interface", "keepalive-time", "key", "key-type", "krb", "libcurl", "limit-rate", "local-port", "login-options", "mail-auth", "mail-from", "mail-rcpt", "max-filesize", "max-redirs", "max-time", "netrc-file", "noproxy", "output", "pass", "pinnedpubkey", "proto", "proto-default", "proto-redir", "proxy-cacert", "proxy-capath", "proxy-cert", "proxy-cert-type", "proxy-ciphers", "proxy-crlfile", "proxy-header", "proxy-key", "proxy-key-type", "proxy-pass", "proxy-service-name", "proxy-tlsauthtype", "proxy-tlspassword", "proxy-tlsuser", "proxy-user", "proxy1.0", "pubkey", "random-file", "range", "referer", "request", "resolve", "retry", "retry-delay", "retry-max-time", "service-name", "socks4", "socks4a", "socks5", "socks5-gssapi-service", "socks5-hostname", "speed-limit", "speed-time", "telnet-option", "tftp-blksize", "time-cond", "tls-max", "tlsauthtype", "tlsuser", "trace", "trace-ascii", "unix-socket", "upload-file", "url", "user", "user-agent", "write-out"}
6+
)

args/gen.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package args
2+
3+
//go:generate ./gen.sh

args/gen.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
curl -h|perl -pe 's/^\s*(?:-([^-]),\s+)?--(.*?)\s+<.*?>.*/push @a, $1; push @b, $2/e; undef $_; END {print "package args\n\nvar (\n\tcurlShortValues = \"", @a, "\"\n\tcurlLongValues = []string{", join(", ", map {"\"$_\""} @b), "}\n)\n"}' > curlopts.go

args/httpie.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package args
2+
3+
import (
4+
"encoding/json"
5+
"net/url"
6+
"strings"
7+
)
8+
9+
type argType int
10+
11+
const (
12+
unknownArg argType = iota
13+
headerArg
14+
paramArg
15+
fieldArg
16+
jsonArg
17+
)
18+
19+
func parseFancyArgs(args []string) (opts []string) {
20+
if len(args) == 0 {
21+
return
22+
}
23+
method := strings.ToUpper(args[0])
24+
switch method {
25+
case "HEAD", "GET", "POST", "PUT", "DELETE":
26+
opts = append(opts, "-X", method)
27+
args = args[1:]
28+
}
29+
if len(args) == 0 {
30+
return
31+
}
32+
hostIdx := len(opts)
33+
opts = append(opts, args[0]) // host
34+
args = args[1:]
35+
data := map[string]interface{}{}
36+
for _, arg := range args {
37+
typ, name, value := parseArg(arg)
38+
switch typ {
39+
case headerArg:
40+
opts = append(opts, "-H", name+":"+value)
41+
case paramArg:
42+
opts[hostIdx] = appendURLParam(opts[hostIdx], name, value)
43+
case fieldArg:
44+
data[name] = value
45+
case jsonArg:
46+
var v interface{}
47+
json.Unmarshal([]byte(value), &v)
48+
data[name] = v
49+
default:
50+
opts = append(opts, arg)
51+
}
52+
}
53+
if len(data) > 0 {
54+
j, _ := json.Marshal(data)
55+
opts = append(opts, "-d", string(j))
56+
}
57+
return
58+
}
59+
60+
func parseArg(arg string) (typ argType, name, value string) {
61+
for i := 0; i < len(arg); i++ {
62+
switch arg[i] {
63+
case ':':
64+
if i+1 < len(arg) && arg[i+1] == '=' {
65+
return jsonArg, arg[:i], arg[i+2:]
66+
}
67+
return headerArg, arg[:i], arg[i+1:]
68+
case '=':
69+
if i+1 < len(arg) && arg[i+1] == '=' {
70+
return paramArg, arg[:i], arg[i+2:]
71+
}
72+
return fieldArg, arg[:i], arg[i+1:]
73+
}
74+
}
75+
return
76+
}
77+
78+
func appendURLParam(u, name, value string) string {
79+
sep := "?"
80+
if strings.IndexByte(u, '?') != -1 {
81+
sep = "&"
82+
}
83+
return u + sep + url.QueryEscape(name) + "=" + url.QueryEscape(value)
84+
}

args/parse.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package args
2+
3+
import (
4+
"sort"
5+
"strings"
6+
)
7+
8+
type Opts []string
9+
10+
func (opts Opts) index(opt string) int {
11+
off := 1
12+
if len(opt) > 1 {
13+
off = 2
14+
}
15+
for i, o := range opts {
16+
if len(o) >= 2 && o[0] == '-' {
17+
if o[off:] == opt {
18+
return i
19+
}
20+
}
21+
}
22+
return -1
23+
}
24+
25+
// Has returns true if opt flag is in opts.
26+
func (opts Opts) Has(opt string) bool {
27+
return opts.index(opt) != -1
28+
}
29+
30+
func (opts Opts) Val(opt string) string {
31+
if idx := opts.index(opt); idx != -1 && idx+1 < len(opts) {
32+
return opts[idx+1]
33+
}
34+
return ""
35+
}
36+
37+
// Parse converts an HTTPie like argv into a list of curl options.
38+
func Parse(argv []string) (opts Opts) {
39+
args := []string{}
40+
sort.Strings(curlLongValues)
41+
more := true
42+
for i := 1; i < len(argv); i++ {
43+
arg := argv[i]
44+
if !more || len(arg) < 2 || arg[0] != '-' {
45+
args = append(args, arg)
46+
continue
47+
}
48+
if arg == "--" {
49+
// Enf of opts marker
50+
more = false
51+
continue
52+
}
53+
opts = append(opts, arg)
54+
if arg[1] == '-' {
55+
if longHasValue(arg[2:]) && i+1 < len(argv) {
56+
opts = append(opts, argv[i+1])
57+
i++
58+
}
59+
continue
60+
}
61+
for j := 1; j < len(arg); j++ {
62+
if strings.IndexByte(curlShortValues, arg[j]) != -1 {
63+
if j == len(arg)-1 && i+1 < len(argv) {
64+
opts = append(opts, argv[i+1])
65+
}
66+
break
67+
}
68+
}
69+
}
70+
if len(args) > 0 {
71+
opts = append(opts, parseFancyArgs(args)...)
72+
}
73+
return
74+
}
75+
76+
func longHasValue(arg string) bool {
77+
i := sort.SearchStrings(curlLongValues, arg)
78+
return i < len(curlLongValues) && curlLongValues[i] == arg
79+
}

formatter/cleanup.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package formatter
2+
3+
import (
4+
"bytes"
5+
"io"
6+
)
7+
8+
// HeaderCleaner removes > and < from curl --verbose output.
9+
type HeaderCleaner struct {
10+
Out io.Writer
11+
12+
// Verbose removes the request headers part the output as well as the lines
13+
// starting with * if set to false.
14+
Verbose bool
15+
16+
// Post is inserted after the request headers.
17+
Post *bytes.Buffer
18+
19+
inited bool
20+
muted bool
21+
buf []byte
22+
last byte
23+
skip int
24+
skipLine bool
25+
body bool
26+
}
27+
28+
func (c *HeaderCleaner) Write(p []byte) (n int, err error) {
29+
if !c.inited {
30+
c.inited = true
31+
c.muted = !c.Verbose
32+
}
33+
cp := c.buf
34+
for i := 0; i < len(p); i++ {
35+
b := p[i]
36+
if c.skipLine && b != '\n' {
37+
continue
38+
}
39+
c.skipLine = false
40+
if c.skip > 0 {
41+
c.skip--
42+
continue
43+
}
44+
switch b {
45+
case '>', '<':
46+
if c.last == '\n' {
47+
c.skip = 1 // space
48+
c.last = b
49+
continue
50+
}
51+
case '\r':
52+
if c.last == '>' {
53+
c.body = true
54+
if c.muted {
55+
c.muted = false
56+
c.skip = 1
57+
c.last = '\n'
58+
continue
59+
}
60+
}
61+
default:
62+
if c.last == '\n' || c.last == 0 {
63+
switch b {
64+
case '{', '}':
65+
c.skipLine = true
66+
c.skip = 1
67+
continue
68+
case '*':
69+
if !c.Verbose {
70+
c.skipLine = true
71+
c.skip = 1
72+
continue
73+
}
74+
if c.Post != nil && c.body {
75+
cp = append(append(cp, c.Post.Bytes()...), '\n')
76+
c.Post = nil
77+
}
78+
}
79+
}
80+
}
81+
if !c.muted {
82+
cp = append(cp, b)
83+
}
84+
c.last = b
85+
}
86+
if len(cp) > 0 {
87+
n, err = c.Out.Write(cp)
88+
if err != nil || n != len(cp) {
89+
return
90+
}
91+
}
92+
return len(p), nil
93+
}

0 commit comments

Comments
 (0)