aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Grayson Koonce <grayson.koonce@gmail.com> 2016-10-12 11:04:26 -0700
committerGravatar GitHub <noreply@github.com> 2016-10-12 11:04:26 -0700
commitac8374fd17e30fca9a7773a2a6f690a7ea4d2ec9 (patch)
treee9a61adde4bc5eef80583f3e1d6f9379a20b4f99
parentb80c4f3e920994823061eb05dc0a5bc3881ddb02 (diff)
downloadsally-ac8374fd17e30fca9a7773a2a6f690a7ea4d2ec9.tar.gz
sally-ac8374fd17e30fca9a7773a2a6f690a7ea4d2ec9.tar.zst
sally-ac8374fd17e30fca9a7773a2a6f690a7ea4d2ec9.zip
Rework as HTTP server (#15)
-rw-r--r--Makefile16
-rw-r--r--README.md21
-rw-r--r--bindata.go260
-rw-r--r--config.go (renamed from parse.go)0
-rw-r--r--config_test.go29
-rw-r--r--glide.lock27
-rw-r--r--glide.yaml5
-rw-r--r--handler.go88
-rw-r--r--handler_test.go102
-rw-r--r--main.go19
-rw-r--r--templates/index.tpl10
-rw-r--r--templates/package.tpl11
-rw-r--r--utils_test.go66
-rw-r--r--write.go94
14 files changed, 358 insertions, 390 deletions
diff --git a/Makefile b/Makefile
index d548cd7..d26439a 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.md b/README.md
index 65577be..51527ed 100644
--- a/README.md
+++ b/README.md
@@ -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/parse.go b/config.go
index 5071f37..5071f37 100644
--- a/parse.go
+++ b/config.go
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"})
+}
diff --git a/glide.lock b/glide.lock
index 66c8804..06301bd 100644
--- a/glide.lock
+++ b/glide.lock
@@ -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: []
diff --git a/glide.yaml b/glide.yaml
index 3d8e188..307ffa9 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -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>
+`)
+}
diff --git a/main.go b/main.go
index 2625a5b..9bd56e8 100644
--- a/main.go
+++ b/main.go
@@ -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
-}