diff --git a/.gitignore b/.gitignore index 0cacdfc..99e36be 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ *.so *.dylib hopp-cli +hoop.bin +dist/ # Test binary, built with `go test -c` *.test diff --git a/.goreleaser.yml b/.goreleaser.yml index af3aaf6..7d766ce 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,16 +1,10 @@ -# This is an example goreleaser.yaml file with some sane defaults. -# Make sure to check the documentation at http://goreleaser.com -before: - hooks: - # you may remove this if you don't use vgo - - go mod tidy - # you may remove this if you don't need go generate - - go get -v +env: + - RELEASE_BUILDS=dist/hopp-cli_darwin_amd64/hopp-cli dist/hopp-cli_linux_386/hopp-cli dist/hopp-cli_linux_amd64/hopp-cli dist/hopp-cli_windows_386/hopp-cli.exe dist/hopp-cli_windows_amd64/hopp-cli.exe builds: - env: - CGO_ENABLED=0 ldflags: - - -s -w -X main.VERSION={{.Version}} + - -s -w -X main.buildVersion={{.Version}} goos: - darwin - linux @@ -18,6 +12,8 @@ builds: goarch: - 386 - amd64 + hooks: + post: make pack-releases ignore: - goos: darwin goarch: 386 diff --git a/Makefile b/Makefile index b0bd6e8..ca5ea23 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,18 @@ +BIN:=hopp.bin PREFIX?=/usr/local BINDIR?=$(PREFIX)/bin VERSION?=$(shell git tag | grep ^v | sort -V | tail -n 1) -GOFLAGS?=-ldflags '-X main.VERSION=${VERSION}' +STATIC := ./templates/index.html ./templates/template.md:/template.md -hopp-cli: cli.go go.mod go.sum +deps: + go get -u github.com/knadh/stuffbin/... + +build: cli.go go.mod go.sum @echo @echo Building hopp-cli. This may take a minute or two. @echo - go build $(GOFLAGS) -o $@ + go build -o ${BIN} -ldflags="-s -w -X 'main.buildVersion=${VERSION}'" *.go + stuffbin -a stuff -in ${BIN} -out ${BIN} ${STATIC} @echo @echo ...Done\! @@ -46,3 +51,6 @@ uninstall: rm -f $(BINDIR)/hopp-cli @echo @echo ...Done\! +.PHONY: pack-releases +pack-releases: + $(foreach var,$(RELEASE_BUILDS),stuffbin -a stuff -in ${var} -out ${var} ${STATIC};) \ No newline at end of file diff --git a/README.md b/README.md index e3a008f..c4903bb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Hoppscotch CLI [![hoppscotch](https://img.shields.io/badge/Made_for-Hoppscotch-hex_color_code?logo=Postwoman)](https://hoppscotch.io) [![Go Report Card](https://goreportcard.com/badge/github.com/athul/pwcli)](https://goreportcard.com/report/github.com/athul/pwcli) +# Hoppscotch CLI [![hoppscotch](https://img.shields.io/badge/Made_for-Hoppscotch-hex_color_code?logo=Postwoman)](https://hoppscotch.io) [![Go Report Card](https://goreportcard.com/badge/github.com/athul/pwcli)](https://goreportcard.com/report/github.com/athul/pwcli) -Send HTTP requests from terminal. An alternative to cURL, httpie ⚡️ +Send HTTP requests from terminal and Generate API Docs. An alternative to cURL, httpie ⚡️ -# Installation +## Installation ### From Script @@ -34,46 +34,80 @@ $ sudo make install - Mac(x64) - Windows(x64,x86) +> **IMPORTANT: Not tested on Windows, please leave your feedback/bugs in the Issues section** + ### Homebrew Install by `brew install athul/tap/hopp-cli` -> **IMPORTANT: Not tested on Windows, please leave your feedback/bugs in the Issues section** - -# Usages +## Usages Putting Simply: **Just pass the URL to the request method** -## Basic -- GET : `hopp-cli get ` -- POST: `hopp-cli post ` -- PATCH: `hopp-cli patch ` -- PUT : `hopp-cli put ` -- DELETE: `hopp-cli delete ` -Example for a POST request: -`hopp-cli post https://reqres.in/api/users/2 -c js -b '{"name": "morp","job": "zion resident"}` +### Basic Commands + +- GET : `$ hopp-cli get ` +- POST: `$ hopp-cli post ` +- PATCH: `$ hopp-cli patch ` +- PUT : `$ hopp-cli put ` +- DELETE: `$ hopp-cli delete ` + +Example for a POST request: + +```shell +$ hopp-cli post https://reqres.in/api/users/2 -c js -b '{"name": "morp","job": "zion resident"}' + +``` + +### Extra Commands + +- `send` for testing multiple endpoints +- `gen` for generating API docs from Collection + +**SEND**: This can be used to test multiple endpoints from the `hoppscotch-collection.json` file. -### Extra +> The output will only be the `statuscode` -**SEND**: This can be used to test multiple endpoints from the `hoppscotch-collection.json` file. The output will only be the `statuscode`. Example : `hopp-cli send ` -o/p: + +Sample Output: ![](/assets/send.png) +--- + +**GEN**: Gen command Generates the API Documentation from `hoppscotch-collection.json` file and serves it as a Static Page on port `1341` +Example: `hopp-cli gen ` + +Sample Hosted site: https://hopp-docsify.surge.sh/ + +Powered by [Doscify](https://docsify.js.org) + +Flags: + +- `browser` or `b` to toggle whether the browser should open automatically [Boolean] +- `port` or `p` for specifying the port where the server should listen to [Integer] ### There are 3 Authentication Flags -*(optional)* + +_(optional)_ + - `-t` or `--token` for a Bearer Token for Authentication - `-u` for the `Username` in Basic Auth - `-p` for the `password` in Basic Auth + ### There are 2 flags especially for the data management requests like POST,PUT,PATCH and DELETE -- `-c` or `--ctype` for the *Content Type* -- `-b` or `--body` for the Data Body, this can be of json, html or plain text based on the request. - > Enclose the body in Single Quotes(\') +- `-c` or `--ctype` for the _Content Type_ + +- `-b` or `--body` for the Data Body, this can be of json, html or plain text based on the request. + +> Enclose the body in Single Quotes(\') + +**Content Types can be of** -**Content Types can be of** -`html` : `text/html` -`js` : `application/json` -`xml` : `application/xml` -`plain` : `text/plain` +|Short Code|Content Type| +|:---:|:---:| +|`js`|`application/json`| +|`html`|`text/html`| +|`xml`|`application/xml`| +|`plain`|`text/plain`| diff --git a/assets/send.png b/assets/send.png index 8072cda..7c55257 100644 Binary files a/assets/send.png and b/assets/send.png differ diff --git a/cli.go b/cli.go index 0254fc1..f3b6125 100644 --- a/cli.go +++ b/cli.go @@ -11,12 +11,12 @@ import ( ) // VERSION is set by `make` during the build to the most recent tag -var VERSION = "" +var buildVersion = "unknown" func main() { app := cli.NewApp() app.Name = color.HiGreenString("Hoppscotch CLI") - app.Version = color.HiRedString(VERSION) + app.Version = color.HiRedString(buildVersion) app.Usage = color.HiYellowString("Test API endpoints without the hassle") app.Description = color.HiBlueString("Made with <3 by Hoppscotch Team") @@ -36,6 +36,17 @@ func main() { Usage: "Add the Password", }, } + genFlags := []cli.Flag{ + cli.IntFlag{ + Name: "port, p", + Usage: "Port at which the server will open to", + Value: 1341, + }, + cli.BoolFlag{ + Name: "browser, b", + Usage: "Whether to open the browser automatically", + }, + } postFlags := []cli.Flag{ cli.StringFlag{ Name: "token, t", @@ -124,6 +135,17 @@ func main() { return nil }, }, + { + Name: "gen", + Usage: "Generate Documentation from the Hoppscotch Collection.json", + Flags: genFlags, + Action: func(c *cli.Context) error { + if err := mets.GenerateDocs(c); err != nil { + return err + } + return nil + }, + }, } cli.AppHelpTemplate = fmt.Sprintf(`%s diff --git a/go.mod b/go.mod index 4b0b192..85c04cf 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.13 require ( github.com/fatih/color v1.9.0 + github.com/knadh/stuffbin v1.1.0 github.com/olekukonko/tablewriter v0.0.4 + github.com/pkg/browser v0.0.0-20201112035734-206646e67786 github.com/stretchr/testify v1.4.0 // indirect github.com/tidwall/pretty v1.0.1 github.com/urfave/cli v1.22.2 diff --git a/go.sum b/go.sum index 289477d..d2ff4f6 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knadh/stuffbin v1.1.0 h1:f5S5BHzZALjuJEgTIOMC9NidEnBJM7Ze6Lu1GHR/lwU= +github.com/knadh/stuffbin v1.1.0/go.mod h1:yVCFaWaKPubSNibBsTAJ939q2ABHudJQxRWZWV5yh+4= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -24,6 +26,8 @@ github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+tw github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/pkg/browser v0.0.0-20201112035734-206646e67786 h1:4Gk0Dsp90g2YwfsxDOjvkEIgKGh+2R9FlvormRycveA= +github.com/pkg/browser v0.0.0-20201112035734-206646e67786/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= diff --git a/methods/docs.go b/methods/docs.go new file mode 100644 index 0000000..4ec1aad --- /dev/null +++ b/methods/docs.go @@ -0,0 +1,91 @@ +package methods + +import ( + "bytes" + "fmt" + "log" + "net/http" + "os" + "strconv" + "time" + + "github.com/knadh/stuffbin" + "github.com/pkg/browser" + "github.com/urfave/cli" +) + +//FileTrunk handles the buffer for generated README.md File +type FileTrunk struct{ bytes.Buffer } + +// Name holds the FileName, here README.md +func (f *FileTrunk) Name() string { return "README.md" } + +// Size holds the size of the File +func (f *FileTrunk) Size() int64 { return int64(f.Len()) } + +// Mode holds the file Mode +func (f *FileTrunk) Mode() os.FileMode { return 0755 } + +// ModTime holds creation time of File +func (f *FileTrunk) ModTime() time.Time { return time.Now() } + +// IsDir checks if True +func (f *FileTrunk) IsDir() bool { return false } + +// Sys - I have no idea +func (f *FileTrunk) Sys() interface{} { return nil } + +//GenerateDocs generates the Documentation site from the hoppscotch-collection.json +func GenerateDocs(c *cli.Context) error { + execPath, err := os.Executable() //get Executable Path for StuffBin + if err != nil { + return err + } + fs, err := initFileSystem(execPath) //Init Virtual FS + if err != nil { + return err + } + + colls, err := ReadCollection(c.Args().Get(0)) + if err != nil { + return err + } + // FuncMap for the HTML template + fmap := map[string]interface{}{ + "html": func(val string) string { return val }, + } + + t, err := stuffbin.ParseTemplates(fmap, fs, "/template.md") + + // f will be used to store rendered templates in memory. + var f FileTrunk + + // Execute the template to the file. + if err = t.Execute(&f, colls); err != nil { + return err + } + + if err := fs.Add(stuffbin.NewFile("/README.md", &f, f.Bytes())); err != nil { + return err + } + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + out, err := fs.Read("templates/index.html") + if err != nil { + log.Println(err) + } + w.Write(out) + }) + PortStr := ":" + strconv.Itoa(c.Int("port")) + URL := fmt.Sprintf("http://localhost%s", PortStr) + + http.Handle("/static/", http.StripPrefix("/static/", fs.FileServer())) + + log.Printf("\033[1;36mServer Listening at %s\033[0m", URL) + + if !c.Bool("browser") { //Check if User wants to open the Broswer + browser.OpenURL(URL) // AutoOpen the Broswer + } + + http.ListenAndServe(PortStr, nil) + return nil +} diff --git a/methods/fs.go b/methods/fs.go new file mode 100644 index 0000000..3d353f9 --- /dev/null +++ b/methods/fs.go @@ -0,0 +1,32 @@ +package methods + +import ( + "log" + + "github.com/knadh/stuffbin" +) + +func initFileSystem(binPath string) (stuffbin.FileSystem, error) { + fs, err := stuffbin.UnStuff(binPath) + // If files are not stuffed with the binary, + // try to pick up files from local file system. + if err == stuffbin.ErrNoID { + // Running in local mode. Load the required static assets into + // the in-memory stuffbin.FileSystem. + + files := []string{ + "./templates/index.html", + "./templates/template.md:/template.md", + } + + // mutates err object. + fs, err = stuffbin.NewLocalFS("/", files...) + if err != nil { + log.Println("Error in Virtual FS", err) + } + } + + // Either unstuff or NewLocalFS throws error, + // mutated error value will be returned + return fs, err +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..4f316f6 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,23 @@ + + + + + + + API Dococumentation + + + +
+ + + + + \ No newline at end of file diff --git a/templates/template.md b/templates/template.md new file mode 100644 index 0000000..55c9035 --- /dev/null +++ b/templates/template.md @@ -0,0 +1,175 @@ +{{ range . }} + +# {{.Name}} + +{{- if .Folders}} +{{- range .Folders}} + +--- + +## Folder: {{.Name}} +{{- range .Requests }} + +--- + +### {{.Name}} + +**Method**: {{.Method}} + +**RequestURL**: `{{ .URL }}{{ .Path}}` + +{{if .Headers}} +**Headers**: + + + + + + {{- range .Headers}} + + + + + {{- end -}} +
KeyValue
{{- .Key}}`{{- .Value}}`
+{{- end}} +{{- if .Params}} +**Params**: + + + + + + + {{- range .Params}} + + + + + + {{- end}} +
typeKeyValue
{{- .type}}`{{- .key}}``{{- .value}}`
+{{- end -}} + +{{- if ne .Auth "None" }} + +**Authentication Type**: {{ .Auth}} + +{{- if .Token}} + +**BearerToken**: `{{ .Token -}}` + +{{ end}} + +{{- if and .User .Pass}} + +**Username**: `{{ .User}}` +**Password**: `{{ .Pass}}` + +{{ end -}} + +{{ end}} + +{{- if and .RawParams (ne .RawParams "{}")}} +**RawParams:** + +```json +{{ .RawParams | html}} +``` + +{{ if .Ctype}}**ContentType**: `{{ .Ctype}}` {{ end -}} + +{{ end -}} + +**Pre Request Script**: + +```js +{{ .PreRequestScript | html}} +``` + +**Test Script**: + +```js +{{ .TestScript | html}} +``` + +{{ end -}}{{ end -}}{{ else}} +{{- range .Requests}} + +--- + +### {{ .Name}} + +**Method**: {{ .Method}} + +**RequestURL**: `{{ .URL}}{{ .Path}}` + +{{ if .Headers}} +**Headers**: + + + + + + {{- range .Headers}} + + + + + {{- end -}} +
KeyValue
{{- .Key}}`{{- .Value}}`
+{{- end}} +{{- if .Params}} +**Params**: + + + + + + + {{- range .Params}} + + + + + + {{- end}} +
typeKeyValue
{{- .type}}`{{- .key}}``{{- .value}}`
+{{- end -}} +{{ if ne .Auth "None"}} +**Authentication Type**: {{.Auth}} + +{{ if .Token}}**BearerToken**: `{{ .Token}}`{{ end}} + +{{ if and .User .Pass}} +Username: `{{ .User}}` +Password: `{{ .Pass}}` +{{ end}} +{{ end}} +{{ if and .RawParams (ne .RawParams "{}")}} +**RawParams**: + +```json +{{ .RawParams | html}} +``` + +{{ end}} +{{ if .Ctype}}**ContentType**: `{{ .Ctype}}` {{ end}} + +**Pre Request Script**: + +```js +{{ .PreRequestScript | html}} +``` + +**Test Script**: + +```js +{{ .TestScript | html}} +``` + +{{ end }}{{ end}}{{ end}} + +--- + +Generated by [HoppScotch CLI](https://github.com/hoppscotch/hopp-cli) ❤️ \ No newline at end of file