diff options
author | 2016-10-12 11:04:26 -0700 | |
---|---|---|
committer | 2016-10-12 11:04:26 -0700 | |
commit | ac8374fd17e30fca9a7773a2a6f690a7ea4d2ec9 (patch) | |
tree | e9a61adde4bc5eef80583f3e1d6f9379a20b4f99 | |
parent | b80c4f3e920994823061eb05dc0a5bc3881ddb02 (diff) | |
download | sally-ac8374fd17e30fca9a7773a2a6f690a7ea4d2ec9.tar.gz sally-ac8374fd17e30fca9a7773a2a6f690a7ea4d2ec9.tar.zst sally-ac8374fd17e30fca9a7773a2a6f690a7ea4d2ec9.zip |
Rework as HTTP server (#15)
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | README.md | 21 | ||||
-rw-r--r-- | bindata.go | 260 | ||||
-rw-r--r-- | config.go (renamed from parse.go) | 0 | ||||
-rw-r--r-- | config_test.go | 29 | ||||
-rw-r--r-- | glide.lock | 27 | ||||
-rw-r--r-- | glide.yaml | 5 | ||||
-rw-r--r-- | handler.go | 88 | ||||
-rw-r--r-- | handler_test.go | 102 | ||||
-rw-r--r-- | main.go | 19 | ||||
-rw-r--r-- | templates/index.tpl | 10 | ||||
-rw-r--r-- | templates/package.tpl | 11 | ||||
-rw-r--r-- | utils_test.go | 66 | ||||
-rw-r--r-- | write.go | 94 |
14 files changed, 358 insertions, 390 deletions
@@ -1,10 +1,22 @@ +PACKAGES := $(shell glide novendor) + .PHONY: install install: glide --version || go get github.com/Masterminds/glide glide install - go get -u github.com/jteeuwen/go-bindata/... + + +.PHONY: lint +lint: + go vet $(PACKAGES) + $(foreach pkg, $(PACKAGES), golint $(pkg) &&) echo "success" + + +.PHONY: test +test: lint + go test -race $(PACKAGES) .PHONY: run run: - go generate && go build && ./sally + go build -i && ./sally @@ -1,7 +1,24 @@ # sally -A canonical import path static site generator for Go +A tiny HTTP server for supporting custom Golang import paths ## Installation -`go get -u go.uber.org/sally` +`go get go.uber.org/sally` + +## Usage + +Create a YAML file with the following structure: + +```yaml +url: google.golang.org +packages: + grpc: + repo: github.com/grpc/grpc-go +``` + +Then run Sally to start the HTTP server: + +``` +$ sally -yml site.yaml -port 5000 +``` diff --git a/bindata.go b/bindata.go deleted file mode 100644 index 072daf3..0000000 --- a/bindata.go +++ /dev/null @@ -1,260 +0,0 @@ -// Code generated by go-bindata. -// sources: -// templates/index.tpl -// templates/package.tpl -// DO NOT EDIT! - -package main - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -func (fi bindataFileInfo) Name() string { - return fi.name -} -func (fi bindataFileInfo) Size() int64 { - return fi.size -} -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} -func (fi bindataFileInfo) IsDir() bool { - return false -} -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _templatesIndexTpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb2\x51\x74\xf1\x77\x0e\x89\x0c\x70\x55\xc8\x28\xc9\xcd\xb1\xe3\xb2\x81\x50\x9c\x36\x49\xf9\x29\x95\x40\x9a\xd3\xa6\x14\xc4\xe5\xe4\xac\xae\x56\x28\x4a\xcc\x4b\x4f\x55\x50\xc9\x4e\xad\xd4\x51\x50\x29\x4b\xcc\x29\x4d\x55\xb0\xb2\x55\xd0\x0b\x48\x4c\xce\x4e\x4c\x4f\x2d\x56\xa8\xad\x05\x29\xe4\xb4\xc9\xc9\xb4\x03\xaa\x06\xa9\x03\x0a\x29\xe8\x2a\x80\x38\x60\xe5\x7a\x41\xa9\x05\xf9\x40\x31\x1b\x7d\xa0\x12\xa8\xa1\xa9\x79\x29\x10\x8d\x36\xfa\x60\x9b\x6c\xf4\x21\x36\xdb\xe8\x43\x5c\x02\x08\x00\x00\xff\xff\xb2\xe2\x86\x15\xa1\x00\x00\x00") - -func templatesIndexTplBytes() ([]byte, error) { - return bindataRead( - _templatesIndexTpl, - "templates/index.tpl", - ) -} - -func templatesIndexTpl() (*asset, error) { - bytes, err := templatesIndexTplBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "templates/index.tpl", size: 161, mode: os.FileMode(420), modTime: time.Unix(1475274933, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -var _templatesPackageTpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\x91\xb1\x4e\xc3\x30\x10\x86\x77\x9e\xe2\x08\x73\x6b\x66\x70\xba\x14\xc4\x52\x41\x55\xc1\xc0\x68\x92\xbf\x89\x25\xdb\x57\xec\x4b\x25\x14\xe5\xdd\x71\x9a\x4a\x05\x51\x04\x5e\x6c\x9f\xad\xef\xfb\x75\xa7\x2f\xef\x9e\x96\xcf\xaf\xeb\x7b\x6a\xc5\xbb\xc5\x85\x9e\x36\xca\x4b\xb7\x30\xf5\x74\x3c\x5c\x3d\xc4\x50\x30\x1e\x65\xd1\xf0\xcc\xfa\x1d\x47\x29\xa8\xe2\x20\x08\x52\x16\x7d\x4f\xf3\xa5\x09\x1c\x6c\x65\xdc\xcb\x66\x45\xc3\x40\x8d\x95\xcc\x95\x5d\xba\x51\x6a\x7c\xdf\x60\xc7\xb9\x5e\xfc\x4a\x4d\xdc\xc5\x0a\x7f\x50\xcf\x10\xcf\xd5\x94\x44\x40\x79\x93\x04\xb1\x57\xb5\x8d\xff\xfc\xa5\xfa\xad\x75\x18\xae\x56\xbd\xb3\x01\x3f\xc3\x8e\x90\x19\xde\x3b\xbb\x2f\x8b\x88\x6d\x44\x6a\xbf\x04\xbe\xbe\xa5\x2e\xba\x72\x34\x3c\x70\xcd\xd5\x94\xf9\x08\xd1\xea\xd4\x53\xfd\xc6\xf5\xc7\x89\xfd\xc8\xd2\xda\xd0\x90\x30\x25\x80\x5a\x44\xcc\x69\xed\x60\x12\x48\x67\x69\x36\x4d\xdd\xf8\x46\xf5\xbc\x07\x19\xc7\xa1\xd1\xca\x2c\xe6\x47\xc9\x44\xce\xb2\xc3\x2c\x3f\x03\x00\x00\xff\xff\x69\xc7\x6b\xa3\xe3\x01\x00\x00") - -func templatesPackageTplBytes() ([]byte, error) { - return bindataRead( - _templatesPackageTpl, - "templates/package.tpl", - ) -} - -func templatesPackageTpl() (*asset, error) { - bytes, err := templatesPackageTplBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "templates/package.tpl", size: 483, mode: os.FileMode(420), modTime: time.Unix(1475274933, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "templates/index.tpl": templatesIndexTpl, - "templates/package.tpl": templatesPackageTpl, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png -// then AssetDir("data") would return []string{"foo.txt", "img"} -// AssetDir("data/img") would return []string{"a.png", "b.png"} -// AssetDir("foo.txt") and AssetDir("notexist") would return an error -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - cannonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(cannonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} -var _bintree = &bintree{nil, map[string]*bintree{ - "templates": &bintree{nil, map[string]*bintree{ - "index.tpl": &bintree{templatesIndexTpl, map[string]*bintree{}}, - "package.tpl": &bintree{templatesPackageTpl, map[string]*bintree{}}, - }}, -}} - -// RestoreAsset restores an asset under the given directory -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) - if err != nil { - return err - } - return nil -} - -// RestoreAssets restores an asset under the given directory recursively -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - cannonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) -} - diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..10422e9 --- /dev/null +++ b/config_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + path, clean := TempFile(t, ` + +url: google.golang.org +packages: + grpc: + repo: github.com/grpc/grpc-go + +`) + defer clean() + + config, err := Parse(path) + assert.NoError(t, err) + + assert.Equal(t, config.URL, "google.golang.org") + + pkg, ok := config.Packages["grpc"] + assert.True(t, ok) + + assert.Equal(t, pkg, Package{Repo: "github.com/grpc/grpc-go"}) +} @@ -1,6 +1,27 @@ -hash: 733b6e3d1ba3612c5d43aca4fe2243be4f39f09d609221d841d2eea1d8a060ad -updated: 2016-08-07T15:19:06.856947619-07:00 +hash: 8f67008e0ad384b7bab0cf30ee7b6926b7145c5be7e95b6be35163930eb7f7e6 +updated: 2016-10-12T10:19:26.528895463-07:00 imports: +- name: github.com/davecgh/go-spew + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 + subpackages: + - spew +- name: github.com/julienschmidt/httprouter + version: 8c199fb6259ffc1af525cc3ad52ee60ba8359669 +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/stretchr/testify + version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 + subpackages: + - assert +- name: github.com/yosssi/gohtml + version: ccf383eafddde21dfe37c6191343813822b30e6b +- name: golang.org/x/net + version: 8058fc7b18f8794d9fc57eee98d64fe2c750e3f1 + subpackages: + - html + - html/atom - name: gopkg.in/yaml.v2 - version: e4d366fc3c7938e2958e662b4258c7a89e1f0e3e + version: 31c299268d302dd0aa9a0dcf765a3d58971ac83f testImports: [] @@ -1,3 +1,8 @@ package: github.com/uber-go/sally import: - package: gopkg.in/yaml.v2 +- package: github.com/julienschmidt/httprouter + version: ^1 +testImport: +- package: github.com/stretchr/testify +- package: github.com/yosssi/gohtml diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..09f5999 --- /dev/null +++ b/handler.go @@ -0,0 +1,88 @@ +package main + +import ( + "fmt" + "html/template" + "net/http" + + "github.com/julienschmidt/httprouter" +) + +// CreateHandler creates a Sally http.Handler +func CreateHandler(config Config) http.Handler { + router := httprouter.New() + router.RedirectTrailingSlash = false + + router.GET("/", indexHandler{config: config}.Handle) + + for name, pkg := range config.Packages { + handle := packageHandler{ + pkgName: name, + pkg: pkg, + config: config, + }.Handle + router.GET(fmt.Sprintf("/%s", name), handle) + router.GET(fmt.Sprintf("/%s/*path", name), handle) + } + + return router +} + +type indexHandler struct { + config Config +} + +func (h indexHandler) Handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + if err := indexTemplate.Execute(w, h.config); err != nil { + http.Error(w, err.Error(), 500) + } +} + +var indexTemplate = template.Must(template.New("index").Parse(` +<!DOCTYPE html> +<html> + <body> + <ul> + {{ range $key, $value := .Packages }} + <li>{{ $key }} - {{ $value.Repo }}</li> + {{ end }} + </ul> + </body> +</html> +`)) + +type packageHandler struct { + pkgName string + pkg Package + config Config +} + +func (h packageHandler) Handle(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + canonicalURL := fmt.Sprintf("%s/%s", h.config.URL, h.pkgName) + data := struct { + Repo string + CanonicalURL string + GodocURL string + }{ + Repo: h.pkg.Repo, + CanonicalURL: canonicalURL, + GodocURL: fmt.Sprintf("https://godoc.org/%s%s", canonicalURL, ps.ByName("path")), + } + if err := packageTemplate.Execute(w, data); err != nil { + http.Error(w, err.Error(), 500) + } +} + +var packageTemplate = template.Must(template.New("package").Parse(` +<!DOCTYPE html> +<html> + <head> + <meta name="go-import" content="{{ .CanonicalURL }} git https://{{ .Repo }}"> + <meta name="go-source" content="{{ .CanonicalURL }} https://{{ .Repo }} https://{{ .Repo }}/tree/master{/dir} https://{{ .Repo }}/tree/master{/dir}/{file}#L{line}"> + <meta http-equiv="refresh" content="0; url={{ .GodocURL }}"> + </head> + <body> + Nothing to see here. Please <a href="{{ .GodocURL }}">move along</a>. + </body> +</html> +`)) diff --git a/handler_test.go b/handler_test.go new file mode 100644 index 0000000..9f2eca8 --- /dev/null +++ b/handler_test.go @@ -0,0 +1,102 @@ +package main + +import "testing" + +var config = ` + +url: go.uber.org +packages: + yarpc: + repo: github.com/yarpc/yarpc-go + thriftrw: + repo: github.com/thriftrw/thriftrw-go + +` + +func TestIndex(t *testing.T) { + rr := CallAndRecord(t, config, "/") + AssertResponse(t, rr, 200, ` +<!DOCTYPE html> +<html> + <body> + <ul> + <li>thriftrw - github.com/thriftrw/thriftrw-go</li> + <li>yarpc - github.com/yarpc/yarpc-go</li> + </ul> + </body> +</html> +`) +} + +func TestPackageShouldExist(t *testing.T) { + rr := CallAndRecord(t, config, "/yarpc") + AssertResponse(t, rr, 200, ` +<!DOCTYPE html> +<html> + <head> + <meta name="go-import" content="go.uber.org/yarpc git https://github.com/yarpc/yarpc-go"> + <meta name="go-source" content="go.uber.org/yarpc https://github.com/yarpc/yarpc-go https://github.com/yarpc/yarpc-go/tree/master{/dir} https://github.com/yarpc/yarpc-go/tree/master{/dir}/{file}#L{line}"> + <meta http-equiv="refresh" content="0; url=https://godoc.org/go.uber.org/yarpc"> + </head> + <body> + Nothing to see here. Please <a href="https://godoc.org/go.uber.org/yarpc">move along</a>. + </body> +</html> +`) +} + +func TestNonExistentPackageShould404(t *testing.T) { + rr := CallAndRecord(t, config, "/nonexistent") + AssertResponse(t, rr, 404, ` +404 page not found +`) +} + +func TestTrailingSlash(t *testing.T) { + rr := CallAndRecord(t, config, "/yarpc/") + AssertResponse(t, rr, 200, ` +<!DOCTYPE html> +<html> + <head> + <meta name="go-import" content="go.uber.org/yarpc git https://github.com/yarpc/yarpc-go"> + <meta name="go-source" content="go.uber.org/yarpc https://github.com/yarpc/yarpc-go https://github.com/yarpc/yarpc-go/tree/master{/dir} https://github.com/yarpc/yarpc-go/tree/master{/dir}/{file}#L{line}"> + <meta http-equiv="refresh" content="0; url=https://godoc.org/go.uber.org/yarpc/"> + </head> + <body> + Nothing to see here. Please <a href="https://godoc.org/go.uber.org/yarpc/">move along</a>. + </body> +</html> +`) +} + +func TestDeepImports(t *testing.T) { + rr := CallAndRecord(t, config, "/yarpc/heeheehee") + AssertResponse(t, rr, 200, ` +<!DOCTYPE html> +<html> + <head> + <meta name="go-import" content="go.uber.org/yarpc git https://github.com/yarpc/yarpc-go"> + <meta name="go-source" content="go.uber.org/yarpc https://github.com/yarpc/yarpc-go https://github.com/yarpc/yarpc-go/tree/master{/dir} https://github.com/yarpc/yarpc-go/tree/master{/dir}/{file}#L{line}"> + <meta http-equiv="refresh" content="0; url=https://godoc.org/go.uber.org/yarpc/heeheehee"> + </head> + <body> + Nothing to see here. Please <a href="https://godoc.org/go.uber.org/yarpc/heeheehee">move along</a>. + </body> +</html> +`) + + rr = CallAndRecord(t, config, "/yarpc/heehee/hawhaw") + AssertResponse(t, rr, 200, ` +<!DOCTYPE html> +<html> + <head> + <meta name="go-import" content="go.uber.org/yarpc git https://github.com/yarpc/yarpc-go"> + <meta name="go-source" content="go.uber.org/yarpc https://github.com/yarpc/yarpc-go https://github.com/yarpc/yarpc-go/tree/master{/dir} https://github.com/yarpc/yarpc-go/tree/master{/dir}/{file}#L{line}"> + <meta http-equiv="refresh" content="0; url=https://godoc.org/go.uber.org/yarpc/heehee/hawhaw"> + </head> + <body> + Nothing to see here. Please <a href="https://godoc.org/go.uber.org/yarpc/heehee/hawhaw">move along</a>. + </body> +</html> +`) +} @@ -2,22 +2,25 @@ package main // import "go.uber.org/sally" import ( "flag" + "fmt" "log" + "net/http" ) -//go:generate go-bindata templates/ - func main() { yml := flag.String("yml", "sally.yaml", "yaml file to read config from") - dir := flag.String("dir", "out", "directory to write html files to") + port := flag.Int("port", 8080, "port to listen and serve on") flag.Parse() - c, err := Parse(*yml) + log.Printf("Parsing yaml at path: %s\n", *yml) + config, err := Parse(*yml) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to parse %s: %v", *yml, err) } - if err := Write(c, *dir); err != nil { - log.Fatal(err) - } + log.Printf("Creating HTTP handler with config: %v", config) + handler := CreateHandler(config) + + log.Printf(`Starting HTTP handler on ":%d"`, *port) + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), handler)) } diff --git a/templates/index.tpl b/templates/index.tpl deleted file mode 100644 index bfe01fb..0000000 --- a/templates/index.tpl +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE html> -<html> - <body> - <ul> - {{ range $key, $value := .Packages }} - <li>{{ $key }} - {{ $value.Repo }}</li> - {{ end }} - </ul> - </body> -</html> diff --git a/templates/package.tpl b/templates/package.tpl deleted file mode 100644 index 38deaf3..0000000 --- a/templates/package.tpl +++ /dev/null @@ -1,11 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <meta name="go-import" content="{{ .CanonicalURL }} git https://{{ .Repo }}"> - <meta name="go-source" content="{{ .CanonicalURL }} https://{{ .Repo }} https://{{ .Repo }}/tree/master{/dir} https://{{ .Repo }}/tree/master{/dir}/{file}#L{line}"> - <meta http-equiv="refresh" content="0; url={{ .GodocURL }}"> - </head> - <body> - Nothing to see here. Please <a href="{{ .GodocURL }}">move along</a>. - </body> -</html> diff --git a/utils_test.go b/utils_test.go new file mode 100644 index 0000000..3894e28 --- /dev/null +++ b/utils_test.go @@ -0,0 +1,66 @@ +package main + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yosssi/gohtml" +) + +// TempFile persists contents and returns the path and a clean func +func TempFile(t *testing.T, contents string) (path string, clean func()) { + content := []byte(contents) + tmpfile, err := ioutil.TempFile("", "sally-tmp") + if err != nil { + t.Fatal("Unable to create tmpfile", err) + } + + if _, err := tmpfile.Write(content); err != nil { + t.Fatal("Unable to write tmpfile", err) + } + if err := tmpfile.Close(); err != nil { + t.Fatal("Unable to close tmpfile", err) + } + + return tmpfile.Name(), func() { + os.Remove(tmpfile.Name()) + } +} + +// CreateHandlerFromYAML builds the Sally handler from a yaml config string +func CreateHandlerFromYAML(t *testing.T, content string) (handler http.Handler, clean func()) { + path, clean := TempFile(t, content) + + config, err := Parse(path) + if err != nil { + t.Fatalf("Unable to parse %s: %v", path, err) + } + + return CreateHandler(config), clean +} + +// CallAndRecord makes a GET request to the Sally handler and returns a response recorder +func CallAndRecord(t *testing.T, config string, uri string) *httptest.ResponseRecorder { + handler, clean := CreateHandlerFromYAML(t, config) + defer clean() + + req, err := http.NewRequest("GET", uri, nil) + if err != nil { + t.Fatalf("Unable to create request to %s: %v", uri, err) + } + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + return rr +} + +// AssertResponse normalizes and asserts the body from rr against want +func AssertResponse(t *testing.T, rr *httptest.ResponseRecorder, code int, want string) { + assert.Equal(t, rr.Code, code) + assert.Equal(t, gohtml.Format(want), gohtml.Format(rr.Body.String())) +} diff --git a/write.go b/write.go deleted file mode 100644 index 703b386..0000000 --- a/write.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "html/template" - "io/ioutil" - "os" - "path/filepath" -) - -const ( - indexTplPath = "templates/index.tpl" - packagesTplPath = "templates/package.tpl" -) - -// Write takes a Config and produces a static html site to outDir -func Write(c Config, outDir string) error { - if err := os.MkdirAll(outDir, 0755); err != nil { - return err - } - if err := writeIndex(c, outDir); err != nil { - return err - } - if err := writePackages(c, outDir); err != nil { - return err - } - return nil -} - -func writeIndex(c Config, outDir string) error { - tpl, err := Asset(indexTplPath) - if err != nil { - return err - } - - t, err := template.New(filepath.Base(indexTplPath)).Parse(string(tpl)) - if err != nil { - return err - } - - buf := new(bytes.Buffer) - if err := t.Execute(buf, c); err != nil { - return err - } - - err = ioutil.WriteFile(fmt.Sprintf("%s/index.html", outDir), buf.Bytes(), 0644) - if err != nil { - return err - } - - fmt.Println(buf.String()) - return nil -} - -func writePackages(c Config, outDir string) error { - tpl, err := Asset(packagesTplPath) - if err != nil { - return err - } - - t, err := template.New(filepath.Base(packagesTplPath)).Parse(string(tpl)) - if err != nil { - return err - } - - for name, pkg := range c.Packages { - canonicalURL := fmt.Sprintf("%s/%s", c.URL, name) - tpl := struct { - Name string - CanonicalURL string - GodocURL string - Package - }{ - Name: name, - CanonicalURL: canonicalURL, - GodocURL: fmt.Sprintf("https://godoc.org/%s", canonicalURL), - Package: pkg, - } - - buf := new(bytes.Buffer) - if err := t.Execute(buf, tpl); err != nil { - return err - } - - if err := ioutil.WriteFile(fmt.Sprintf("%s/%s.html", outDir, name), buf.Bytes(), 0644); err != nil { - return err - } - - fmt.Println(buf) - } - - return nil -} |